- 将 wechat_official_account 重命名为 weixin_public_account,符合中文命名规范 - 新增 DOCX 文档生成技能、聊天历史、请求 ID 中间件 - 增强工作流、热点服务、文章服务等模块功能 - 前端同步重命名组件和 API - 新增架构文档 AR13/AR14、专员文档更新 - 补充测试用例(seed_specialists_test, db_migration_test) Co-Authored-AI: yes
126 lines
3.8 KiB
JavaScript
126 lines
3.8 KiB
JavaScript
import http from './http'
|
|
|
|
// 会话持久化:列出当前用户的 AI 对话会话
|
|
export function listChatConversations() {
|
|
return http.get('/ai-chat/conversations')
|
|
}
|
|
|
|
// 会话持久化:获取某个会话的历史消息
|
|
export function getChatConversationMessages(conversationId) {
|
|
return http.get(`/ai-chat/conversations/${conversationId}/messages`)
|
|
}
|
|
|
|
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 })
|
|
}
|
|
|
|
// 用量聚合(管理员 = 全量;员工 = 本人,后端按角色过滤)
|
|
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')
|
|
}
|
|
|
|
// 管理员:AI 通路测试 —— 获取某路由的候选模型清单(配置 model ∪ 自动发现)
|
|
export function getRouteTestModels(routeId) {
|
|
return http.post('/ai/route-test/models', { route_id: routeId })
|
|
}
|
|
|
|
// 管理员:AI 通路测试 —— 对单个模型或候选模型逐个发起真实测试
|
|
// models 为空 → 只测配置 model;填多个 → 逐个试跑
|
|
export function runRouteTest(params) {
|
|
return http.post('/ai/route-test/run', params || {})
|
|
}
|
|
|
|
// SSE 流式对话:后端 text/event-stream,每行 "data: {json}\n\n"
|
|
// conversationId 可选:传入后请求体会携带 conversation_id,用于会话持久化
|
|
export async function streamChat(message, context, history, handlers, conversationId = '') {
|
|
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 aiRouteId = typeof handlers === 'function' ? '' : (handlers?.aiRouteId || '')
|
|
const payload = { message, context, history, ai_route_id: aiRouteId }
|
|
if (conversationId) {
|
|
payload.conversation_id = conversationId
|
|
}
|
|
const resp = await fetch('/api/ai-chat/message', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
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 */
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|