SoFunction
Updated on 2025-04-05

vue3 implements AI chat dialog box function

Learning of various functional parts

input input

Use the @keydown keyboard to perform operations, and search with Enter and click

@="handleSend"
@="newline"

Button loading loading icon: set template slot here

<el-button
  type="primary"
  :loading="loading"
  @click="handleSend"
  >
  <template #icon>
    <el-icon><Position /></el-icon>
  </template>
  send
</el-button>

The responsibilities are separated, and the child components complete the page construction, what logic is needed to be used, and then communicate to the parent component through emit and is completed by the parent component. The entire page involves loading properties used by multiple components, stored by the settings repository.

message dialog box

A simple line of code puts user messages on the right

  &.message-user {
    flex-direction: row-reverse;
    //Flip to realize user layout on the right    .message-content {
      align-items: flex-end;
    }
  }

Line break attributeswhite-space: pre-wrap;Keep whitespace characters and line breaks in the source code, otherwisewhite-space: normal;(Default): Merge continuous whitespace characters, ignore newlines in the source code, and automatically wrap lines

settings settings panel

  • The settings attributes are all set in the repository and are used for global use.
  • Style Namingw-fullThis can always be easily understood and can be seen that the width is full.
  • How to modify the original style of element Plus in style.
  • elementPlus Set dark mode
//
<template>
  <div :class="{ 'dark': isDarkMode }">
  <router-view />
  </div>
  </template>
  <script setup>
  import { computed } from 'vue'
import { useSettingsStore } from './stores/settings'
const settingsStore = useSettingsStore()
const isDarkMode = computed(() => )
  </script>
//
 {
  // Element Plus Dark Mode Variable Override  --el-bg-color: var(--bg-color);
  --el-bg-color-overlay: var(--bg-color-secondary);
  --el-text-color-primary: var(--text-color-primary);
  --el-text-color-regular: var(--text-color-regular);
  --el-border-color: var(--border-color);
  // Element Plus component dark mode style overlay  .el-input-number {
    --el-input-number-bg-color: var(--bg-color-secondary);
    --el-input-number-text-color: var(--text-color-primary);
  }
  .el-select-dropdown {
    --el-select-dropdown-bg-color: var(--bg-color);
    --el-select-dropdown-text-color: var(--text-color-primary);
  }
  .el-slider {
    --el-slider-main-bg-color: var(--primary-color);
  }
}
//
import './assets/styles/'

Use scss to set dark mode:

1. Use pinia state management.
2. Click to switch in the settings panel to trigger the toggleDarkMode action (set in pinia), switch the isDarlMode state, and update the data-theme attributes of the elements.
3. The style of the dark mode is set in the scss variable

After refreshing, the style will change back to daytime mode, but at this time, the night mode is still selected in settings. Why is this?

A: Although the settings are stored in localStorage, the dark mode style is not properly initialized after the page is refreshed. We need to apply stored theme settings immediately when the app starts.

Solution: Set it once when adding mount.

onMounted(() => {
  // Initialize the topic according to the storage settings  ('data-theme',  ? 'dark' : 'light')
})

Transfer data

axios、 XMLHttpRequest 、fetch

The following isAxiosXMLHttpRequestandFetchComparisons when sending HTTP requests include detailed analysis of usage, performance, compatibility and applicable scenarios:

Comparison of technologies used in front-end and back-end communication

AI conversations require streaming response processing, and communication technology ultimately adopts fetch.

Scene Axios XMLHttpRequest Fetch
Quick development of simple requests Excellent, concise grammar Not suitable, cumbersome code Good, concise grammar
Global configuration requirements Supported, provideddefaultsConfiguration Not supported Need to be implemented manually
Request/response interception processing Native support for interceptors Not supported Need to be implemented manually
Upload/download progress monitoring Not supported support Not supported
Compatible with older browsers Supported bypolyfill support Not supported
Streaming response processing Not supported Not supported Support (combinedReadableStream
Lightweight requirements Suitable Not suitable Suitable

Implement streaming data processing

1. Project implementation code

  • Send a request
    In src/utils/, the method is responsible for sending the request. Determine whether to request a stream response based on the value of the stream parameter.
async sendMessage(messages, stream = false) {
    // ...
    const response = await fetch(`${API_BASE_URL}/chat/completions`, {
        method: 'POST',
        headers: {
            ...createHeaders(),
            ...(stream && { 'Accept': 'text/event-stream' }) // If it is a streaming response, add the corresponding Accept header        },
        body: (payload)
    })
    // ...
    if (stream) {
        return response // For streaming response, directly return the response object    }
    // ...
}

Handle streaming responses
In src/utils/, the processStreamResponse method is responsible for handling streaming responses. It uses the getReader method of ReadableStream to read the data step by step and decode the data using TextDecoder.

  • Read stream data
  • Decode data blocks
  • Process the decoded data: first split into rows (arrays), then convert to json strings, then convert to js object, extract the content content in the object, update the message, and update the token usage
async processStreamResponse(response, { updateMessage, updateTokenCount }) {
  try {
      let fullResponse = '';
      const reader = ();
      const decoder = new TextDecoder();
          // 1. Read stream data      while (true) {
          const { done, value } = await ();
          if (done) {
              ('Stream response is completed');
              break;
          }
          //2. Decode the data block          const chunk = (value);  // Here each chunk is a possible array that may contain multiple arrays          //3. Process the decoded data, first split it into rows (arrays), then convert it into a json string, then convert it into a js object, extract the content content in the object, update the message, and update the usage of token          // 3.1 Split into rows          const lines = ('\n').filter(line => () !== '');
          for (const line of lines) {
              if (('data: ')) {
          // 3.2 Convert to json string                  const jsonStr = ('data: ', '');
                  // Check whether it is finished                  if (jsonStr === '[DONE]') {
                      ('Stream response is completed, read is completed');
                      continue;
                  }
          // 3.3 Convert to js object                  try {
                      const jsData = (jsonStr);
                      if ([0].) {
                          const content = [0].;
          //3.4 Extract the content content from the object and update the message                          fullResponse += content;
                          updateMessage(fullResponse);
                      }
          // 3.5 update token usage                      if () {
                          updateTokenCount();
                      }
                  } catch (e) {
                      ('Parse JSON failed:', e);
                  }
              }
          }
      }
  } catch (error) {
      ('Stream processing error:', error);
      throw error;
  }
},

Update the interface
In src/views/, the handleSend method calls processStreamResponse and updates the interface through the callback functions updateMessage and updateTokenCount.

const handleSend = async (content) => {
    // ...
    try {
        const response = await (
            (0, -1).map(m => ({
                role: ,
                content: 
            })),
            
        );
        if () {
          // Will this change to synchronization when using await? I learned that after using await, I will wait for the subsequent function to be called before executing the subsequent code. Is that true?            await (response, {
                updateMessage: (content) => (content),
                updateTokenCount: (usage) => (usage)
            });
        }
        // ...
    } catch (error) {
        ('Sorry, an error occurred, please try again later.  ')
    } finally {
         = false
    }
}

2. Knowledge Points

2.1. Questions and answers

Several core concepts: Is () a method of ReadableStream? (), 4. Uint8Array, 5. Streams API,
(The returned by fetch is a problem derived from the ReadableStream object) What are the ways to deal with responses with fetch? What is the type?

2.1.1. Understanding of the answer

Concepts about streaming

ReadableStreamIt is one of the core objects of StreamsAPI, which involves it because of network requests.It's oneReadableStreamObject.

In-depth addition:Streams APIIt is a Web API for streaming data processinggetReader()yesReadableStreamA method of (because 1 soThere is also this method)

This method will return a reader object (here is the abbreviation), which can read data in the stream block by block and provide complete control of the stream (Note: After using getReader, the stream cannot be accessed elsewhere, meaning that only the reader object it returns can access the stream)

There is a method for the reader object()Read data blocks in the stream asynchronously. The return value is as follows:

{ done: true/false, value: Uint8Array | undefined }
  • done: Iftrue, means that the stream has been read.
  • value: The currently read block data, usuallyUint8Array, each element takes up 1 byte.

Uint8ArrayIt's a kindTyped arrays, efficiently process raw binary data, such as file blocks, images, and network responses.

Processing of binary data:

  • WillUint8ArrayConvert to a string, useTextDecoder, coded asutf-8utf-16. Use itdecodeMethod converts byte data into a string.
  • Convert to image/video, useBlob, set the type image or video, and then convert the generated blob object to a URL, and you can use it.

Summary illustration:

Streams API  
├── ReadableStream(type) → Provide streaming data blocks
│    ├── getReader() → Get reader
│    │    └── () → Read a single block of data {done, value}
│    │
│    └── Data blocks are usually Uint8Array type
│
└── TextDecoder → decoding Uint8Array as a string
└── Blob → decoding Uint8Array For image video

fetch response method is extremely type

method Return type Common Scenes
() Promise<string> Text, HTML
() Promise<Object> JSON API Response
() Promise<Blob> Download pictures, videos, files
() Promise<ArrayBuffer> Binary data, file parsing
() Promise<FormData> Form Response (Rare)
ReadableStream Real-time processing, progress tracking

Notice:

The response body of fetch can only be read once (that is, it cannot be called at the same time()and()wait )

Even if the response has an error status (such as 404),fetchNo exception is thrown, it needs to be checked manually. The following code processing method:

let response = await fetch('');
if (!) {
  throw new Error(`HTTP error! status: ${}`);
}

See the full code:github

This is the article about the implementation of Vue3 Ai chat dialog box. For more related contents of Vue3 Ai chat dialog box, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!