Files

69 lines
6.7 KiB
Plaintext
Raw Permalink Normal View History

import { revisionParams } from './records.uts'
import { validRecordId } from './matches.uts'
import { requestJson } from '@/services/api.uts'
export type AssistantProduct = { id: string, name: string, state: string, stateLabel: string, demo: boolean }
export type AssistantContext = { revisionId: string, assessmentId: string, productIds: string[], title: string, products: AssistantProduct[], questions: string[], notice: string }
export type AssistantChat = { id: string, title: string, context: AssistantContext }
export type AssistantTurn = { id: string, requestId: string, message: string, reply: string, status: string, attempt: number, canRetry: boolean }
export type AssistantPage = { items: AssistantTurn[], more: boolean, cursor: string }
export type AssistantHistory = { id: string, title: string, createdAt: string }
export type AssistantHistoryPage = { items: AssistantHistory[], more: boolean, cursor: string }
function text(data: UTSJSONObject, key: string): string { return data.getString(key) ?? '' }
function contextView(data: UTSJSONObject): AssistantContext {
return { revisionId: text(data, 'revisionId'), assessmentId: text(data, 'assessmentId'), productIds: data['productIds'] as string[], title: text(data, 'title'),
products: (data['products'] as UTSJSONObject[]).map((p: UTSJSONObject): AssistantProduct => ({ id: text(p, 'id'), name: text(p, 'name'), state: text(p, 'state'), stateLabel: text(p, 'stateLabel'), demo: p.getBoolean('isDemo') ?? false })),
questions: data['questions'] as string[], notice: text(data, 'notice') }
}
function chatView(data: UTSJSONObject): AssistantChat {
const id = text(data, 'chatId')
if (!validRecordId(id)) throw new Error('返回的会话编号无效,请重新加载。')
return { id, title: text(data, 'title'), context: contextView(data['context'] as UTSJSONObject) }
}
// 回包仍须与本次选择一致;未指定版本时允许服务端解析成功版本,不跟随错版回包改绑定。
function checkContext(context: AssistantContext, assessmentId: string, ids: string[], revisionId: string): void {
if (context.assessmentId != assessmentId || context.productIds.join(',') != ids.join(',') || (revisionId.length > 0 && context.revisionId != revisionId)) throw new Error('返回的对话上下文与本次评估、产品或版本不一致,请重新加载。')
if ((assessmentId.length > 0 && !validRecordId(context.revisionId)) || (assessmentId.length == 0 && context.revisionId.length > 0) || context.products.map((item: AssistantProduct): string => item.id).join(',') != ids.join(',')) throw new Error('返回的对话上下文不完整,请重新加载。')
}
function turnView(data: UTSJSONObject): AssistantTurn { return { id: text(data, 'id'), requestId: text(data, 'requestId'), message: text(data, 'message'), reply: text(data, 'reply'), status: text(data, 'status'), attempt: data.getNumber('attempt') ?? 0, canRetry: data.getBoolean('canRetry') ?? false } }
function contextQuery(assessmentId: string, ids: string[], revisionId: string): UTSJSONObject { return { ...revisionParams(revisionId), contextAssessmentId: assessmentId, contextProductIds: ids.join(',') } }
export function assistantKey(): string { return 'chat_' + Date.now().toString() + '_' + Math.floor(Math.random() * 1000000000).toString() }
export function assistantRoute(assessmentId: string, ids: string[], from: string, chatId: string = '', revisionId: string = ''): string {
return '/packages/finance/assistant/assistant?from=' + from + (assessmentId.length > 0 ? '&assessmentId=' + assessmentId + '&productIds=' + ids.join(',') : '') + (chatId.length > 0 ? '&chatId=' + chatId : '') + (revisionId.length > 0 ? '&revisionId=' + revisionId : '')
}
export async function previewAssistant(assessmentId: string, ids: string[], revisionId: string = ''): Promise<AssistantContext> {
const result = contextView(await requestJson('/assistant/context', 'GET', contextQuery(assessmentId, ids, revisionId)))
checkContext(result, assessmentId, ids, revisionId)
return result
}
export async function loadAssistant(chatId: string, assessmentId: string, ids: string[], checkBinding: boolean, revisionId: string = ''): Promise<AssistantChat> {
const result = chatView(await requestJson('/assistant/chats/' + chatId, 'GET', checkBinding ? contextQuery(assessmentId, ids, revisionId) : new UTSJSONObject()))
if (result.id != chatId) throw new Error('返回的会话不一致,请重新加载。')
if (checkBinding) checkContext(result.context, assessmentId, ids, revisionId)
else {
checkContext(result.context, result.context.assessmentId, result.context.productIds, revisionId)
}
return result
}
export async function createAssistant(assessmentId: string, ids: string[], requestId: string, revisionId: string = ''): Promise<AssistantChat> {
const result = chatView(await requestJson('/assistant/chats', 'POST', { ...revisionParams(revisionId), contextAssessmentId: assessmentId.length > 0 ? assessmentId : null, contextProductIds: ids, requestId }))
checkContext(result.context, assessmentId, ids, revisionId)
return result
}
export async function assistantMessages(chatId: string, cursor: string): Promise<AssistantPage> {
const data = await requestJson('/assistant/chats/' + chatId + '/messages', 'GET', { limit: '20', cursor })
if (text(data, 'chatId') != chatId) throw new Error('消息不属于本次会话。')
return { items: (data['items'] as UTSJSONObject[]).map((v: UTSJSONObject): AssistantTurn => turnView(v)), more: data.getBoolean('hasMore') ?? false, cursor: text(data, 'nextCursor') }
}
export async function submitAssistant(chatId: string, requestId: string, message: string, attempt: number): Promise<AssistantTurn> {
const data = await requestJson('/assistant/chats/' + chatId + '/messages', 'POST', { requestId, message, retryAttempt: attempt })
if (text(data, 'chatId') != chatId) throw new Error('回复不属于本次会话。')
const item = turnView(data['item'] as UTSJSONObject)
// 同键重放可能返回已推进的尝试;旧尝试或另一条问题不能确认本次发送成功。
if (item.requestId != requestId || item.message != message || item.attempt < attempt) throw new Error('发送回执与本次问题不一致,请重试本次发送。')
return item
}
export async function assistantHistory(cursor: string): Promise<AssistantHistoryPage> {
const data = await requestJson('/assistant/chats', 'GET', { limit: '20', cursor })
return { items: (data['items'] as UTSJSONObject[]).map((v: UTSJSONObject): AssistantHistory => ({ id: text(v, 'chatId'), title: text(v, 'title'), createdAt: text(v, 'createdAt').slice(0, 10) })), more: data.getBoolean('hasMore') ?? false, cursor: text(data, 'nextCursor') }
}