feat: add avatar chat and knowledge workflow

This commit is contained in:
stefanfeng
2026-07-23 17:21:49 +08:00
parent 501f548bcc
commit 2d9f26a6f0
15 changed files with 3635 additions and 0 deletions
+302
View File
@@ -0,0 +1,302 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
// API 基址:优先级 window.__APP_CONFIG__.apiBase > 环境变量 > 默认 '/api'
// - 开发/Vite 代理:'/api'(由 vite.config 代理到后端 :8000)
// - web-view 内(混合架构):需配置为后端公网地址,例如 'https://geo.99hui.com/api'
// - 同域部署的构建产物:可保持 '/api'
function resolveBaseURL(): string {
const cfg = (window as any).__APP_CONFIG__
if (cfg && cfg.apiBase) return cfg.apiBase as string
const env = import.meta.env.VITE_API_BASE as string | undefined
if (env) return env
return '/api'
}
// 统一认证 token(由 uniapp 壳通过 URL 注入,见 utils/uniapp-bridge.ts)
let _authToken: string | null = null
export function setAuthToken(token: string | null) {
_authToken = token
}
export function getAuthToken(): string | null {
return _authToken
}
// 创建 axios 实例(复用现有项目模式)
const createRequest = (config?: AxiosRequestConfig): AxiosInstance => {
const request = axios.create({
baseURL: resolveBaseURL(),
timeout: 30000,
...config
})
// 请求拦截器:注入认证头
request.interceptors.request.use((cfg) => {
if (_authToken) {
cfg.headers = cfg.headers || {}
;(cfg.headers as any).Authorization = `Bearer ${_authToken}`
}
return cfg
})
// 响应拦截器
request.interceptors.response.use(
(res) => {
const data = fixDatetimeTZ(res.data)
if (data && data.code && data.code !== 200) {
console.error('API Error:', data.message)
return Promise.reject(new Error(data.message))
}
// 解包标准响应 { code, message, data }
if (data && data.code === 200) {
return data.data !== undefined ? data.data : data
}
return data
},
(err) => {
const msg = err.response?.data?.detail || err.response?.data?.message || err.message || '网络错误'
console.error('Network Error:', msg)
return Promise.reject(err)
}
)
return request
}
// 递归修复时区标识(复用现有项目逻辑)
function fixDatetimeTZ(obj: any): any {
if (typeof obj === 'string') {
return obj.replace(/T(\d{2}:\d{2}:\d{2})\+00:00/g, 'T$1\+08:00')
}
if (Array.isArray(obj)) return obj.map(fixDatetimeTZ)
if (obj && typeof obj === 'object') {
const result: any = {}
for (const k in obj) result[k] = fixDatetimeTZ(obj[k])
return result
}
return obj
}
const request = createRequest()
// ==================== 分身管理 API ====================
export interface Avatar {
id: string
name: string
displayName: string
description: string
photoUrl?: string
status: 'active' | 'inactive' | 'training'
tokenBalance: number
createdAt: string
updatedAt: string
}
// 获取分身列表
export const getAvatarList = (params?: { page?: number; limit?: number }) =>
request.get<{ data: Avatar[]; total: number }>('/avatar', { params })
// 获取分身详情
export const getAvatarDetail = (id: string) =>
request.get<Avatar>(`/avatar/${id}`)
// 创建分身
export const createAvatar = (data: Partial<Avatar>) =>
request.post<Avatar>('/avatar', data)
// 更新分身
export const updateAvatar = (id: string, data: Partial<Avatar>) =>
request.put<Avatar>(`/avatar/${id}`, data)
// 删除分身
export const deleteAvatar = (id: string) =>
request.delete(`/avatar/${id}`)
// ==================== Token 管理 API ====================
// 获取 Token 余额
export const getTokenBalance = () =>
request.get<{ balance: number }>('/token/balance')
// 获取充值套餐
export const getRechargePlans = () =>
request.get<Array<{ id: string; name: string; amount: number; price: number }>>('/token/plans')
// 执行充值
export const chargeToken = (planId: string) =>
request.post<{ balance: number; charged: number }>('/token/charge', { planId })
// ==================== 授权管理 API ====================
export interface Authorization {
id: string
avatarId: string
targetType: 'user' | 'organization' | 'application'
targetId: string
targetName: string
permissions: string[]
status: 'active' | 'inactive'
createdAt: string
}
// 获取授权列表
export const getAuthorizationList = (avatarId: string) =>
request.get<Authorization[]>(`/avatar/${avatarId}/authorizations`)
// 更新授权
export const updateAuthorization = (avatarId: string, data: Partial<Authorization>) =>
request.put(`/avatar/${avatarId}/authorizations`, data)
// ==================== 组织管理 API ====================
export interface Organization {
id: string
name: string
description: string
role: 'admin' | 'member' | 'viewer'
memberCount: number
createdAt: string
}
// 获取组织列表
export const getOrganizationList = (params?: any) =>
request.get<{ data: Organization[]; total: number }>('/organizations', { params })
// 创建组织
export const createOrganization = (data: Partial<Organization>) =>
request.post<Organization>('/organizations', data)
// ==================== 知识库管理 API ====================
export interface KnowledgeDoc {
id: string
avatarId: string
filename: string
fileType: string
fileSize: number
fileUrl: string
status: string
vectorized?: boolean
embeddingModel?: string
chunkCount?: number
createdAt: string
}
export interface QAPair {
id: string
avatarId: string
question: string
answer: string
enabled?: boolean
createdAt: string
updatedAt: string
}
export interface SearchResult {
docId: string
filename: string
fileType: string
snippet: string
score: number
}
// 文档列表
export const getKnowledgeDocs = (avatarId: string) =>
request.get<KnowledgeDoc[]>(`/avatar/${avatarId}/knowledge/docs`)
// 上传文档(支持 md/txt/pdf/doc/docx/xlsx)
export const uploadKnowledgeDoc = (avatarId: string, file: File) => {
const form = new FormData()
form.append('file', file)
return request.post<KnowledgeDoc>(`/avatar/${avatarId}/knowledge/docs`, form, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 删除文档
export const deleteKnowledgeDoc = (avatarId: string, docId: string) =>
request.delete(`/avatar/${avatarId}/knowledge/docs/${docId}`)
// 标准问答对列表
export const getQAPairs = (avatarId: string) =>
request.get<QAPair[]>(`/avatar/${avatarId}/knowledge/qa`)
// 创建问答对
export const createQAPair = (avatarId: string, data: { question: string; answer: string; enabled?: boolean }) =>
request.post<QAPair>(`/avatar/${avatarId}/knowledge/qa`, data)
// 更新问答对
export const updateQAPair = (avatarId: string, id: string, data: { question: string; answer: string; enabled?: boolean }) =>
request.put<QAPair>(`/avatar/${avatarId}/knowledge/qa/${id}`, data)
// 切换问答对启用状态
export const setQaEnabled = (avatarId: string, id: string, enabled: boolean) =>
request.put<QAPair>(`/avatar/${avatarId}/knowledge/qa/${id}/enabled`, { enabled })
// 删除问答对
export const deleteQAPair = (avatarId: string, id: string) =>
request.delete(`/avatar/${avatarId}/knowledge/qa/${id}`)
// 文档向量检索
export const searchKnowledge = (avatarId: string, q: string, topK = 5) =>
request.get<SearchResult[]>(`/avatar/${avatarId}/knowledge/search`, {
params: { q, top_k: topK }
})
// ==================== 数字分身聊天 API ====================
export interface ChatMessage {
role: 'user' | 'assistant'
content: string
}
export interface ChatResponse {
answer: string
source: 'qa' | 'knowledge' | 'qwen'
references?: Array<{ docId?: string; filename?: string; fileType?: string; snippet?: string; score?: number }>
}
export const sendAvatarChat = (avatarId: string, payload: { message: string; history?: ChatMessage[] }) =>
request.post<ChatResponse>(`/avatar/${avatarId}/chat`, payload)
// ==================== 会会用户资料 API ====================
export interface UserProfile {
userId: string
nickname: string
avatarUrl: string
}
// 获取会会系统中的用户头像与昵称
export const getUserProfile = () =>
request.get<UserProfile>('/user/profile')
// ==================== 会会短信验证码登录 API ====================
export interface SmsLoginResult {
token: string
user: UserProfile & { huihuiUserId: string; phone: string; createdAt?: string; lastLoginAt?: string }
huihui: { userId: string; nickname: string; avatarUrl: string; token: string }
}
// 发送短信验证码(演示模式会额外返回 devCode / dev 标记)
export const sendSmsCode = (phone: string) =>
request.post<{ sent: boolean; devCode?: string; dev?: boolean }>('/huihui/sms/send', { phone })
// 短信验证码登录
export const loginBySms = (phone: string, code: string) =>
request.post<SmsLoginResult>('/huihui/sms/login', { phone, code })
// 账号密码登录(会会 loginType=password)
export const loginByPassword = (account: string, password: string) =>
request.post<SmsLoginResult>('/huihui/pwd/login', { account, password })
// 当前登录用户
export const getCurrentUser = () =>
request.get<UserProfile & { huihuiUserId: string; phone: string }>('/huihui/me')
// 退出登录
export const logoutUser = () =>
request.post('/huihui/logout')
export default request