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,129 @@
|
||||
import type {
|
||||
AssistantAction,
|
||||
AssistantResult,
|
||||
Dimension,
|
||||
PresentationBlock,
|
||||
PresentationInteraction,
|
||||
} from "../../../shared/assets/assistant";
|
||||
|
||||
export type PresentationDatum = Record<string, unknown>;
|
||||
const fields: Record<NonNullable<PresentationBlock["dataset"]>, string[]> = {
|
||||
groups: [
|
||||
"name",
|
||||
"count",
|
||||
"operating",
|
||||
"rate",
|
||||
"abnormal",
|
||||
"pending",
|
||||
"share",
|
||||
],
|
||||
daily: ["date", "delivered", "returned", "replaced", "total"],
|
||||
comparison: ["label", "current", "previous", "delta", "rate"],
|
||||
rows: [
|
||||
"id",
|
||||
"plate",
|
||||
"vehicleClass",
|
||||
"type",
|
||||
"model",
|
||||
"region",
|
||||
"city",
|
||||
"department",
|
||||
"status",
|
||||
"inventory",
|
||||
"abnormal",
|
||||
"pending",
|
||||
"abnormalType",
|
||||
"targetCompletionTime",
|
||||
"actualCompletionTime",
|
||||
"date",
|
||||
"event",
|
||||
],
|
||||
};
|
||||
export function presentationData(
|
||||
result: AssistantResult,
|
||||
dataset: PresentationBlock["dataset"],
|
||||
): PresentationDatum[] {
|
||||
return dataset ? (result[dataset] || []).map((row) => ({ ...row })) : [];
|
||||
}
|
||||
export function presentationFields(block: PresentationBlock) {
|
||||
const allowed = block.dataset ? fields[block.dataset] : [];
|
||||
const defaults =
|
||||
block.dataset === "daily"
|
||||
? [
|
||||
{ field: "date", label: "日期" },
|
||||
{ field: "total", label: "车次" },
|
||||
]
|
||||
: block.dataset === "comparison"
|
||||
? [
|
||||
{ field: "label", label: "指标" },
|
||||
{ field: "current", label: "本期" },
|
||||
{ field: "previous", label: "上期" },
|
||||
]
|
||||
: block.dataset === "rows"
|
||||
? [
|
||||
{ field: "plate", label: "车牌" },
|
||||
{ field: "status", label: "状态" },
|
||||
]
|
||||
: [
|
||||
{ field: "name", label: "分组" },
|
||||
{ field: "count", label: "数量" },
|
||||
];
|
||||
return (block.columns?.length ? block.columns : defaults).filter((column) =>
|
||||
allowed.includes(column.field),
|
||||
);
|
||||
}
|
||||
export function presentationSeries(block: PresentationBlock) {
|
||||
const allowed = block.dataset ? fields[block.dataset] : [];
|
||||
const fallback =
|
||||
block.dataset === "daily"
|
||||
? { field: "total", label: "车次" }
|
||||
: block.dataset === "comparison"
|
||||
? { field: "current", label: "本期" }
|
||||
: { field: "count", label: "数量" };
|
||||
return (block.series?.length ? block.series : [fallback]).filter(
|
||||
(series) =>
|
||||
allowed.includes(series.field) &&
|
||||
!["name", "date", "label"].includes(series.field),
|
||||
);
|
||||
}
|
||||
export function presentationMetrics(
|
||||
result: AssistantResult,
|
||||
labels?: string[],
|
||||
) {
|
||||
return labels?.length
|
||||
? labels.flatMap((label) => {
|
||||
const metric = result.metrics.find((item) => item.label === label);
|
||||
return metric ? [metric] : [];
|
||||
})
|
||||
: result.metrics;
|
||||
}
|
||||
export function presentationValue(value: unknown, field: string) {
|
||||
if (value === undefined || value === null || value === "") return "—";
|
||||
if (typeof value === "number")
|
||||
return Number.isFinite(value)
|
||||
? `${value.toLocaleString("zh-CN")}${["rate", "share"].includes(field) ? "%" : ""}`
|
||||
: "—";
|
||||
if (typeof value === "boolean") return value ? "是" : "否";
|
||||
return typeof value === "string" ? value : "—";
|
||||
}
|
||||
export function presentationAction(
|
||||
interaction: PresentationInteraction | undefined,
|
||||
datum?: PresentationDatum,
|
||||
): AssistantAction | undefined {
|
||||
if (!interaction) return;
|
||||
if (interaction.kind === "followup")
|
||||
return { type: "explore", interactionId: interaction.id };
|
||||
if (
|
||||
interaction.kind === "group" &&
|
||||
datum?.keys &&
|
||||
typeof datum.keys === "object" &&
|
||||
Object.keys(datum.keys).length
|
||||
)
|
||||
return {
|
||||
type: "explore",
|
||||
interactionId: interaction.id,
|
||||
keys: datum.keys as Partial<Record<Dimension, string>>,
|
||||
};
|
||||
if (interaction.kind === "point" && typeof datum?.date === "string")
|
||||
return { type: "explore", interactionId: interaction.id, date: datum.date };
|
||||
}
|
||||
Reference in New Issue
Block a user