@xpert-ai/plugin-live-dashboard 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.xpertai-plugin/plugin.json +10 -2
- package/README.md +6 -0
- package/assets/branding/cover-v1.prompt.txt +27 -0
- package/assets/branding/cover-v1.webp +0 -0
- package/assets/previews/customer-brief.png +0 -0
- package/assets/previews/profit-atlas.png +0 -0
- package/assets/previews/trend-workspace.png +0 -0
- package/dist/index.js +6 -3
- package/dist/lib/agent.schema.d.ts +43 -37
- package/dist/lib/agent.schema.js +2 -1
- package/dist/lib/app-config.d.ts +62 -0
- package/dist/lib/app-config.js +38 -0
- package/dist/lib/artifact-type.provider.d.ts +15 -0
- package/dist/lib/artifact-type.provider.js +78 -0
- package/dist/lib/assistant-template.d.ts +1 -2
- package/dist/lib/assistant-template.js +3 -52
- package/dist/lib/content.provider.js +13 -5
- package/dist/lib/dashboard-navigation.d.ts +58 -14
- package/dist/lib/dashboard-navigation.js +11 -10
- package/dist/lib/dashboard-view.schema.d.ts +197 -46
- package/dist/lib/dashboard-view.schema.js +8 -1
- package/dist/lib/dashboard.service.d.ts +39 -2
- package/dist/lib/dashboard.service.js +52 -28
- package/dist/lib/document-libraries.d.ts +1 -1
- package/dist/lib/document-libraries.js +3 -2
- package/dist/lib/document-network.d.ts +1 -1
- package/dist/lib/document-network.js +2 -6
- package/dist/lib/editing-feedback.d.ts +5 -0
- package/dist/lib/editing-feedback.js +19 -0
- package/dist/lib/editing-patch.d.ts +22 -0
- package/dist/lib/editing-patch.js +46 -0
- package/dist/lib/editing-session.d.ts +167 -0
- package/dist/lib/editing-session.js +53 -0
- package/dist/lib/editing.d.ts +92 -60
- package/dist/lib/editing.js +36 -24
- package/dist/lib/html-guidance.d.ts +1 -1
- package/dist/lib/html-guidance.js +1 -1
- package/dist/lib/middleware.js +42 -18
- package/dist/lib/monitor.d.ts +70 -0
- package/dist/lib/monitor.js +101 -0
- package/dist/lib/plugin.js +4 -1
- package/dist/lib/project.d.ts +106 -0
- package/dist/lib/project.js +17 -0
- package/dist/lib/projects.schema.d.ts +49 -0
- package/dist/lib/projects.schema.js +7 -0
- package/dist/lib/projects.service.d.ts +23 -0
- package/dist/lib/projects.service.js +65 -0
- package/dist/lib/resource-card.js +1 -1
- package/dist/lib/runtime-context.d.ts +3 -0
- package/dist/lib/runtime-context.js +13 -0
- package/dist/lib/save-evidence.d.ts +1 -1
- package/dist/lib/templates.d.ts +2 -0
- package/dist/lib/templates.js +55 -0
- package/dist/lib/view.provider.d.ts +97 -7
- package/dist/lib/view.provider.js +39 -10
- package/dist/libraries/TREND-WORKSPACE-ICONS-LICENSE.txt +43 -0
- package/dist/libraries/catalog.json +1 -1
- package/dist/remote/app.css +1 -1
- package/dist/remote/app.js +33 -33
- package/dist/remote/build-manifest.json +115 -32
- package/dist/sandbox-actions/action.json +1 -1
- package/dist/sandbox-actions/bundle/host.js +2 -2
- package/dist/sandbox-actions/bundle/resource-cache.mjs +2 -1
- package/dist/sandbox-actions/bundle/validate.mjs +1 -1
- package/docs/docs.json +7 -0
- package/docs/frontend-libraries.mdx +12 -4
- package/docs/html-mode.md +3 -3
- package/docs/quick-start-acceptance.mdx +50 -0
- package/docs/quick-start.mdx +64 -0
- package/docs/superstore.mdx +38 -0
- package/docs/team-sharing.mdx +6 -2
- package/docs/trend-workspace-acceptance.mdx +43 -0
- package/docs/trend-workspace.mdx +30 -0
- package/docs/verification.mdx +4 -2
- package/docs/visual-styles-acceptance.mdx +40 -0
- package/docs/visual-styles.mdx +46 -0
- package/package.json +23 -17
- package/skills/live-dashboard/SKILL.md +27 -3
- package/skills/live-dashboard/examples/README.md +25 -0
- package/skills/live-dashboard/examples/catalog.json +79 -0
- package/skills/live-dashboard/examples/production-quality/README.md +25 -0
- package/skills/live-dashboard/examples/production-quality/dashboard.html +129 -0
- package/skills/live-dashboard/examples/production-quality/definition.json +67 -0
- package/skills/live-dashboard/examples/production-quality/demo.csv +7 -0
- package/skills/live-dashboard/examples/production-quality/demo.json +44 -0
- package/skills/live-dashboard/examples/production-quality/expected.json +26 -0
- package/skills/live-dashboard/examples/production-quality/preview.png +0 -0
- package/skills/live-dashboard/examples/production-quality/query.json +53 -0
- package/skills/live-dashboard/examples/regional-sales/README.md +25 -0
- package/skills/live-dashboard/examples/regional-sales/dashboard-create.json +35 -0
- package/skills/live-dashboard/examples/regional-sales/definition.json +35 -0
- package/skills/live-dashboard/examples/regional-sales/demo.csv +5 -0
- package/skills/live-dashboard/examples/regional-sales/demo.json +18 -0
- package/skills/live-dashboard/examples/regional-sales/expected.json +19 -0
- package/skills/live-dashboard/examples/regional-sales/preview.png +0 -0
- package/skills/live-dashboard/examples/regional-sales/query.json +45 -0
- package/skills/live-dashboard/examples/sales-overview/README.md +25 -0
- package/skills/live-dashboard/examples/sales-overview/dashboard.html +129 -0
- package/skills/live-dashboard/examples/sales-overview/definition.json +57 -0
- package/skills/live-dashboard/examples/sales-overview/demo.csv +7 -0
- package/skills/live-dashboard/examples/sales-overview/demo.json +38 -0
- package/skills/live-dashboard/examples/sales-overview/expected.json +22 -0
- package/skills/live-dashboard/examples/sales-overview/preview.png +0 -0
- package/skills/live-dashboard/examples/sales-overview/query.json +52 -0
- package/skills/live-dashboard/examples/superstore/README.md +64 -0
- package/skills/live-dashboard/examples/superstore/THIRD_PARTY_LICENSE.txt +207 -0
- package/skills/live-dashboard/examples/superstore/build_data.py +155 -0
- package/skills/live-dashboard/examples/superstore/build_pages.py +129 -0
- package/skills/live-dashboard/examples/superstore/checks.json +104 -0
- package/skills/live-dashboard/examples/superstore/customers-mobile-dark.png +0 -0
- package/skills/live-dashboard/examples/superstore/customers-select.png +0 -0
- package/skills/live-dashboard/examples/superstore/customers.html +316 -0
- package/skills/live-dashboard/examples/superstore/customers.png +0 -0
- package/skills/live-dashboard/examples/superstore/customers.queries.json +512 -0
- package/skills/live-dashboard/examples/superstore/expected.json +4069 -0
- package/skills/live-dashboard/examples/superstore/import.json +200 -0
- package/skills/live-dashboard/examples/superstore/orders.csv +10195 -0
- package/skills/live-dashboard/examples/superstore/overview-mobile-dark.png +0 -0
- package/skills/live-dashboard/examples/superstore/overview-select.png +0 -0
- package/skills/live-dashboard/examples/superstore/overview.html +310 -0
- package/skills/live-dashboard/examples/superstore/overview.png +0 -0
- package/skills/live-dashboard/examples/superstore/overview.queries.json +636 -0
- package/skills/live-dashboard/examples/superstore/page.js +109 -0
- package/skills/live-dashboard/examples/superstore/profitability-mobile-dark.png +0 -0
- package/skills/live-dashboard/examples/superstore/profitability-select.png +0 -0
- package/skills/live-dashboard/examples/superstore/profitability.html +307 -0
- package/skills/live-dashboard/examples/superstore/profitability.png +0 -0
- package/skills/live-dashboard/examples/superstore/profitability.queries.json +334 -0
- package/skills/live-dashboard/examples/superstore/provenance.json +24 -0
- package/skills/live-dashboard/examples/superstore/semantic-model.json +220 -0
- package/skills/live-dashboard/examples/superstore/test_data.py +62 -0
- package/skills/live-dashboard/references/html-runtime.md +1 -1
- package/skills/live-dashboard/themes/README.md +50 -0
- package/skills/live-dashboard/themes/customer-brief/README.md +47 -0
- package/skills/live-dashboard/themes/customer-brief/charts.js +17 -0
- package/skills/live-dashboard/themes/customer-brief/layout.html +22 -0
- package/skills/live-dashboard/themes/customer-brief/theme.css +108 -0
- package/skills/live-dashboard/themes/profit-atlas/README.md +43 -0
- package/skills/live-dashboard/themes/profit-atlas/charts.js +30 -0
- package/skills/live-dashboard/themes/profit-atlas/layout.html +19 -0
- package/skills/live-dashboard/themes/profit-atlas/theme.css +75 -0
- package/skills/live-dashboard/themes/trend-workspace/ICON_LICENSE.txt +43 -0
- package/skills/live-dashboard/themes/trend-workspace/README.md +144 -0
- package/skills/live-dashboard/themes/trend-workspace/charts.js +60 -0
- package/skills/live-dashboard/themes/trend-workspace/icons.js +11 -0
- package/skills/live-dashboard/themes/trend-workspace/layout.html +45 -0
- package/skills/live-dashboard/themes/trend-workspace/select.js +127 -0
- package/skills/live-dashboard/themes/trend-workspace/theme.css +137 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"payloadFields": {
|
|
3
|
+
"select": [
|
|
4
|
+
"region",
|
|
5
|
+
"amount"
|
|
6
|
+
],
|
|
7
|
+
"filters": [
|
|
8
|
+
{
|
|
9
|
+
"column": "region",
|
|
10
|
+
"operator": "eq",
|
|
11
|
+
"value": {
|
|
12
|
+
"type": "parameter",
|
|
13
|
+
"name": "region"
|
|
14
|
+
},
|
|
15
|
+
"skipWhenNull": true
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"orderBy": [
|
|
19
|
+
{
|
|
20
|
+
"column": "region",
|
|
21
|
+
"direction": "asc"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"parameters": [
|
|
26
|
+
{
|
|
27
|
+
"name": "region",
|
|
28
|
+
"controlId": "region",
|
|
29
|
+
"type": "string",
|
|
30
|
+
"required": false,
|
|
31
|
+
"defaultValue": null
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"grain": [
|
|
35
|
+
"region"
|
|
36
|
+
],
|
|
37
|
+
"controls": [
|
|
38
|
+
{
|
|
39
|
+
"controlId": "region",
|
|
40
|
+
"label": "区域",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"defaultValue": null
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 销售总览 / sales-overview — v1
|
|
2
|
+
|
|
3
|
+
这是可复制的制作配方,演示数据是人为构造的,不代表任何客户或业务。`preview.png` 是这份演示数据的页面预览。
|
|
4
|
+
|
|
5
|
+
## 数据与口径
|
|
6
|
+
|
|
7
|
+
- 粒度:`date, region`,同一粒度不重复。金额单位为整元 CNY;数量为非负整数。
|
|
8
|
+
- 筛选:`region`,默认 `null` 表示全部;不要用字符串 `"null"`。
|
|
9
|
+
- 金额可按互斥粒度求和;订单数同一粒度不可重复计算。
|
|
10
|
+
- `definition.json` 给出列类型和业务说明;`query.json` 给出查询字段、参数和控件;`expected.json` 给出验收结果。
|
|
11
|
+
|
|
12
|
+
## 用演示数据制作
|
|
13
|
+
|
|
14
|
+
1. 读取主技能及 `../README.md`。确认当前 ChatKit 项目已选定,用户明确要求演示数据。
|
|
15
|
+
2. 读取 `demo.csv`,以 SandboxFile 工具写入当前项目的 `live-dashboard-examples/sales-overview/demo-v1.csv`。如果已存在,先读内容;不同则使用新文件名,不覆盖。`demo.json` 是同一数据的替代输入,使用时 reader 应为 `{"format":"json","rowsPath":[]}`,列 path 保持相同。
|
|
16
|
+
3. 按共享制作流程注册实际文件并取得当前 source / discovery / bindingPayload;不要把文件路径放进 Binding payload。
|
|
17
|
+
4. 基于 `query.json`,使用发现结果的 datasetId、revision 和 contractHash 构造查询。预览全部、至少两个筛选值,逐项比对 `expected.json`。输出字段 id 必须与本样例列 key 一致;logicalType/nullable/unit 必须与发现结果一致。
|
|
18
|
+
5. 读取 `dashboard.html`,仅使用本次唯一的完整 Dataset Binding。保留注释中的数据与生命周期约束。调用 dashboard_libraries,写入当前项目文件,再使用本次 previewIds、query.json 的 controls 和 libraries:["echarts"] 调用 dashboard_create_html。
|
|
19
|
+
6. 检查成功保存回执,报告看板及草稿修订,结束制作;不要为了确认保存而再调用 dashboard_open/update。用户通过回执卡片打开看板核对筛选、刷新与项目归属。保存失败不得宣布完成,发布仍需人工确认。
|
|
20
|
+
|
|
21
|
+
## 换成真实数据
|
|
22
|
+
|
|
23
|
+
先使用已授权来源发现与真实预览,将业务字段映射到本配方列(或同步改写页面字段和控件)。确认粒度、单位、日期和指标口径。缺少必要字段时说明缺口,不插入演示行、不补零、不复制旧 source/preview/Artifact ID。移除演示标识仅限绑定和所有可见数值均来自已验证真实来源之后。
|
|
24
|
+
|
|
25
|
+
配方更新不迁移或覆盖已创建的看板。编辑现有看板必须先 dashboard_open,保持 Artifact、组件 ID 和预期修订。
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-Hans">
|
|
3
|
+
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>销售总览 · 演示数据</title>
|
|
4
|
+
<style>
|
|
5
|
+
:root{color-scheme:light;--bg:#f5f7fa;--paper:#fff;--ink:#172d42;--muted:#526579;--line:#d8e0e8;--accent:#22649a;--second:#16847d}
|
|
6
|
+
*{box-sizing:border-box}
|
|
7
|
+
body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.5 system-ui,"PingFang SC",sans-serif}
|
|
8
|
+
body.dark{color-scheme:dark;--bg:#142433;--paper:#1c3042;--ink:#ecf2f8;--muted:#b0c2d1;--line:#3b5266;--accent:#75b7ee;--second:#65cabc}
|
|
9
|
+
main{max-width:1320px;margin:auto;padding:28px}
|
|
10
|
+
header{display:flex;justify-content:space-between;align-items:flex-start;gap:20px;border-bottom:1px solid var(--line);padding-bottom:20px}
|
|
11
|
+
h1{font-size:26px;line-height:1.3;margin:4px 0 8px}
|
|
12
|
+
h2{font-size:17px;margin:0 0 8px}
|
|
13
|
+
p{margin:0}
|
|
14
|
+
.muted{color:var(--muted)}
|
|
15
|
+
.demo{display:inline-block;border:1px solid var(--line);border-radius:4px;padding:2px 7px;font-size:12px}
|
|
16
|
+
.filter{display:grid;gap:6px;min-width:160px}
|
|
17
|
+
select{font:inherit;color:var(--ink);background:var(--paper);border:1px solid var(--line);border-radius:6px;padding:9px}
|
|
18
|
+
select:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
|
|
19
|
+
.metrics{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;padding:25px 0}
|
|
20
|
+
.metric{border-left:3px solid var(--accent);padding-left:16px}
|
|
21
|
+
.metric p{color:var(--muted)}
|
|
22
|
+
.metric output{font-size:32px;font-weight:650;font-variant-numeric:tabular-nums;display:block}
|
|
23
|
+
.plots{display:grid;grid-template-columns:3fr 2fr;gap:20px}
|
|
24
|
+
.panel{min-width:0;background:var(--paper);padding:20px;border:1px solid var(--line);border-radius:8px}
|
|
25
|
+
.chart{height:300px}
|
|
26
|
+
.details{margin-top:20px;overflow:auto}
|
|
27
|
+
table{width:100%;border-collapse:collapse;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
28
|
+
caption{text-align:left;font-size:17px;font-weight:600;margin-bottom:10px}
|
|
29
|
+
th,td{padding:10px 12px;border-bottom:1px solid var(--line);text-align:right}
|
|
30
|
+
th:first-child,td:first-child{text-align:left}
|
|
31
|
+
th{color:var(--muted);font-weight:500}
|
|
32
|
+
#feedback{min-height:28px;margin-top:16px}
|
|
33
|
+
footer{display:flex;justify-content:space-between;gap:16px;margin-top:20px;font-size:12px;color:var(--muted)}
|
|
34
|
+
@media(max-width:700px){main{padding:16px}
|
|
35
|
+
header{flex-direction:column}
|
|
36
|
+
.filter{width:100%}
|
|
37
|
+
.metrics{gap:12px}
|
|
38
|
+
.metric{padding-left:8px}
|
|
39
|
+
.metric output{font-size:24px}
|
|
40
|
+
.plots{grid-template-columns:1fr}
|
|
41
|
+
.panel{padding:14px}
|
|
42
|
+
footer{flex-direction:column}
|
|
43
|
+
}
|
|
44
|
+
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
</style></head>
|
|
48
|
+
<body><main>
|
|
49
|
+
<header><div><span class="demo">演示数据 · 人为构造</span><h1>销售总览</h1><p class="muted">从每日销售趋势追踪区域表现</p></div><label class="filter" for="scope">区域<select id="scope" data-dashboard-control="region" disabled></select></label></header>
|
|
50
|
+
<section class="metrics" aria-label="主要指标"><div class="metric"><p>销售额(CNY)</p><output id="amount">—</output></div><div class="metric"><p>订单数</p><output id="orders">—</output></div><div class="metric"><p>平均订单金额(CNY)</p><output id="average">—</output></div></section>
|
|
51
|
+
<div class="plots"><section class="panel"><h2>每日销售趋势</h2><p class="muted">销售额(CNY)</p><div id="trend" class="chart" role="img" aria-label="每日销售趋势,同一数据详见下表"></div></section><section class="panel"><h2>区域销售对比</h2><p class="muted">销售额(CNY)</p><div id="breakdown" class="chart" role="img" aria-label="区域销售对比,同一数据详见下表"></div></section></div>
|
|
52
|
+
<p id="feedback" role="status">正在查询…</p>
|
|
53
|
+
<section class="panel details"><table><caption>数据明细</caption><thead><tr><th scope="col">日期</th><th scope="col">区域</th><th scope="col">销售额(CNY)</th><th scope="col">订单数</th></tr></thead><tbody id="rows"></tbody></table></section>
|
|
54
|
+
<footer><span>金额为整元 CNY;演示日期为 2026 年 1 月 1 日至 3 日。</span><span id="freshness">尚未取得查询结果</span></footer>
|
|
55
|
+
</main><script>
|
|
56
|
+
// Example v1. One complete Dataset drives every panel. Adapt explicit field IDs after real preview.
|
|
57
|
+
// No business rows, source IDs, query text or credentials belong in this HTML.
|
|
58
|
+
(async () => {
|
|
59
|
+
const live = window.xpertLiveArtifact;
|
|
60
|
+
const filter = document.getElementById('scope');
|
|
61
|
+
const feedback = document.getElementById('feedback');
|
|
62
|
+
const fields = ["date", "region", "amount", "orders"];
|
|
63
|
+
const measures = ["amount", "orders"];
|
|
64
|
+
const fmt = new Intl.NumberFormat('zh-CN', {maximumFractionDigits: 2});
|
|
65
|
+
let trend, breakdown;
|
|
66
|
+
function clear(message) {
|
|
67
|
+
document.querySelectorAll('output').forEach(el => { el.textContent = '—'; });
|
|
68
|
+
document.getElementById('rows').replaceChildren();
|
|
69
|
+
trend?.clear(); breakdown?.clear();
|
|
70
|
+
feedback.textContent = message;
|
|
71
|
+
document.getElementById('freshness').textContent = '当前结果不可用';
|
|
72
|
+
}
|
|
73
|
+
function total(rows, field) {
|
|
74
|
+
const values = rows.map(row => {
|
|
75
|
+
const raw = row[field];
|
|
76
|
+
if (typeof raw !== 'string' && typeof raw !== 'number' || !/^\d+$/.test(String(raw))) throw Error('计数字段或金额单位不符合样例口径');
|
|
77
|
+
const n = Number(raw); if (!Number.isSafeInteger(n)) throw Error('数值超过本样例安全整数范围');
|
|
78
|
+
return n;
|
|
79
|
+
});
|
|
80
|
+
const sum = values.reduce((a,b) => a+b,0);
|
|
81
|
+
if (!Number.isSafeInteger(sum)) throw Error('合计超过本样例安全整数范围');
|
|
82
|
+
return sum;
|
|
83
|
+
}
|
|
84
|
+
function group(rows, dimension, measure) {
|
|
85
|
+
const groups = new Map();
|
|
86
|
+
for (const row of rows) {
|
|
87
|
+
if (typeof row[dimension] !== 'string') throw Error('维度字段缺失');
|
|
88
|
+
const key = row[dimension]; if (!groups.has(key)) groups.set(key,[]); groups.get(key).push(row);
|
|
89
|
+
}
|
|
90
|
+
return [...groups].sort(([a],[b])=>a.localeCompare(b)).map(([key,items])=>[key,total(items,measure)]);
|
|
91
|
+
}
|
|
92
|
+
function render(dataset) {
|
|
93
|
+
try {
|
|
94
|
+
if (!dataset.completeness || dataset.completeness.truncated) { clear('结果不完整,请缩小范围后重试;当前不显示汇总值。'); return; }
|
|
95
|
+
const rows = dataset.rows;
|
|
96
|
+
if (!rows.length) { clear('此筛选下没有数据。'); return; }
|
|
97
|
+
for (const row of rows) for (const field of fields) if (row[field] === null || row[field] === undefined) throw Error('必要字段为空,无法展示完整指标');
|
|
98
|
+
const totals = Object.fromEntries(measures.map(field => [field,total(rows,field)]));
|
|
99
|
+
document.getElementById('amount').textContent=fmt.format(totals.amount);document.getElementById('orders').textContent=fmt.format(totals.orders);document.getElementById('average').textContent=totals.orders ? fmt.format(totals.amount/totals.orders) : '—';
|
|
100
|
+
const daily = group(rows,'date','amount');
|
|
101
|
+
const groups = group(rows,'region','amount');
|
|
102
|
+
const color = getComputedStyle(document.body).getPropertyValue('--ink').trim();
|
|
103
|
+
const line = getComputedStyle(document.body).getPropertyValue('--line').trim();
|
|
104
|
+
const axis = {axisLabel:{color},axisLine:{lineStyle:{color:line}},splitLine:{lineStyle:{color:line}}};
|
|
105
|
+
trend.setOption({animation:false,aria:{enabled:true},color:['#3984bd'],grid:{left:60,right:18,top:28,bottom:35},tooltip:{trigger:'axis'},xAxis:{...axis,type:'category',data:daily.map(([k])=>k)},yAxis:{...axis,type:'value'},series:[{id:'daily',name:'销售额(CNY)',type:'line',symbolSize:7,areaStyle:{opacity:0.08},data:daily.map(([,v])=>v)}]},{notMerge:true});
|
|
106
|
+
breakdown.setOption({animation:false,aria:{enabled:true},color:['#16847d'],grid:{left:65,right:30,top:20,bottom:35},tooltip:{trigger:'axis'},xAxis:{...axis,type:'value'},yAxis:{...axis,type:'category',data:groups.map(([k])=>k)},series:[{id:'breakdown',name:'销售额(CNY)',type:'bar',barMaxWidth:38,label:{show:true,position:'right',color},data:groups.map(([,v])=>v)}]},{notMerge:true});
|
|
107
|
+
document.getElementById('rows').replaceChildren(...rows.map(row => { const tr=document.createElement('tr'); for(const field of fields){const td=document.createElement('td');td.textContent=String(row[field]);tr.append(td);}return tr;}));
|
|
108
|
+
feedback.textContent = '已显示 '+ rows.length +' 行,所有图表与指标使用同一筛选结果。';
|
|
109
|
+
document.getElementById('freshness').textContent = '查询时间:'+(dataset.freshness?.sourceFetchedAt ?? '未提供');
|
|
110
|
+
} catch (error) { clear(error.message || '数据无法展示'); }
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const context = await live.ready();
|
|
114
|
+
document.body.classList.toggle('dark', context.theme === 'dark');
|
|
115
|
+
if (context.bindingIds.length !== 1) throw Error('本样例要求一个已验证的完整数据集');
|
|
116
|
+
const bindingId = context.bindingIds[0];
|
|
117
|
+
trend=window.echarts.init(document.getElementById('trend'),null,{renderer:'svg'});
|
|
118
|
+
breakdown=window.echarts.init(document.getElementById('breakdown'),null,{renderer:'svg'});
|
|
119
|
+
new ResizeObserver(()=>{trend.resize();breakdown.resize();}).observe(document.querySelector('.plots'));
|
|
120
|
+
// Subscribe before bindSelect/query so toolbar refresh uses this same render function.
|
|
121
|
+
live.subscribe(bindingId,render);
|
|
122
|
+
filter.addEventListener('change',()=>clear('正在查询…'));
|
|
123
|
+
filter.addEventListener('xpert-query-error',()=>clear('查询失败,请检查访问权限或重试。'));
|
|
124
|
+
live.bindSelect(filter,{bindingId,controlId:'region',fieldId:'region',allLabel:'全部'});
|
|
125
|
+
await live.query(bindingId,{...context.defaults});
|
|
126
|
+
filter.disabled=false;
|
|
127
|
+
} catch(error) { clear(error.message || '查询失败,请重试'); }
|
|
128
|
+
})();
|
|
129
|
+
</script></body></html>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"path": "live-dashboard-examples/sales-overview/demo-v1.csv",
|
|
3
|
+
"name": "销售总览 · 演示数据 v1",
|
|
4
|
+
"description": "Synthetic example data; not business results. Rows have disjoint grain; amount is whole CNY and orders are counts.",
|
|
5
|
+
"reader": {
|
|
6
|
+
"format": "csv",
|
|
7
|
+
"delimiter": ",",
|
|
8
|
+
"headerRow": 1
|
|
9
|
+
},
|
|
10
|
+
"columns": [
|
|
11
|
+
{
|
|
12
|
+
"key": "date",
|
|
13
|
+
"path": [
|
|
14
|
+
"date"
|
|
15
|
+
],
|
|
16
|
+
"label": "日期",
|
|
17
|
+
"description": "Synthetic demonstration only. Unique daily dimension grain.",
|
|
18
|
+
"logicalType": "date",
|
|
19
|
+
"nullable": false,
|
|
20
|
+
"role": "time"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"key": "region",
|
|
24
|
+
"path": [
|
|
25
|
+
"region"
|
|
26
|
+
],
|
|
27
|
+
"label": "区域",
|
|
28
|
+
"description": "Synthetic demonstration only. Unique daily dimension grain.",
|
|
29
|
+
"logicalType": "string",
|
|
30
|
+
"nullable": false,
|
|
31
|
+
"role": "dimension"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"key": "amount",
|
|
35
|
+
"path": [
|
|
36
|
+
"amount"
|
|
37
|
+
],
|
|
38
|
+
"label": "销售额",
|
|
39
|
+
"description": "Synthetic demonstration only. Whole CNY, no fractional currency.",
|
|
40
|
+
"logicalType": "int64",
|
|
41
|
+
"nullable": false,
|
|
42
|
+
"role": "measure",
|
|
43
|
+
"unit": "CNY"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"key": "orders",
|
|
47
|
+
"path": [
|
|
48
|
+
"orders"
|
|
49
|
+
],
|
|
50
|
+
"label": "订单数",
|
|
51
|
+
"description": "Synthetic demonstration only. Nonnegative whole-item count.",
|
|
52
|
+
"logicalType": "int64",
|
|
53
|
+
"nullable": false,
|
|
54
|
+
"role": "measure"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"date": "2026-01-01",
|
|
4
|
+
"region": "East",
|
|
5
|
+
"amount": 12000,
|
|
6
|
+
"orders": 60
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"date": "2026-01-01",
|
|
10
|
+
"region": "West",
|
|
11
|
+
"amount": 8000,
|
|
12
|
+
"orders": 40
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"date": "2026-01-02",
|
|
16
|
+
"region": "East",
|
|
17
|
+
"amount": 15000,
|
|
18
|
+
"orders": 75
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"date": "2026-01-02",
|
|
22
|
+
"region": "West",
|
|
23
|
+
"amount": 11000,
|
|
24
|
+
"orders": 55
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"date": "2026-01-03",
|
|
28
|
+
"region": "East",
|
|
29
|
+
"amount": 17000,
|
|
30
|
+
"orders": 85
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"date": "2026-01-03",
|
|
34
|
+
"region": "West",
|
|
35
|
+
"amount": 13000,
|
|
36
|
+
"orders": 65
|
|
37
|
+
}
|
|
38
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"all": {
|
|
3
|
+
"amount": 76000,
|
|
4
|
+
"orders": 380
|
|
5
|
+
},
|
|
6
|
+
"filtered": {
|
|
7
|
+
"East": {
|
|
8
|
+
"amount": 44000,
|
|
9
|
+
"orders": 220
|
|
10
|
+
},
|
|
11
|
+
"West": {
|
|
12
|
+
"amount": 32000,
|
|
13
|
+
"orders": 160
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"checks": [
|
|
17
|
+
"All \u2192 first option \u2192 second option \u2192 all; chart, KPI and detail agree.",
|
|
18
|
+
"Refresh uses the current filter.",
|
|
19
|
+
"Empty results clear charts and show no-data; errors never show old totals as current.",
|
|
20
|
+
"Saved draft remains unpublished and belongs to current Project."
|
|
21
|
+
]
|
|
22
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"payloadFields": {
|
|
3
|
+
"select": [
|
|
4
|
+
"date",
|
|
5
|
+
"region",
|
|
6
|
+
"amount",
|
|
7
|
+
"orders"
|
|
8
|
+
],
|
|
9
|
+
"filters": [
|
|
10
|
+
{
|
|
11
|
+
"column": "region",
|
|
12
|
+
"operator": "eq",
|
|
13
|
+
"value": {
|
|
14
|
+
"type": "parameter",
|
|
15
|
+
"name": "region"
|
|
16
|
+
},
|
|
17
|
+
"skipWhenNull": true
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"orderBy": [
|
|
21
|
+
{
|
|
22
|
+
"column": "date",
|
|
23
|
+
"direction": "asc"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"column": "region",
|
|
27
|
+
"direction": "asc"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"parameters": [
|
|
32
|
+
{
|
|
33
|
+
"name": "region",
|
|
34
|
+
"controlId": "region",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"required": false,
|
|
37
|
+
"defaultValue": null
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"grain": [
|
|
41
|
+
"date",
|
|
42
|
+
"region"
|
|
43
|
+
],
|
|
44
|
+
"controls": [
|
|
45
|
+
{
|
|
46
|
+
"controlId": "region",
|
|
47
|
+
"label": "区域",
|
|
48
|
+
"type": "string",
|
|
49
|
+
"defaultValue": null
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Superstore 企业 BI — v1
|
|
2
|
+
|
|
3
|
+
一个 ChatKit 项目,三个看板:`overview.html`(零售经营总览)、`profitability.html`(商品盈利)、`customers.html`(客户与退货)。这是 Tableau 的虚构教学数据,必须保留页面演示标识。页面参考经典企业经营分析的信息组织,自行实现;不是第三方产品截图。
|
|
4
|
+
|
|
5
|
+
## 数据准备(管理员一次完成)
|
|
6
|
+
|
|
7
|
+
1. 阅读 `provenance.json` 和 `THIRD_PARTY_LICENSE.txt`。`orders.csv` 是固定官方工作簿的确定性转换结果,10,194 行;不要把整个文件送入模型上下文。
|
|
8
|
+
2. 在 Data Xpert 中为有权限的工作空间绑定一个 PostgreSQL 演示数据源,通过“导入”上传 `orders.csv`。`import.json` 是字段定义:金额为 NUMERIC(20,6),邮编为文本,`returned_order_id` 允许 NULL。使用一个尚不存在的表名;不要选择删除/覆盖已有表。API 自动化也必须先检查表是否存在,不能重复追加。
|
|
9
|
+
3. 创建新的 SQL 语义模型,以 `semantic-model.json` 为结构模板,按实际元数据调整数据库/schema/表名;保留技术 Cube 名 `SuperstoreOrders` 和字段名。核对所有度量与维度后执行草稿验证,发布到有权限的业务域。
|
|
10
|
+
4. 将发布后的本体资源授权给当前 Live Dashboard Assistant,配置 Data Xpert integration 和当前查看者的只读查询权限。数据权限不会由安装本样例自动授予。
|
|
11
|
+
5. 通过 Data Xpert 真实查询核对 `expected.json`。全量订单 5,111、客户 804、退货订单 296;金额核对到 6 位小数。未经验证不能宣布“数据准备完成”。
|
|
12
|
+
|
|
13
|
+
此配方使用 `data-xpert.ontology`,不是 `file.table`。缺少已发布模型时报告这一前置步骤;不得用三个入门配方、小量自造数据或浏览器内嵌数据替代。数据准备不需要增加新的 Project View 或后台定时任务。
|
|
14
|
+
|
|
15
|
+
## 助理制作步骤
|
|
16
|
+
|
|
17
|
+
先读 [主题选择说明](../../themes/README.md),再读目标页的独立主题指南。三个可选风格都使用键盘可用的双层标签选择器;CSS、选择器和图表组件随技能安装,通过版本和内容哈希固定的文档库加载。
|
|
18
|
+
|
|
19
|
+
| 页面 | 风格及指南 | libraries |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `overview.html` | [Trend Workspace](../../themes/trend-workspace/README.md):浅蓝 KPI 侧栏、上下趋势 | `["echarts","trend-workspace"]` |
|
|
22
|
+
| `profitability.html` | [Profit Atlas](../../themes/profit-atlas/README.md):深色横向指标、大散点图、带正负号的利润排名 | `["echarts","profit-atlas"]` |
|
|
23
|
+
| `customers.html` | [Customer Brief](../../themes/customer-brief/README.md):暖纸色、衬线标题、分组退货指标 | `["echarts","customer-brief"]` |
|
|
24
|
+
|
|
25
|
+
用户要求美化已有看板时,逐个 `dashboard_open` / `dashboard_update`,保留 Artifact、Binding、控件和已有 HTML ID,显式传入目标页对应的 libraries。先读当前编辑副本,用有界精确编辑替换业务布局,不能新建副本。没有要求重设计时保留已有风格。用于其他数据时须重新匹配字段和口径,不能只替换标题而保留 Superstore 字段。
|
|
26
|
+
|
|
27
|
+
1. 先阅读主技能和 HTML runtime/lifecycle。确认当前 ChatKit Project。用户请求整个案例时制作三个草稿;只请求其中一个时仅制作该页。
|
|
28
|
+
2. 使用 `dashboard_list_sources` 和 Data Xpert analytics definition 的 connections/inspect/register/discover 流程,找到当前 Assistant 已授权的 **SuperstoreOrders**。记录实际 definitionId/versionId、resourceId、entityRef;不能沿用别的 Assistant/项目的 ID。
|
|
29
|
+
3. 用 `read_skill_file` 阅读目标页的 `*.queries.json`(相对技能目录定位)。每个 binding 是查询计划,尚未包含 source;替换 payload 中发现得到的 resourceId/entityRef,并添加本次 source。字段类型、可空性、action schema 以实际发现结果为准;稳定保留 binding.id、output.datasetId、output.fields[].id 和控件 ID,HTML 使用这些名字。
|
|
30
|
+
4. 每个查询包含同样的 year/region/category 参数。空字符串表示全部,通过 contains 匹配;allowedValues 严格限定工作簿中真实存在的枚举。总计 Binding 的 dimensions 必须为空,让后端执行 COUNT DISTINCT;不能把月度、子品类或分群的订单/客户数相加冒充全量。
|
|
31
|
+
5. 使用 `dashboard_preview_binding` 预览每个完整 Binding。先读小文件 `checks.json`,对总计 Binding 用 `dashboard_test_filter` 验证全量、West、East、2024、Furniture 和组合筛选;`controls` 原样传全部三个值,`expectations` 选择实际存在的字段,`fieldId` 设为该字段名,`oneOf` 使用对应 case 的 summary 中原样字符串值。Data Xpert 的 int64/decimal 查询结果是字符串,oneOf 必须同类型。不要把全量计数当成区域计数;不要为验证筛选反复修改 Binding 默认值。分组查询与 `expected.json` 对照,并完成 preview 返回的 validation.suggestedCall。
|
|
32
|
+
6. 调用 `dashboard_libraries`,按上表只选择 ECharts 和该页一个主题。使用 `read_skill_file` 读取目标 HTML **完整内容**(不是只读前一段);若改用 sandbox_read_file 必须读取全部行。逐页读取、写入并保存,完成一页后再处理下一页,不在一次模型输出中重写三页。原样保留 HTML/CSS/JS,不压缩或简化。写入当前项目的新相对路径,例如 `superstore-v1/overview.html`。若路径已有不同内容,改用新的文件名,不覆盖别的 Artifact。使用 SandboxFile 工具;不需要重写模板代码或复制 10,194 行数据到 Sandbox。
|
|
33
|
+
7. 对三个页面分别调用 `dashboard_create_html`:标题为 `Superstore · 零售经营总览`、`Superstore · 商品盈利分析`、`Superstore · 客户与退货分析`,传入各自全部 previewIds、原样 controls 和 libraries。预检失败则按具体错误修复后重试,不省略 Binding 或控件。**三个页面的 Binding 集合不同,不能只把标题换掉。**
|
|
34
|
+
8. 收到成功的草稿回执后报告三个 Artifact 和修订,用户通过回执卡片/级联选择器打开。不要重新打开未改变的看板来确认保存;不要自动发布。
|
|
35
|
+
|
|
36
|
+
## 总计筛选基准(当前固定数据版本)
|
|
37
|
+
|
|
38
|
+
执行 `dashboard_test_filter` 时用下表当前筛选对应的值,不猜测常见 Superstore 版本的计数。int64 和 decimal 都使用字符串。全量参数都是空字符串。先验证 Orders 与 Customers;金额同时与真实返回值核对。
|
|
39
|
+
|
|
40
|
+
| year | region | category | Orders | Customers | Sales | Profit |
|
|
41
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
42
|
+
| `""` | `""` | `""` | `"5111"` | `"804"` | `"2326534.354300"` | `"292296.814600"` |
|
|
43
|
+
| `""` | `"West"` | `""` | `"1635"` | `"686"` | `"739813.608500"` | `"110798.817000"` |
|
|
44
|
+
| `""` | `"East"` | `""` | `"1475"` | `"685"` | `"691828.168000"` | `"94883.260300"` |
|
|
45
|
+
| `"2024"` | `""` | `""` | `"1723"` | `"704"` | `"745567.531200"` | `"95926.347600"` |
|
|
46
|
+
| `""` | `""` | `"Furniture"` | `"1819"` | `"714"` | `"754747.761300"` | `"19729.995600"` |
|
|
47
|
+
| `"2024"` | `"West"` | `"Furniture"` | `"202"` | `"184"` | `"74155.119500"` | `"2892.830000"` |
|
|
48
|
+
|
|
49
|
+
West 的 Orders 必须为 `"1635"`,不是其他版本的示例计数。若校验失败,读取 `checks.json` 与查询结果查明差异,不要通过更改默认筛选反复重试同一错误期望。
|
|
50
|
+
|
|
51
|
+
## 指标与交互
|
|
52
|
+
|
|
53
|
+
- Sales/Profit/Quantity 为订单行求和;Orders/Customers 为当前筛选的不同订单/客户数。
|
|
54
|
+
- 利润率 = 总利润 / 总销售额;客单价 = 总销售额 / 订单数。分母为零不显示伪造比率。
|
|
55
|
+
- Returns 原表 800 行,先按 Order ID 去重成 296 个订单再关联。ReturnedOrders 忽略 NULL,ReturnedSales 是退货订单关联的销售额,**不是退款或损失**。
|
|
56
|
+
- 年份、区域、品类改变时查询当前页的所有 Binding。各看板控件状态独立;同一页所有面板同范围。
|
|
57
|
+
- 查询错误、截断、无结果时清除之前的指标,保留明确状态;不要继续展示上一筛选的数值。
|
|
58
|
+
- 原值以 decimal 字符串传输,金额展示到两位;绘图及比率计算在验证安全数值范围后转换为 Number。
|
|
59
|
+
|
|
60
|
+
## 重建与验收
|
|
61
|
+
|
|
62
|
+
开发者安装 `xlrd==2.0.2` 后运行 `python build_data.py --workbook <下载文件>`;哈希不一致会停止。再运行 `python build_pages.py`,将本目录的业务布局与运行时组合成三个 HTML;主题组件由每页对应的受控文档库注入。Agent 直接使用已安装的组件与配方,不需要运行构建器。原始数据版本、CSV 哈希和许可见 `provenance.json`。
|
|
63
|
+
|
|
64
|
+
验收:全量 → West → East → 全量,2024、Furniture、组合筛选,刷新、空结果、权限失败、截断、窄屏和深色主题。比较所有图表和总计,确认项目双向同步与 Artifact 归属。浏览器模拟检查只证明页面生命周期;真实 Data Xpert 查询与安装后界面验收另行记录。
|