import http from './http' export function quickActions() { return http.get('/ai-chat/quick-actions') } export function quickAction(actionId, params) { return http.post('/ai-chat/quick-action', { action_id: actionId, params }) } // 本人 AI 点数 + 用量(PathCoach 面板数据源) export function getAiMe() { return http.get('/ai/me') } // 用量聚合(管理员 = 全量;员工 = 本人,后端按角色过滤) export function getAiUsage(params) { return http.get('/ai/usage', { params }) } // 管理员:按用户聚合用量 + 剩余点数 export function getAiUsageUsers() { return http.get('/ai/usage/users') } // 管理员:AI 配置(ai_config.json)读 / 写 / 热重载 / 密钥状态 export function getAiConfig() { return http.get('/ai/config') } export function putAiConfig(config) { return http.put('/ai/config', config) } export function reloadAiConfig() { return http.post('/ai/reload') } export function getSecretsStatus() { return http.get('/ai/secrets-status') } // SSE 流式对话:后端 text/event-stream,每行 "data: {json}\n\n" export async function streamChat(message, context, history, handlers) { const token = localStorage.getItem('token') const onChunk = typeof handlers === 'function' ? handlers : handlers?.onChunk const onMeta = typeof handlers === 'function' ? null : handlers?.onMeta const onDone = typeof handlers === 'function' ? null : handlers?.onDone const traceId = `chat-${Date.now()}` // #region debug-point A:frontend-request fetch('http://127.0.0.1:7777/event', { method: 'POST', body: JSON.stringify({ sessionId: 'knowledge-chat-401', runId: 'pre-fix', hypothesisId: 'A', location: 'frontend/src/api/ai.js:streamChat:request', traceId, msg: '[DEBUG] streamChat request', data: { hasToken: Boolean(token), url: '/api/ai-chat/message', messagePreview: String(message || '').slice(0, 80), spaceKey: context?.knowledge_space_key || '', }, ts: Date.now(), }), }).catch(() => {}) // #endregion const resp = await fetch('/api/ai-chat/message', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ message, context, history }), }) if (!resp.ok) { let msg = 'LLM 请求失败' let raw = '' try { raw = await resp.text() const j = JSON.parse(raw) msg = j.message || j.error || msg } catch { /* ignore */ } // #region debug-point A:frontend-non200 fetch('http://127.0.0.1:7777/event', { method: 'POST', body: JSON.stringify({ sessionId: 'knowledge-chat-401', runId: 'pre-fix', hypothesisId: 'A', location: 'frontend/src/api/ai.js:streamChat:non200', traceId, msg: '[DEBUG] streamChat non-200 response', data: { status: resp.status, statusText: resp.statusText, bodyPreview: String(raw || '').slice(0, 240), }, ts: Date.now(), }), }).catch(() => {}) // #endregion throw new Error(msg) } const reader = resp.body.getReader() const decoder = new TextDecoder() let buffer = '' // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) let idx while ((idx = buffer.indexOf('\n\n')) !== -1) { const raw = buffer.slice(0, idx).trim() buffer = buffer.slice(idx + 2) if (!raw.startsWith('data:')) continue const payload = raw.slice(5).trim() if (!payload) continue let obj try { obj = JSON.parse(payload) } catch { continue } if (obj.type === 'text') { onChunk?.(obj.content) } else if (obj.type === 'meta') { onMeta?.(obj) } else if (obj.type === 'error') { throw new Error(obj.message || 'LLM 请求失败') } else if (obj.type === 'done') { onDone?.() return } } } }