import { AbstractChatProvider, XRequest, type TransformMessage, type XRequestOptions, } from '@ant-design/x-sdk'; import { usePermissionStore } from '../../store/permission/permissionStore'; import { useUserStore } from '../../store/user/userStore'; import type { AiArtifactSchema, AiChatInput, AiChatMessage, AiReviewSchema, AiSseChunk } from './types'; import { emptyAssistant, parseSsePayload, reduceAiSseMessage } from './sseReducer'; export { parseSsePayload, reduceAiSseMessage }; export async function authenticatedFetch( input: RequestInfo | URL, init?: RequestInit, ): Promise { const headers = new Headers(init?.headers); const token = useUserStore.getState().token; if (token) headers.set('Authorization', `Bearer ${token}`); headers.set('Accept', 'text/event-stream'); let requestInput = input; let requestInit = init; if (typeof init?.body === 'string') { try { const body = JSON.parse(init.body) as AiChatInput; if (body.regenerateMessageId) { requestInput = `${String(input).replace(/\/stream$/, '')}/messages/${body.regenerateMessageId}/regenerate/stream`; requestInit = { ...init, body: JSON.stringify({ clientRequestId: body.clientRequestId, reasoningEffort: body.reasoningEffort, }), }; } else if (body.editMessageId) { requestInput = `${String(input).replace(/\/stream$/, '')}/messages/${body.editMessageId}/edit/stream`; requestInit = { ...init, body: JSON.stringify({ content: body.message, clientRequestId: body.clientRequestId, reasoningEffort: body.reasoningEffort, }), }; } else if (body.formSubmission) { requestInput = `${String(input).replace(/\/conversations\/\d+\/stream$/, '')}/forms/${body.formSubmission.formId}/submit/stream`; requestInit = { ...init, body: JSON.stringify({ clientRequestId: body.clientRequestId, values: body.formSubmission.values, reasoningEffort: body.reasoningEffort, }), }; } else if (body.reviewSubmission) { requestInput = `${String(input).replace(/\/conversations\/\d+\/stream$/, '')}/reviews/${body.reviewSubmission.reviewId}/submit/stream`; requestInit = { ...init, body: JSON.stringify({ clientRequestId: body.clientRequestId, reasoningEffort: body.reasoningEffort, }), }; } else { const { localAttachments: _localAttachments, reloadMessage: _reloadMessage, regenerateMessageId: _regenerateMessageId, editMessageId: _editMessageId, formSubmission: _formSubmission, reviewSubmission: _reviewSubmission, ...payload } = body; requestInit = { ...init, body: JSON.stringify(payload) }; } } catch { requestInit = init; } } const response = await fetch(requestInput, { ...requestInit, headers }); if (response.status === 401) { // 提示由登录页读取展示:直接弹 toast 会被跳转销毁 sessionStorage.setItem('login_expired_hint', '1'); useUserStore.getState().logout(); usePermissionStore.getState().clearPermissions(); window.location.href = '/login'; } return response; } export class GongxueAiChatProvider extends AbstractChatProvider< AiChatMessage, AiChatInput, AiSseChunk > { /** Routes events that target another (already streamed) message. */ onExternalReview?: (messageId: number, review: AiReviewSchema) => void; onExternalArtifact?: (messageId: number, artifact: AiArtifactSchema) => void; constructor(url: string, onSettled?: (result?: { ok: boolean; aborted?: boolean }) => void) { super({ request: XRequest(url, { manual: true, fetch: authenticatedFetch, timeout: 15_000, streamTimeout: 1_800_000, callbacks: { onUpdate: () => undefined, onSuccess: () => onSettled?.({ ok: true }), onError: (error) => onSettled?.({ ok: false, aborted: error?.name === 'AbortError', }), }, }), }); } transformParams( requestParams: Partial, options: XRequestOptions, ): AiChatInput { return { ...options.params, message: requestParams.message?.trim() || '', attachmentIds: requestParams.attachmentIds ?? [], skillKey: requestParams.skillKey ?? null, clientRequestId: requestParams.clientRequestId || crypto.randomUUID(), reasoningEffort: requestParams.reasoningEffort, localAttachments: requestParams.localAttachments, formSubmission: requestParams.formSubmission, reviewSubmission: requestParams.reviewSubmission, regenerateMessageId: requestParams.regenerateMessageId, editMessageId: requestParams.editMessageId, reloadMessage: requestParams.reloadMessage, }; } transformLocalMessage(requestParams: Partial): AiChatMessage | AiChatMessage[] { if (requestParams.editMessageId) { // 编辑消息不需要新增用户气泡,store 里已原位更新原消息。 return []; } if (requestParams.formSubmission) { return { role: 'user', content: '', reasoningContent: '', toolRuns: [], attachments: requestParams.localAttachments ?? [], metadata: { a2uiSubmit: { formTitle: requestParams.formSubmission.formTitle, }, }, }; } if (requestParams.reviewSubmission) { return { role: 'user', content: '', reasoningContent: '', toolRuns: [], attachments: requestParams.localAttachments ?? [], metadata: { a2uiReviewSubmit: { reviewTitle: requestParams.reviewSubmission.reviewTitle, }, }, }; } return { role: 'user', content: requestParams.message?.trim() || '', reasoningContent: '', toolRuns: [], attachments: requestParams.localAttachments ?? [], }; } transformMessage(info: TransformMessage): AiChatMessage { const { event, payload } = parseSsePayload(info.chunk); if ( event === 'ui.artifact' && payload.artifact && typeof payload.messageId === 'number' && info.originMessage?.id !== payload.messageId ) { this.onExternalArtifact?.(payload.messageId, payload.artifact); return info.originMessage ?? emptyAssistant(); } return reduceAiSseMessage(info.originMessage, info.chunk); } }