feat(asset-ai): generate contextual presentations and drilldown plans
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { GenerativeResultView } from "./GenerativeResultView";
|
||||
import { CompoundResultView } from "./CompoundResultView";
|
||||
import {
|
||||
presentationAction,
|
||||
presentationData,
|
||||
presentationFields,
|
||||
presentationMetrics,
|
||||
} from "./generative-presentation";
|
||||
import type {
|
||||
AssistantResult,
|
||||
PresentationInteraction,
|
||||
} from "../../../shared/assets/assistant";
|
||||
|
||||
const result: AssistantResult = {
|
||||
resultId: "generated-result",
|
||||
sessionId: "demo",
|
||||
plan: {
|
||||
kind: "assets",
|
||||
category: "inventory",
|
||||
groupBy: "region",
|
||||
view: "chart",
|
||||
filters: {},
|
||||
},
|
||||
title: "库存分布",
|
||||
summary: "华东库存12台,华南8台。",
|
||||
source: "演示台账",
|
||||
fetchedAt: "2026-09-23T08:00:00Z",
|
||||
rows: [],
|
||||
metrics: [
|
||||
{ label: "总库存", value: 20, unit: "台" },
|
||||
{ label: "异动", value: 2, unit: "台" },
|
||||
],
|
||||
groups: [
|
||||
{
|
||||
name: "华东",
|
||||
count: 12,
|
||||
operating: 0,
|
||||
rate: 0,
|
||||
keys: { region: "华东" },
|
||||
},
|
||||
{ name: "华南", count: 8, operating: 0, rate: 0, keys: { region: "华南" } },
|
||||
],
|
||||
daily: [
|
||||
{ date: "2026-09-23", delivered: 1, returned: 2, replaced: 0, total: 3 },
|
||||
],
|
||||
presentation: {
|
||||
title: "库存情况",
|
||||
blocks: [
|
||||
{ id: "conclusion", kind: "summary" },
|
||||
{ id: "selected", kind: "metrics", metrics: ["异动", "总库存"] },
|
||||
{ id: "narrative", kind: "narrative", text: "<script>不能执行</script>" },
|
||||
{
|
||||
id: "regions",
|
||||
kind: "table",
|
||||
dataset: "groups",
|
||||
columns: [
|
||||
{ field: "count", label: "库存数量" },
|
||||
{ field: "name", label: "所属区域" },
|
||||
],
|
||||
interactionId: "cities",
|
||||
},
|
||||
],
|
||||
interactions: [
|
||||
{
|
||||
id: "cities",
|
||||
label: "查看城市分布",
|
||||
kind: "group",
|
||||
plan: {
|
||||
kind: "assets",
|
||||
category: "inventory",
|
||||
groupBy: "city",
|
||||
view: "chart",
|
||||
filters: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const callbacks = {
|
||||
ask: () => {},
|
||||
operate: () => {},
|
||||
restore: () => {},
|
||||
activate: () => {},
|
||||
showCorrection: () => {},
|
||||
active: true,
|
||||
subject: "演示公司",
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
test("model-selected metrics and columns preserve their order and never invent values", () => {
|
||||
assert.deepEqual(
|
||||
presentationMetrics(result, ["异动", "不存在", "总库存"]).map(
|
||||
(item) => item.value,
|
||||
),
|
||||
[2, 20],
|
||||
);
|
||||
assert.deepEqual(
|
||||
presentationFields({
|
||||
id: "table",
|
||||
kind: "table",
|
||||
dataset: "groups",
|
||||
columns: [
|
||||
{ field: "count", label: "数量" },
|
||||
{ field: "secret", label: "无此字段" },
|
||||
],
|
||||
}),
|
||||
[{ field: "count", label: "数量" }],
|
||||
);
|
||||
assert.deepEqual(presentationData(result, "groups")[0].keys, {
|
||||
region: "华东",
|
||||
});
|
||||
assert.deepEqual(presentationData(result, "comparison"), []);
|
||||
});
|
||||
test("exploration uses stored interaction ID and actual clicked keys or date", () => {
|
||||
const group = result.presentation!.interactions[0];
|
||||
assert.deepEqual(
|
||||
presentationAction(group, presentationData(result, "groups")[1]),
|
||||
{ type: "explore", interactionId: "cities", keys: { region: "华南" } },
|
||||
);
|
||||
assert.equal(presentationAction(group, { name: "华东" }), undefined);
|
||||
const point: PresentationInteraction = { ...group, id: "day", kind: "point" };
|
||||
assert.deepEqual(
|
||||
presentationAction(point, presentationData(result, "daily")[0]),
|
||||
{ type: "explore", interactionId: "day", date: "2026-09-23" },
|
||||
);
|
||||
assert.equal(presentationAction(point, { name: "昨天" }), undefined);
|
||||
assert.deepEqual(presentationAction({ ...group, kind: "followup" }), {
|
||||
type: "explore",
|
||||
interactionId: "cities",
|
||||
});
|
||||
});
|
||||
test("semantic render respects block order, real table fields, plain text, scope and answer anchors", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
createElement(GenerativeResultView, { ...callbacks, result }),
|
||||
);
|
||||
assert.ok(
|
||||
html.indexOf('data-block-id="selected"') <
|
||||
html.indexOf('data-block-id="regions"'),
|
||||
);
|
||||
assert.ok(html.indexOf(">异动</span>") < html.indexOf(">总库存</span>"));
|
||||
assert.ok(html.indexOf(">库存数量</th>") < html.indexOf(">所属区域</th>"));
|
||||
assert.match(html, /<script>不能执行<\/script>/);
|
||||
assert.doesNotMatch(html, /<script>/);
|
||||
for (const expected of [
|
||||
"aq-query-generated-result",
|
||||
"aq-answer-generated-result",
|
||||
"查看城市分布",
|
||||
"源数据更新时间:",
|
||||
"未提供",
|
||||
"演示公司",
|
||||
"华东",
|
||||
"复制完整结果",
|
||||
])
|
||||
assert.ok(html.includes(expected), expected);
|
||||
});
|
||||
test("stack layout renders each real section while tabs expand only the active section", () => {
|
||||
const compound: AssistantResult = {
|
||||
...result,
|
||||
resultId: "parent",
|
||||
layout: "stack",
|
||||
presentation: undefined,
|
||||
sections: [
|
||||
{
|
||||
id: "first",
|
||||
title: "库存分析",
|
||||
question: "库存",
|
||||
display: "chart",
|
||||
result,
|
||||
},
|
||||
{
|
||||
id: "second",
|
||||
title: "异动分析",
|
||||
question: "异动",
|
||||
display: "table",
|
||||
result: { ...result, resultId: "second-child", summary: "异动2台。" },
|
||||
},
|
||||
{
|
||||
id: "failed",
|
||||
title: "成本分析",
|
||||
question: "成本",
|
||||
display: "metrics",
|
||||
error: "缺少成本数据",
|
||||
},
|
||||
],
|
||||
};
|
||||
const stacked = renderToStaticMarkup(
|
||||
createElement(CompoundResultView, {
|
||||
...callbacks,
|
||||
result: compound,
|
||||
activeResultId: "second-child",
|
||||
}),
|
||||
);
|
||||
assert.ok(stacked.includes('id="aq-query-generated-result"'));
|
||||
assert.ok(stacked.includes('id="aq-query-second-child"'));
|
||||
assert.ok(stacked.includes('data-section-id="first"'));
|
||||
assert.match(stacked, /缺少成本数据/);
|
||||
assert.doesNotMatch(stacked, /role="tablist"/);
|
||||
const tabbed = renderToStaticMarkup(
|
||||
createElement(CompoundResultView, {
|
||||
...callbacks,
|
||||
result: { ...compound, layout: "tabs" },
|
||||
activeResultId: "second-child",
|
||||
}),
|
||||
);
|
||||
assert.ok(!tabbed.includes('id="aq-query-generated-result"'));
|
||||
assert.ok(tabbed.includes('id="aq-query-second-child"'));
|
||||
assert.match(tabbed, /role="tablist"/);
|
||||
});
|
||||
|
||||
test("AI summary text is honored and metrics-only layouts do not receive an unsolicited visible summary", () => {
|
||||
const summary = renderToStaticMarkup(
|
||||
createElement(GenerativeResultView, {
|
||||
...callbacks,
|
||||
result: {
|
||||
...result,
|
||||
presentation: {
|
||||
blocks: [{ id: "s", kind: "summary", text: "异动2台,应先处理。" }],
|
||||
interactions: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
assert.match(summary, /异动2台,应先处理。/);
|
||||
const metricsOnly = renderToStaticMarkup(
|
||||
createElement(GenerativeResultView, {
|
||||
...callbacks,
|
||||
result: {
|
||||
...result,
|
||||
presentation: {
|
||||
blocks: [{ id: "m", kind: "metrics", metrics: ["总库存"] }],
|
||||
interactions: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
assert.ok(
|
||||
metricsOnly.indexOf(result.summary) >
|
||||
metricsOnly.indexOf('<details class="aq-result-definitions">'),
|
||||
);
|
||||
assert.ok(!metricsOnly.includes('class="aq-answer"'));
|
||||
assert.ok(metricsOnly.includes('id="aq-query-generated-result"'));
|
||||
});
|
||||
|
||||
test("generated exploration does not add unrelated fixed followup suggestions", () => {
|
||||
const markup=renderToStaticMarkup(createElement(GenerativeResultView,{
|
||||
...callbacks,result:{...result,suggestions:["不应插入的固定城市路径"],presentation:{blocks:[{id:"m",kind:"metrics",metrics:["总库存"]}],interactions:[]}},
|
||||
}));
|
||||
assert.doesNotMatch(markup,/不应插入的固定城市路径/);
|
||||
});
|
||||
Reference in New Issue
Block a user