- 新增代码内业务上下文元数据层(实体字典 + 三大闭环工作流) - 新增 get_business_context / get_entity_schema / get_pending_tasks 运行时工具 - SYSTEM_PROMPT 与技能目录改为先查业务流程/待办再执行 - A2UI 增加 ai_a2ui_submissions 幂等表、表单过期、ui.artifact 事件 - 提交回灌携带 submissionId / fieldErrors / 下一步建议 - 前端 uiArtifacts 归一化与过期表单禁用
128 lines
4.7 KiB
TypeScript
128 lines
4.7 KiB
TypeScript
import { AiReview } from './entities/ai-review.entity';
|
||
import type { AiChatServiceContext } from './ai-chat.types';
|
||
|
||
export function a2uiSubmitInfo(
|
||
metadata: Record<string, unknown> | null,
|
||
): {
|
||
formId: string;
|
||
title: string;
|
||
values: Record<string, unknown>;
|
||
submissionId?: string;
|
||
fieldErrors?: Array<{ field: string; message: string }>;
|
||
} | null {
|
||
const submit = metadata?.a2uiSubmit;
|
||
if (!submit || typeof submit !== 'object' || Array.isArray(submit)) return null;
|
||
const record = submit as Record<string, unknown>;
|
||
const formId = typeof record.formId === 'string' ? record.formId : '';
|
||
const title = typeof record.formTitle === 'string' ? record.formTitle : '表单';
|
||
const values =
|
||
record.values && typeof record.values === 'object' && !Array.isArray(record.values)
|
||
? (record.values as Record<string, unknown>)
|
||
: {};
|
||
const submissionId =
|
||
typeof record.submissionId === 'string' ? record.submissionId : undefined;
|
||
const fieldErrors = Array.isArray(record.fieldErrors)
|
||
? (record.fieldErrors as Array<{ field: string; message: string }>)
|
||
: [];
|
||
return { formId, title, values, submissionId, fieldErrors };
|
||
}
|
||
|
||
export function buildFormSubmitModelContent(submit: {
|
||
title: string;
|
||
values: Record<string, unknown>;
|
||
submissionId?: string;
|
||
fieldErrors?: Array<{ field: string; message: string }>;
|
||
}): string {
|
||
let json: string;
|
||
try {
|
||
json = JSON.stringify(submit.values);
|
||
} catch {
|
||
json = '[无法序列化]';
|
||
}
|
||
const submissionId = submit.submissionId ? `\n提交ID:${submit.submissionId}` : '';
|
||
const fieldErrors = submit.fieldErrors?.length
|
||
? `\nfieldErrors: ${JSON.stringify(submit.fieldErrors)}`
|
||
: '\nfieldErrors: []';
|
||
return `【表单提交:${submit.title}】${submissionId}\n提交值(JSON):${json.slice(0, 32 * 1024)}${fieldErrors}\n用户已在表单中确认,你可以执行允许的写操作工具。`;
|
||
}
|
||
|
||
export async function markFormSubmittedOnMessage(
|
||
context: AiChatServiceContext,
|
||
assistantMessageId: number,
|
||
conversationId: number,
|
||
): Promise<void> {
|
||
const assistant = await context.messages.findOne({
|
||
where: { id: assistantMessageId, conversationId },
|
||
});
|
||
const a2ui = assistant?.metadata?.a2uiForm;
|
||
if (assistant && a2ui && typeof a2ui === 'object' && !Array.isArray(a2ui)) {
|
||
assistant.metadata = {
|
||
...assistant.metadata,
|
||
a2uiForm: { ...(a2ui as Record<string, unknown>), status: 'submitted' },
|
||
};
|
||
await context.messages.save(assistant);
|
||
}
|
||
}
|
||
|
||
export function a2uiReviewSubmitInfo(
|
||
metadata: Record<string, unknown> | null,
|
||
): {
|
||
reviewId: string;
|
||
reviewTitle: string;
|
||
resultMessage: string;
|
||
submissionId?: string;
|
||
nextSteps?: Array<{ key: string; label: string }>;
|
||
} | null {
|
||
const submit = metadata?.a2uiReviewSubmit;
|
||
if (!submit || typeof submit !== 'object' || Array.isArray(submit)) return null;
|
||
const record = submit as Record<string, unknown>;
|
||
if (typeof record.reviewId !== 'string') return null;
|
||
const submissionId =
|
||
typeof record.submissionId === 'string' ? record.submissionId : undefined;
|
||
const nextSteps = Array.isArray(record.nextSteps)
|
||
? (record.nextSteps as Array<{ key: string; label: string }>)
|
||
: [];
|
||
return {
|
||
reviewId: record.reviewId,
|
||
reviewTitle: typeof record.reviewTitle === 'string' ? record.reviewTitle : '批量导入',
|
||
resultMessage: typeof record.resultMessage === 'string' ? record.resultMessage : '导入已完成',
|
||
submissionId,
|
||
nextSteps,
|
||
};
|
||
}
|
||
|
||
export function buildReviewSubmitModelContent(submit: {
|
||
reviewId: string;
|
||
reviewTitle: string;
|
||
resultMessage: string;
|
||
submissionId?: string;
|
||
nextSteps?: Array<{ key: string; label: string }>;
|
||
}): string {
|
||
const submissionId = submit.submissionId ? `\n提交ID:${submit.submissionId}` : '';
|
||
const nextSteps = submit.nextSteps?.length
|
||
? `\n下一步建议:${submit.nextSteps.map((step) => step.label).join(';')}`
|
||
: '';
|
||
return `【批量导入已确认:${submit.reviewTitle}】${submissionId}\n${submit.resultMessage}${nextSteps}\n数据已由系统入库,不要再次调用写入工具,直接向用户汇报导入结果即可。`;
|
||
}
|
||
|
||
export async function markReviewSubmittedOnMessage(
|
||
context: AiChatServiceContext,
|
||
assistantMessageId: number,
|
||
conversationId: number,
|
||
review?: AiReview,
|
||
): Promise<void> {
|
||
const assistant = await context.messages.findOne({
|
||
where: { id: assistantMessageId, conversationId },
|
||
});
|
||
const a2ui = assistant?.metadata?.a2uiReview;
|
||
if (assistant && a2ui && typeof a2ui === 'object' && !Array.isArray(a2ui)) {
|
||
assistant.metadata = {
|
||
...assistant.metadata,
|
||
a2uiReview: review
|
||
? context.reviewService.serialize(review)
|
||
: { ...(a2ui as Record<string, unknown>), status: 'submitted' },
|
||
};
|
||
await context.messages.save(assistant);
|
||
}
|
||
}
|