Skip to content

创建对话后,使用SSE协议发起对话

最近更新日期:2026-01-19 22:12:06

概述

本文档介绍在创建对话后,如何使用 SSE (Server-Sent Events) 协议与 AI 进行实时流式对话。

流程概览

  1. 获取角色列表 - 从服务器获取可用的 AI 角色列表
  2. 创建对话 - 选择角色后创建新对话,获取 chatID
  3. 发送消息 (SSE) - 使用 SSE 协议发送消息并接收流式响应

API 接口说明

1. 获取角色列表

请求

GET /product/zeroai/character/list

请求头

Authorization: {User:Token}

响应示例

json
{
  "code": 200,
  "data": {
    "list": [
      {
        "id": "character_id_1",
        "name": "角色名称"
      }
    ]
  }
}

2. 创建对话

请求

POST /product/zeroai/chat/creat

请求头

Authorization: {User:Token}
Content-Type: multipart/form-data

请求参数 (FormData)

  • characterID: 角色 ID

响应示例

json
{
  "code": 200,
  "data": {
    "id": "chat_id_123"
  }
}

3. 发送消息 (SSE 流式对话)

请求

GET /product/zeroai/chat/chat?chatID={chatID}&text={message}

请求头

Authorization: {User:Token}

SSE 数据格式

data: {"output":"回复内容","action":"动作","expression":"表情"}

data: {"output":"继续回复"}

前端实现代码

完整示例代码

javascript
import { ref, reactive, onMounted, nextTick, watch } from 'vue'
import { MessagePlugin } from 'tdesign-vue-next';
import axios from 'axios';
import config from '../../../js/config.js';
import { useCookies } from "vue3-cookies";

const { cookies } = useCookies();

// 角色列表
const characterList = ref([]);
const selectedCharacter = ref(null);
const loadingCharacters = ref(false);

// 聊天数据
const chatId = ref(null);
const chatHistory = ref([]);
const inputMessage = ref('');
const sendingMessage = ref(false);
const chatContainerRef = ref(null);

// 获取角色列表
const fetchCharacterList = async () => {
    loadingCharacters.value = true;
    try {
        const { data } = await axios.get(`${config.apiBaseUrl}/product/zeroai/character/list`, {
            headers: {
                'Authorization': cookies.get('User:Token')
            }
        });

        if (data.code === 200) {
            if (Array.isArray(data.data)) {
                characterList.value = data.data;
            } else if (data.data.list && Array.isArray(data.data.list)) {
                characterList.value = data.data.list;
            }
        } else {
            MessagePlugin.error(data.message || '获取角色列表失败');
        }
    } catch (error) {
        console.error('获取角色列表失败:', error);
        MessagePlugin.error('获取角色列表失败');
    } finally {
        loadingCharacters.value = false;
    }
};

// 创建对话
const createChat = async () => {
    if (!selectedCharacter.value) {
        MessagePlugin.warning('请先选择角色');
        return;
    }

    try {
        const formData = new FormData();
        formData.append('characterID', selectedCharacter.value);

        const { data } = await axios.post(`${config.apiBaseUrl}/product/zeroai/chat/creat`, formData, {
            headers: {
                'Authorization': cookies.get('User:Token')
            }
        });

        if (data.code === 200) {
            chatId.value = data.data.id;
            chatHistory.value = [];
            MessagePlugin.success('创建对话成功');
        } else {
            MessagePlugin.error(data.message || '创建对话失败');
        }
    } catch (error) {
        console.error('创建对话失败:', error);
        MessagePlugin.error('创建对话失败');
    }
};

// 解析SSE数据
const parseSSEData = (text) => {
    const lines = text.split('\n').filter(line => line.trim().startsWith('data: '));
    let jsonString = '';
    lines.forEach(line => {
        jsonString += line.replace('data: ', '');
    });
    try {
        return JSON.parse(jsonString);
    } catch (e) {
        console.error('解析SSE数据失败:', e);
        return null;
    }
};

// 发送消息
const sendMessage = async () => {
    if (!chatId.value) {
        MessagePlugin.warning('请先创建对话');
        return;
    }
    if (!inputMessage.value.trim()) {
        return;
    }

    // 添加用户消息到历史
    chatHistory.value.push({
        role: 'user',
        content: inputMessage.value
    });

    const message = inputMessage.value;
    inputMessage.value = '';
    sendingMessage.value = true;

    await scrollToBottom();

    try {
        const response = await fetch(`${config.apiBaseUrl}/product/zeroai/chat/chat?chatID=${chatId.value}&text=${encodeURIComponent(message)}`, {
            method: 'GET',
            headers: {
                'Authorization': cookies.get('User:Token')
            }
        });

        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let sseText = '';

        while (true) {
            const { done, value } = await reader.read();
            if (done) break;

            sseText += decoder.decode(value, { stream: true });

            // 解析并更新UI
            const parsedData = parseSSEData(sseText);
            if (parsedData && parsedData.output) {
                // 移除之前的AI消息,重新渲染
                const lastAIMessage = chatHistory.value[chatHistory.value.length - 1];
                if (lastAIMessage && lastAIMessage.role === 'ai') {
                    chatHistory.value[chatHistory.value.length - 1] = {
                        role: 'ai',
                        content: parsedData.output
                    };
                } else {
                    chatHistory.value.push({
                        role: 'ai',
                        content: parsedData.output
                    });
                }
                await scrollToBottom();
            }
        }
    } catch (error) {
        console.error('发送消息失败:', error);
        MessagePlugin.error('发送消息失败');
    } finally {
        sendingMessage.value = false;
    }
};

// 滚动到底部
const scrollToBottom = async () => {
    await nextTick();
    if (chatContainerRef.value) {
        chatContainerRef.value.scrollTop = chatContainerRef.value.scrollHeight;
    }
};

// 监听角色选择变化
watch(selectedCharacter, (newVal) => {
    if (newVal) {
        createChat();
    }
});

// 页面加载时获取角色列表
onMounted(() => {
    fetchCharacterList();
});

关键技术点

1. SSE 数据解析

SSE 流式数据以 data: 前缀格式发送,每行一个数据块。需要累积所有数据块后合并解析为 JSON。

javascript
const parseSSEData = (text) => {
    const lines = text.split('\n').filter(line => line.trim().startsWith('data: '));
    let jsonString = '';
    lines.forEach(line => {
        jsonString += line.replace('data: ', '');
    });
    try {
        return JSON.parse(jsonString);
    } catch (e) {
        console.error('解析SSE数据失败:', e);
        return null;
    }
};

2. Fetch API 读取流

使用 response.body.getReader() 获取流式读取器,通过 TextDecoder 解码数据。

javascript
const response = await fetch(url, { method: 'GET', headers });
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    
    const text = decoder.decode(value, { stream: true });
    // 处理流式数据
}

3. 流式 UI 更新

在接收流式数据时,实时更新聊天历史,实现打字机效果。

javascript
const parsedData = parseSSEData(sseText);
if (parsedData && parsedData.output) {
    const lastAIMessage = chatHistory.value[chatHistory.value.length - 1];
    if (lastAIMessage && lastAIMessage.role === 'ai') {
        // 更新最后一条 AI 消息
        chatHistory.value[chatHistory.value.length - 1] = {
            role: 'ai',
            content: parsedData.output
        };
    } else {
        // 添加新的 AI 消息
        chatHistory.value.push({
            role: 'ai',
            content: parsedData.output
        });
    }
}

4. 自动滚动到底部

使用 Vue 的 nextTick 确保 DOM 更新后再滚动。

javascript
const scrollToBottom = async () => {
    await nextTick();
    if (chatContainerRef.value) {
        chatContainerRef.value.scrollTop = chatContainerRef.value.scrollHeight;
    }
};

注意事项

  1. Token 认证:所有请求都需要携带 Authorization 请求头
  2. URL 编码:发送消息时需要对消息内容进行 encodeURIComponent 编码
  3. 流式数据处理:SSE 数据可能分多次发送,需要累积后统一解析
  4. 错误处理:需要处理网络错误和解析错误
  5. 并发控制:使用 sendingMessage 标志防止重复发送