init: 数字员工平台初始代码

包含前端(Vue3 + VueFlow 画布)、后端(Go)、文档体系。
- 工作台画布:节点拖放、连线模式、右键菜单、AI 助手
- 后端:连接器 API、专员种子数据
- 导航:左侧导航、工坊、市场、控制台
This commit is contained in:
eaiadmin
2026-08-18 20:19:58 +08:00
commit 4e8817d768
239 changed files with 48631 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
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, onChunk) {
const token = localStorage.getItem('token')
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 请求失败'
try {
const j = await resp.json()
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 === 'error') {
throw new Error(obj.message || 'LLM 请求失败')
} else if (obj.type === 'done') {
return
}
}
}
}