feat(ai): 业务上下文感知与 A2UI 链路统一
- 新增代码内业务上下文元数据层(实体字典 + 三大闭环工作流) - 新增 get_business_context / get_entity_schema / get_pending_tasks 运行时工具 - SYSTEM_PROMPT 与技能目录改为先查业务流程/待办再执行 - A2UI 增加 ai_a2ui_submissions 幂等表、表单过期、ui.artifact 事件 - 提交回灌携带 submissionId / fieldErrors / 下一步建议 - 前端 uiArtifacts 归一化与过期表单禁用
This commit is contained in:
@@ -8,6 +8,7 @@ import { usePermissionStore } from '../../store/permission/permissionStore';
|
||||
import { useUserStore } from '../../store/user/userStore';
|
||||
import type {
|
||||
AiAttachment,
|
||||
AiArtifactSchema,
|
||||
AiChatInput,
|
||||
AiChatMessage,
|
||||
AiChartSchema,
|
||||
@@ -34,6 +35,7 @@ interface AiSsePayload {
|
||||
durationMs?: number | null;
|
||||
attachment?: AiAttachment;
|
||||
form?: AiFormSchema;
|
||||
artifact?: AiArtifactSchema;
|
||||
review?: AiReviewSchema;
|
||||
chart?: AiChartSchema;
|
||||
preflight?: AiImportPreflight;
|
||||
@@ -62,6 +64,7 @@ function emptyAssistant(): AiChatMessage {
|
||||
toolRuns: [],
|
||||
attachments: [],
|
||||
forms: [],
|
||||
uiArtifacts: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -165,10 +168,50 @@ function applyMessagePayload(
|
||||
message.charts,
|
||||
(nested.metadata?.a2uiChart as AiChartSchema | AiChartSchema[] | undefined) ?? payload.chart,
|
||||
);
|
||||
const artifacts = nested.metadata?.uiArtifacts;
|
||||
if (Array.isArray(artifacts)) {
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact && typeof artifact === 'object' && typeof artifact.id === 'string') {
|
||||
mergeArtifactIntoMessage(message, artifact as AiArtifactSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
message.replyToMessageId = nested.replyToMessageId ?? message.replyToMessageId;
|
||||
message.metadata = nested.metadata ?? message.metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将统一 artifact 归入 uiArtifacts,并按类型派发到 legacy 列表。
|
||||
*/
|
||||
export function mergeArtifactIntoMessage(
|
||||
message: AiChatMessage,
|
||||
artifact: AiArtifactSchema,
|
||||
): AiChatMessage {
|
||||
message.uiArtifacts = mergeById<AiArtifactSchema>(message.uiArtifacts, artifact);
|
||||
const payload =
|
||||
artifact.payload && typeof artifact.payload === 'object'
|
||||
? (artifact.payload as Record<string, unknown>)
|
||||
: {};
|
||||
if (artifact.type === 'form') {
|
||||
message.forms = mergeForms(message.forms, payload as unknown as AiFormSchema);
|
||||
} else if (artifact.type === 'review') {
|
||||
message.reviews = mergeById<AiReviewSchema>(
|
||||
message.reviews,
|
||||
payload as unknown as AiReviewSchema,
|
||||
);
|
||||
} else if (artifact.type === 'chart') {
|
||||
message.charts = mergeById<AiChartSchema>(
|
||||
message.charts,
|
||||
payload as unknown as AiChartSchema,
|
||||
);
|
||||
} else if (artifact.type === 'import_preflight') {
|
||||
message.metadata = { ...message.metadata, a2uiImportPreflight: payload };
|
||||
} else if (artifact.type === 'import_wizard') {
|
||||
message.metadata = { ...message.metadata, a2uiImportWizard: payload };
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
export function reduceAiSseMessage(
|
||||
originMessage: AiChatMessage | undefined,
|
||||
chunk?: AiSseChunk,
|
||||
@@ -198,6 +241,8 @@ export function reduceAiSseMessage(
|
||||
message.reviews = mergeById<AiReviewSchema>(message.reviews, payload.review);
|
||||
} else if (event === 'ui.chart' && payload.chart) {
|
||||
message.charts = mergeById<AiChartSchema>(message.charts, payload.chart);
|
||||
} else if (event === 'ui.artifact' && payload.artifact) {
|
||||
mergeArtifactIntoMessage(message, payload.artifact);
|
||||
} else if (event === 'ui.import_preflight' && payload.preflight) {
|
||||
message.metadata = { ...message.metadata, a2uiImportPreflight: payload.preflight };
|
||||
} else if (event === 'ui.import_wizard' && payload.wizard) {
|
||||
@@ -319,6 +364,7 @@ export class GongxueAiChatProvider extends AbstractChatProvider<
|
||||
> {
|
||||
/** 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({
|
||||
@@ -404,6 +450,30 @@ export class GongxueAiChatProvider extends AbstractChatProvider<
|
||||
|
||||
transformMessage(info: TransformMessage<AiChatMessage, AiSseChunk>): 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();
|
||||
}
|
||||
if (
|
||||
event === 'ui.form' &&
|
||||
payload.form &&
|
||||
typeof payload.messageId === 'number' &&
|
||||
info.originMessage?.id !== payload.messageId
|
||||
) {
|
||||
this.onExternalArtifact?.(payload.messageId, {
|
||||
id: payload.form.id,
|
||||
type: 'form',
|
||||
status: payload.form.status ?? 'pending',
|
||||
messageId: payload.messageId,
|
||||
payload: payload.form,
|
||||
});
|
||||
return info.originMessage ?? emptyAssistant();
|
||||
}
|
||||
if (
|
||||
event === 'ui.review' &&
|
||||
payload.review &&
|
||||
|
||||
Reference in New Issue
Block a user