feat: complete grounded digital avatar chat experience

This commit is contained in:
stefanfeng
2026-08-19 14:21:56 +08:00
parent 07d4a21379
commit 4a2d788e85
16 changed files with 1013 additions and 193 deletions
+66
View File
@@ -90,6 +90,7 @@ export interface Avatar {
tokenBalance: number
createdAt: string
updatedAt: string
config?: Record<string, any>
}
// 获取分身列表
@@ -108,6 +109,14 @@ export const createAvatar = (data: Partial<Avatar>) =>
export const updateAvatar = (id: string, data: Partial<Avatar>) =>
request.put<Avatar>(`/avatar/${id}`, data)
export const uploadAvatarPhoto = (id: string, file: File) => {
const form = new FormData()
form.append('file', file)
return request.post<{ photoUrl: string }>(`/avatar/${id}/photo`, form, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 删除分身
export const deleteAvatar = (id: string) =>
request.delete(`/avatar/${id}`)
@@ -259,6 +268,63 @@ export interface ChatResponse {
export const sendAvatarChat = (avatarId: string, payload: { message: string; history?: ChatMessage[] }) =>
request.post<ChatResponse>(`/avatar/${avatarId}/chat`, payload)
export interface PublicAvatar {
id: string
name: string
displayName: string
description?: string
photoUrl?: string
emoji?: string
status: 'active' | 'inactive' | 'training'
}
export const createAvatarShareLink = (avatarId: string) =>
request.post<{ shareToken: string }>(`/avatar/${avatarId}/share`)
export const getPublicAvatar = (shareToken: string) =>
request.get<PublicAvatar>(`/public/avatar/${shareToken}`)
export const sendPublicAvatarChat = (shareToken: string, payload: { message: string; history?: ChatMessage[] }) =>
request.post<ChatResponse>(`/public/avatar/${shareToken}/chat`, payload)
type ChatStreamHandlers = {
onMeta: (meta: Pick<ChatResponse, 'source' | 'references'>) => void
onDelta: (content: string) => void
}
const streamChat = async (path: string, payload: { message: string; history?: ChatMessage[] }, handlers: ChatStreamHandlers) => {
const headers: Record<string, string> = { 'Content-Type': 'application/json', Accept: 'text/event-stream' }
if (_authToken) headers.Authorization = `Bearer ${_authToken}`
const response = await fetch(`${resolveBaseURL()}${path}`, { method: 'POST', headers, body: JSON.stringify(payload) })
if (!response.ok || !response.body) throw new Error(`对话请求失败(${response.status})`)
const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
buffer += decoder.decode(value || new Uint8Array(), { stream: !done })
const events = buffer.split('\n\n')
buffer = events.pop() || ''
for (const eventBlock of events) {
const event = eventBlock.match(/^event:\s*(.+)$/m)?.[1] || 'message'
const data = eventBlock.match(/^data:\s*(.+)$/m)?.[1]
if (!data) continue
const parsed = JSON.parse(data)
if (event === 'meta') handlers.onMeta(parsed)
if (event === 'delta') handlers.onDelta(parsed.content || '')
if (event === 'error') throw new Error(parsed.message || '对话暂时不可用')
}
if (done) break
}
}
export const streamAvatarChat = (avatarId: string, payload: { message: string; history?: ChatMessage[] }, handlers: ChatStreamHandlers) =>
streamChat(`/avatar/${avatarId}/chat/stream`, payload, handlers)
export const streamPublicAvatarChat = (shareToken: string, payload: { message: string; history?: ChatMessage[] }, handlers: ChatStreamHandlers) =>
streamChat(`/public/avatar/${shareToken}/chat/stream`, payload, handlers)
// ==================== 会会用户资料 API ====================
export interface UserProfile {