feat(asset-ai): generate contextual presentations and drilldown plans
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
shishengliang
2026-09-23 17:52:33 +08:00
parent b5a48b6417
commit dc0923b566
33 changed files with 6193 additions and 1165 deletions
@@ -113,12 +113,22 @@ function setup(t: TestContext) {
delete process.env.ASSET_AI_EXTERNAL_ENABLED;
else process.env.ASSET_AI_EXTERNAL_ENABLED = originalEnabled;
});
// These scenarios assert planning behavior. A minimal valid presentation must
// not consume the queued planning response for the next user turn.
const requests: string[] = [];
const presentationRequests: string[] = [];
const responses: (Proposal | Error | string)[] = [];
t.mock.method(
globalThis,
"fetch",
async (_url: unknown, init?: RequestInit) => {
const body = JSON.parse(String(init?.body));
const context = JSON.parse(body.messages.find((message: { role: string }) => message.role === "user").content);
if (context.stage === "presentation") {
presentationRequests.push(String(init?.body));
const presentation = { layout: "stack", results: context.results.map((result: { id: string }) => ({ id: result.id, blocks: [{ id: "summary", kind: "summary" }], interactions: [] })) };
return new Response(JSON.stringify({ choices: [{ message: { content: JSON.stringify(presentation) }, finish_reason: "stop" }] }));
}
requests.push(String(init?.body));
assert.ok(
responses.length,
@@ -174,7 +184,7 @@ function setup(t: TestContext) {
assert.ok(result, "Expected completed assistant result");
return result as AssistantResult;
}
return { ask, responses, requests, recorded, rows };
return { ask, responses, requests, presentationRequests, recorded, rows };
}
const based = (result: AssistantResult) => ({
sessionId: result.sessionId,
@@ -431,7 +441,7 @@ test("a specific vehicle can be selected without broadening the user's authorize
assert.ok(!ids(result).includes("not-authorized"));
});
test("clearing a filter and resetting the scope are deterministic and do not need another model call", async (t) => {
test("clearing a filter and resetting the scope are deterministic and do not need another planning call", async (t) => {
const { ask, responses, requests } = setup(t);
responses.push({
plan: plan({