From 4cf415ff548dde88742b1ab53659fd64fb7a6726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E5=A3=AE?= Date: Thu, 2 Apr 2026 09:45:40 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=A1=B5=E9=9D=A2=E5=92=8C=20ESLint=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=AF=BC=E5=85=A5=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: 1. AI 对话测试页面 - 用于测试 AI 模型对话功能 - 带详细日志输出 - 支持多种模型选择 2. ESLint 自动导入配置 - 企业门户和经销商门户的自动导入规则 - 提升开发体验 Co-Authored-By: Claude Sonnet 4.6 --- .../apps/web-antd/src/api/chat/test/index.ts | 206 +++++++ .../web-antd/src/views/chat/test/index.vue | 541 ++++++++++++++++++ .../.eslintrc-auto-import.json | 78 +++ .../.eslintrc-auto-import.json | 76 +++ 4 files changed, 901 insertions(+) create mode 100644 hzhub-admin/apps/web-antd/src/api/chat/test/index.ts create mode 100644 hzhub-admin/apps/web-antd/src/views/chat/test/index.vue create mode 100644 hzhub-portal-company/.eslintrc-auto-import.json create mode 100644 hzhub-portal-dealer/.eslintrc-auto-import.json diff --git a/hzhub-admin/apps/web-antd/src/api/chat/test/index.ts b/hzhub-admin/apps/web-antd/src/api/chat/test/index.ts new file mode 100644 index 0000000..bd52188 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/api/chat/test/index.ts @@ -0,0 +1,206 @@ +import { useAccessStore } from '@vben/stores'; + +import { useAppConfig } from '@vben/hooks'; + +// 获取 clientId 配置 +const { clientId } = useAppConfig(import.meta.env, import.meta.env.PROD); + +/** + * 对话请求参数 + */ +export interface ChatTestRequest { + /** 模型 */ + model: string; + /** 对话消息 */ + content: string; + /** 会话ID */ + sessionId?: number; + /** 是否启用工作流 */ + enableWorkFlow?: boolean; + /** 是否启用深度思考 */ + enableThinking?: boolean; + /** 是否联网 */ + enableInternet?: boolean; +} + +/** + * 对话响应 + */ +export interface ChatTestResponse { + uuid: string; + sessionId: number; + model: string; + content: string; + role: string; + createTime?: string; +} + +/** + * 日志类型 + */ +export type LogType = 'info' | 'error' | 'success' | 'warning'; + +/** + * 日志条目 + */ +export interface LogEntry { + time: string; + type: LogType; + message: string; + data?: any; +} + +/** + * 发送聊天消息 (SSE 流式响应) + * + * 使用原生 fetch API 处理 SSE 流 + * + * @param data 对话请求 + * @param onMessage 消息回调 + * @param onComplete 完成回调 + * @param onError 错误回调 + * @param onLog 日志回调 + * @returns abort 函数 + */ +export function sendChatMessageStream( + data: ChatTestRequest, + onMessage: (data: ChatTestResponse) => void, + onComplete?: () => void, + onError?: (error: Error) => void, + onLog?: (log: LogEntry) => void, +): () => void { + // 记录日志 + const log = (type: LogType, message: string, data?: any) => { + onLog?.({ + time: new Date().toLocaleTimeString(), + type, + message, + data, + }); + }; + + // 从 pinia store 获取 token + const accessStore = useAccessStore(); + const token = accessStore.accessToken; + + if (!token) { + const error = new Error('未登录或会话已过期,请重新登录'); + log('error', error.message); + onError?.(error); + return () => {}; + } + + log('info', '已获取认证 token'); + + const abortController = new AbortController(); + + // 发送聊天请求 + log('info', `发送聊天请求: POST /api/chat/send`, data); + + fetch('/api/chat/send', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'ClientID': clientId, + }, + body: JSON.stringify(data), + signal: abortController.signal, + }) + .then(async (response) => { + log('info', `聊天请求响应状态: ${response.status}`); + + if (!response.ok) { + const text = await response.text(); + throw new Error(`HTTP ${response.status}: ${text.substring(0, 200)}`); + } + + // 处理 SSE 流 + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + + if (!reader) { + throw new Error('无法获取响应流'); + } + + log('success', '开始接收 SSE 流'); + + let buffer = ''; // 缓冲区用于处理不完整的行 + + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + log('success', '对话完成'); + onComplete?.(); + break; + } + + const chunk = decoder.decode(value, { stream: true }); + buffer += chunk; + const lines = buffer.split('\n'); + + // 保留最后一行(可能不完整) + buffer = lines.pop() || ''; + + for (const line of lines) { + if (!line.trim()) continue; + + log('info', `收到原始数据: ${line}`); + + if (line.startsWith('data:')) { + const data = line.substring(5).trim(); + + // 检查是否是完成信号(支持两种格式) + if (data === '[DONE]' || (data.includes('"event":"done"') && data.includes('"done":true'))) { + log('success', '对话完成'); + onComplete?.(); + break; + } + + try { + const parsed = JSON.parse(data); + + // 再次检查解析后的对象是否标记完成 + if (parsed.event === 'done' && parsed.done === true) { + log('success', '对话完成'); + onComplete?.(); + break; + } + + log('info', '解析后的数据', parsed); + + onMessage({ + uuid: parsed.uuid || '', + sessionId: parsed.sessionId || 0, + model: parsed.model || '', + content: parsed.content || '', + role: 'assistant', + }); + } catch (e: any) { + log('error', `JSON 解析失败: ${e.message}, 原始数据: ${data}`); + } + } + } + } + } catch (error: any) { + if (error.name !== 'AbortError') { + log('error', `读取流错误: ${error.message}`); + onError?.(error); + } + } + }) + .catch((error) => { + if (error.name !== 'AbortError') { + log('error', `聊天请求错误: ${error.message}`); + onError?.(error); + } + }); + + // 返回 abort 函数 + return () => { + log('info', '用户中止请求'); + abortController.abort(); + }; +} \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/chat/test/index.vue b/hzhub-admin/apps/web-antd/src/views/chat/test/index.vue new file mode 100644 index 0000000..204f7de --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/chat/test/index.vue @@ -0,0 +1,541 @@ + + + +