@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,25 @@
|
|
|
1
|
+
# Example authoring workflow
|
|
2
|
+
|
|
3
|
+
Use `read_skill_file` with paths resolved relative to the catalog SKILL.md to read `catalog.json`, then only the selected recipe. Supporting files can be read with the same tool; do not search sandbox directories or pass an absolute skill path to SandboxFile. Each recipe has a stable key and version, source, a preview, semantic field requirements, synthetic CSV/JSON and expected checks. Previews are illustrations of the bundled demo fixture, never evidence of a user's business data. The catalog is bundled with the skill, not a remote marketplace.
|
|
4
|
+
|
|
5
|
+
The Superstore enterprise suite is an exception to the small file workflow below: read `superstore/README.md` and use its published Data Xpert model, independent totals and multiple Bindings. Do not run file registration for that suite.
|
|
6
|
+
|
|
7
|
+
## Select the data mode first
|
|
8
|
+
|
|
9
|
+
An explicit request to “try demo” authorizes copying synthetic fixture data into the currently selected Project. If the mode is unspecified, ask whether to use the demo or real sources. Never put example data into an existing production file or silently use it when a real query fails. Do not create a new Project implicitly; let ChatKit's Project picker establish the scope.
|
|
10
|
+
|
|
11
|
+
## Execute, do not just return a page
|
|
12
|
+
|
|
13
|
+
1. Read the recipe's README, `definition.json`, `query.json` and source. Read HTML lifecycle guidance for HTML recipes. `demo.csv` and `demo.json` are alternatives, not two datasets to combine.
|
|
14
|
+
2. Use SandboxFile to write the demo fixture to a new file in the current Project. Inspect an existing path before reusing it; identical bytes may be reused, different bytes need a new path. Registration resolves the current Project from trusted runtime context.
|
|
15
|
+
3. Call `analytics_definition_connections`. Select the actual `file.table` connection, then `analytics_definition_inspect` with that returned selection. Follow the returned schemas. Use `definition.json` with the actual runtime path for `analytics_definition_register`; use a stable requestId for this exact file/definition. Reuse an existing matching authorized definition when available. Do not invent connectionKey, definitionId or versionId.
|
|
16
|
+
4. Refresh `dashboard_list_sources`, discover the registered source, and use its current `bindingPayload`. Merge `query.json.payloadFields` into that payload. Bind query parameter names to the same control IDs. Output fields map to the recipe column keys, with the actual declared types/units/nullability and explicit grain.
|
|
17
|
+
5. Use `dashboard_preview` for defaults and at least two filter values. Compare real returned rows, completeness, totals and weighted ratios with `expected.json`. Use `dashboard_test_filter` from the preview checks for the two filter values; keep the original default/null Binding. Use exactly one current successful preview receipt for the one saved Binding, not a duplicate Binding per tested filter. Keep the returned datasetId in standard chart/table definitions. An HTML example expects exactly one saved complete Binding and resolves its ID from the SDK's declared bindingIds.
|
|
18
|
+
6. Standard recipe: call `dashboard_create` using `dashboard-create.json` with this run's previewId and datasetId. HTML: call `dashboard_libraries`, write the complete HTML file, then `dashboard_create_html` with this run's previewIds, declared controls and `libraries:["echarts"]`. Do not publish automatically. Fix preflight errors before claiming success.
|
|
19
|
+
7. Stop authoring after the successful draft receipt and report it with the checked values. Do not call dashboard_open/update to confirm an unchanged save. The emitted card lets the user open the exact dashboard. UI acceptance verifies the persisted draft in Live Dashboard: all → option A → option B → all, chart, totals, details and toolbar refresh. A successful query alone is not proof of visible correct charts. Check Project synchronization and report the actual draft receipt. Publication is a separate human action.
|
|
20
|
+
|
|
21
|
+
## Adapt to business data
|
|
22
|
+
|
|
23
|
+
Use authorized files, database sources or published Data Xpert ontology resources. Discover and preview the actual source first. Map fields explicitly; confirm grain, currency, units and date semantics. Missing fields or denied access are blockers for the affected panel, not permission to invent data. For quality, weighted yield is sum(good)/sum(produced); a zero denominator displays “—”. These examples use safe integer counts and whole currency units; fractional or high-precision monetary data requires exact decimal aggregation and formatting rather than silently coercing to Number.
|
|
24
|
+
|
|
25
|
+
Never copy demonstration IDs, old preview receipts, file scope, credentials or backend SQL into HTML. Remove the visible demo label only after replacing and verifying all demo bindings. Preserve loading, empty, incomplete and error states; clear stale charts when a query fails. Keep every panel on the same filter. Existing saved dashboards are independent Artifacts: updating this example pack must not modify them. To edit one, use `dashboard_open` and its exact revision followed by the existing update workflow.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"examples": [
|
|
4
|
+
{
|
|
5
|
+
"key": "regional-sales",
|
|
6
|
+
"version": 1,
|
|
7
|
+
"title": "区域销售",
|
|
8
|
+
"mode": "standard",
|
|
9
|
+
"recipe": "regional-sales/README.md",
|
|
10
|
+
"source": "regional-sales/dashboard-create.json",
|
|
11
|
+
"preview": "regional-sales/preview.png",
|
|
12
|
+
"data": [
|
|
13
|
+
"demo.csv",
|
|
14
|
+
"demo.json"
|
|
15
|
+
],
|
|
16
|
+
"dataMode": "synthetic-demo",
|
|
17
|
+
"libraries": []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"key": "sales-overview",
|
|
21
|
+
"version": 1,
|
|
22
|
+
"title": "销售总览",
|
|
23
|
+
"mode": "html",
|
|
24
|
+
"recipe": "sales-overview/README.md",
|
|
25
|
+
"source": "sales-overview/dashboard.html",
|
|
26
|
+
"preview": "sales-overview/preview.png",
|
|
27
|
+
"data": [
|
|
28
|
+
"demo.csv",
|
|
29
|
+
"demo.json"
|
|
30
|
+
],
|
|
31
|
+
"dataMode": "synthetic-demo",
|
|
32
|
+
"libraries": [
|
|
33
|
+
"echarts"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"key": "production-quality",
|
|
38
|
+
"version": 1,
|
|
39
|
+
"title": "生产质量",
|
|
40
|
+
"mode": "html",
|
|
41
|
+
"recipe": "production-quality/README.md",
|
|
42
|
+
"source": "production-quality/dashboard.html",
|
|
43
|
+
"preview": "production-quality/preview.png",
|
|
44
|
+
"data": [
|
|
45
|
+
"demo.csv",
|
|
46
|
+
"demo.json"
|
|
47
|
+
],
|
|
48
|
+
"dataMode": "synthetic-demo",
|
|
49
|
+
"libraries": [
|
|
50
|
+
"echarts"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"key": "superstore",
|
|
55
|
+
"version": 1,
|
|
56
|
+
"title": "Superstore 企业 BI",
|
|
57
|
+
"mode": "html-suite",
|
|
58
|
+
"recipe": "superstore/README.md",
|
|
59
|
+
"sources": [
|
|
60
|
+
"superstore/overview.html",
|
|
61
|
+
"superstore/profitability.html",
|
|
62
|
+
"superstore/customers.html"
|
|
63
|
+
],
|
|
64
|
+
"previews": [
|
|
65
|
+
"superstore/overview.png",
|
|
66
|
+
"superstore/profitability.png",
|
|
67
|
+
"superstore/customers.png"
|
|
68
|
+
],
|
|
69
|
+
"data": [
|
|
70
|
+
"orders.csv"
|
|
71
|
+
],
|
|
72
|
+
"dataMode": "tableau-teaching-data",
|
|
73
|
+
"providerId": "data-xpert.ontology",
|
|
74
|
+
"libraries": [
|
|
75
|
+
"echarts"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 生产质量 / production-quality — v1
|
|
2
|
+
|
|
3
|
+
这是可复制的制作配方,演示数据是人为构造的,不代表任何客户或业务。`preview.png` 是这份演示数据的页面预览。
|
|
4
|
+
|
|
5
|
+
## 数据与口径
|
|
6
|
+
|
|
7
|
+
- 粒度:`date, line`,同一粒度不重复。产量、良品数和不良品数的单位为件,均为非负整数。
|
|
8
|
+
- 筛选:`line`,默认 `null` 表示全部;不要用字符串 `"null"`。
|
|
9
|
+
- 良品率 = sum(good) / sum(produced),不能平均每行百分比;无产量时显示“—”。defects 为不良品数量,不是缺陷事件数。
|
|
10
|
+
- `definition.json` 给出列类型和业务说明;`query.json` 给出查询字段、参数和控件;`expected.json` 给出验收结果。
|
|
11
|
+
|
|
12
|
+
## 用演示数据制作
|
|
13
|
+
|
|
14
|
+
1. 读取主技能及 `../README.md`。确认当前 ChatKit 项目已选定,用户明确要求演示数据。
|
|
15
|
+
2. 读取 `demo.csv`,以 SandboxFile 工具写入当前项目的 `live-dashboard-examples/production-quality/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="line" disabled></select></label></header>
|
|
50
|
+
<section class="metrics" aria-label="主要指标"><div class="metric"><p>产量</p><output id="produced">—</output></div><div class="metric"><p>加权良品率(%)</p><output id="yield">—</output></div><div class="metric"><p>不良品数</p><output id="defects">—</output></div></section>
|
|
51
|
+
<div class="plots"><section class="panel"><h2>每日产量趋势</h2><p class="muted">产量(件)</p><div id="trend" class="chart" role="img" aria-label="每日产量趋势,同一数据详见下表"></div></section><section class="panel"><h2>产线不良品对比</h2><p class="muted">不良品数(件)</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">产量</th><th scope="col">良品数</th><th scope="col">不良品数</th></tr></thead><tbody id="rows"></tbody></table></section>
|
|
54
|
+
<footer><span>良品率 = 良品数合计 ÷ 产量合计;不良品数 = 产量 − 良品数。</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", "line", "produced", "good", "defects"];
|
|
63
|
+
const measures = ["produced", "good", "defects"];
|
|
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
|
+
if(totals.good>totals.produced || totals.defects!==totals.produced-totals.good) throw Error('产量、良品与不良品数量不一致');document.getElementById('produced').textContent=fmt.format(totals.produced);document.getElementById('defects').textContent=fmt.format(totals.defects);document.getElementById('yield').textContent=totals.produced ? fmt.format(totals.good/totals.produced*100) : '—';
|
|
100
|
+
const daily = group(rows,'date','produced');
|
|
101
|
+
const groups = group(rows,'line','defects');
|
|
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:'产量(件)',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:'不良品数(件)',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:'line',fieldId:'line',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,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"path": "live-dashboard-examples/production-quality/demo-v1.csv",
|
|
3
|
+
"name": "生产质量 · 演示数据 v1",
|
|
4
|
+
"description": "Synthetic example data; not business results. Yield = sum(good)/sum(produced); defects = produced-good. Rows are disjoint.",
|
|
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": "line",
|
|
24
|
+
"path": [
|
|
25
|
+
"line"
|
|
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": "produced",
|
|
35
|
+
"path": [
|
|
36
|
+
"produced"
|
|
37
|
+
],
|
|
38
|
+
"label": "产量",
|
|
39
|
+
"description": "Synthetic demonstration only. Nonnegative whole-item count.",
|
|
40
|
+
"logicalType": "int64",
|
|
41
|
+
"nullable": false,
|
|
42
|
+
"role": "measure"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"key": "good",
|
|
46
|
+
"path": [
|
|
47
|
+
"good"
|
|
48
|
+
],
|
|
49
|
+
"label": "良品数",
|
|
50
|
+
"description": "Synthetic demonstration only. Nonnegative whole-item count.",
|
|
51
|
+
"logicalType": "int64",
|
|
52
|
+
"nullable": false,
|
|
53
|
+
"role": "measure"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"key": "defects",
|
|
57
|
+
"path": [
|
|
58
|
+
"defects"
|
|
59
|
+
],
|
|
60
|
+
"label": "不良品数",
|
|
61
|
+
"description": "Synthetic demonstration only. Nonnegative whole-item count.",
|
|
62
|
+
"logicalType": "int64",
|
|
63
|
+
"nullable": false,
|
|
64
|
+
"role": "measure"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"date": "2026-01-01",
|
|
4
|
+
"line": "Line A",
|
|
5
|
+
"produced": 100,
|
|
6
|
+
"good": 96,
|
|
7
|
+
"defects": 4
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"date": "2026-01-01",
|
|
11
|
+
"line": "Line B",
|
|
12
|
+
"produced": 120,
|
|
13
|
+
"good": 114,
|
|
14
|
+
"defects": 6
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"date": "2026-01-02",
|
|
18
|
+
"line": "Line A",
|
|
19
|
+
"produced": 110,
|
|
20
|
+
"good": 104,
|
|
21
|
+
"defects": 6
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"date": "2026-01-02",
|
|
25
|
+
"line": "Line B",
|
|
26
|
+
"produced": 130,
|
|
27
|
+
"good": 126,
|
|
28
|
+
"defects": 4
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"date": "2026-01-03",
|
|
32
|
+
"line": "Line A",
|
|
33
|
+
"produced": 120,
|
|
34
|
+
"good": 117,
|
|
35
|
+
"defects": 3
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"date": "2026-01-03",
|
|
39
|
+
"line": "Line B",
|
|
40
|
+
"produced": 140,
|
|
41
|
+
"good": 133,
|
|
42
|
+
"defects": 7
|
|
43
|
+
}
|
|
44
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"all": {
|
|
3
|
+
"produced": 720,
|
|
4
|
+
"good": 690,
|
|
5
|
+
"defects": 30,
|
|
6
|
+
"yieldPercent": 95.83333333333333
|
|
7
|
+
},
|
|
8
|
+
"filtered": {
|
|
9
|
+
"Line A": {
|
|
10
|
+
"produced": 330,
|
|
11
|
+
"good": 317,
|
|
12
|
+
"defects": 13
|
|
13
|
+
},
|
|
14
|
+
"Line B": {
|
|
15
|
+
"produced": 390,
|
|
16
|
+
"good": 373,
|
|
17
|
+
"defects": 17
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"checks": [
|
|
21
|
+
"All \u2192 first option \u2192 second option \u2192 all; chart, KPI and detail agree.",
|
|
22
|
+
"Refresh uses the current filter.",
|
|
23
|
+
"Empty results clear charts and show no-data; errors never show old totals as current.",
|
|
24
|
+
"Saved draft remains unpublished and belongs to current Project."
|
|
25
|
+
]
|
|
26
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"payloadFields": {
|
|
3
|
+
"select": [
|
|
4
|
+
"date",
|
|
5
|
+
"line",
|
|
6
|
+
"produced",
|
|
7
|
+
"good",
|
|
8
|
+
"defects"
|
|
9
|
+
],
|
|
10
|
+
"filters": [
|
|
11
|
+
{
|
|
12
|
+
"column": "line",
|
|
13
|
+
"operator": "eq",
|
|
14
|
+
"value": {
|
|
15
|
+
"type": "parameter",
|
|
16
|
+
"name": "line"
|
|
17
|
+
},
|
|
18
|
+
"skipWhenNull": true
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"orderBy": [
|
|
22
|
+
{
|
|
23
|
+
"column": "date",
|
|
24
|
+
"direction": "asc"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"column": "line",
|
|
28
|
+
"direction": "asc"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"parameters": [
|
|
33
|
+
{
|
|
34
|
+
"name": "line",
|
|
35
|
+
"controlId": "line",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"required": false,
|
|
38
|
+
"defaultValue": null
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"grain": [
|
|
42
|
+
"date",
|
|
43
|
+
"line"
|
|
44
|
+
],
|
|
45
|
+
"controls": [
|
|
46
|
+
{
|
|
47
|
+
"controlId": "line",
|
|
48
|
+
"label": "产线",
|
|
49
|
+
"type": "string",
|
|
50
|
+
"defaultValue": null
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 区域销售 / regional-sales — v1
|
|
2
|
+
|
|
3
|
+
这是可复制的制作配方,演示数据是人为构造的,不代表任何客户或业务。`preview.png` 是这份演示数据的页面预览。
|
|
4
|
+
|
|
5
|
+
## 数据与口径
|
|
6
|
+
|
|
7
|
+
- 粒度:`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/regional-sales/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-create.json`,将 previewIds 替换为本次预览凭证,图表/table 的 datasetId 替换为预览返回值,再调用 dashboard_create。
|
|
19
|
+
6. 检查成功保存回执,报告看板及草稿修订;打开看板核对筛选、刷新与项目归属。保存失败不得宣布完成,发布仍需人工确认。
|
|
20
|
+
|
|
21
|
+
## 换成真实数据
|
|
22
|
+
|
|
23
|
+
先使用已授权来源发现与真实预览,将业务字段映射到本配方列(或同步改写页面字段和控件)。确认粒度、单位、日期和指标口径。缺少必要字段时说明缺口,不插入演示行、不补零、不复制旧 source/preview/Artifact ID。移除演示标识仅限绑定和所有可见数值均来自已验证真实来源之后。
|
|
24
|
+
|
|
25
|
+
配方更新不迁移或覆盖已创建的看板。编辑现有看板必须先 dashboard_open,保持 Artifact、组件 ID 和预期修订。
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "区域销售 · 演示数据",
|
|
3
|
+
"previewIds": [
|
|
4
|
+
"<current-preview-id>"
|
|
5
|
+
],
|
|
6
|
+
"controls": [
|
|
7
|
+
{
|
|
8
|
+
"controlId": "region",
|
|
9
|
+
"label": "区域",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"defaultValue": null
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"charts": [
|
|
15
|
+
{
|
|
16
|
+
"id": "region-sales",
|
|
17
|
+
"title": "区域销售额 · CNY",
|
|
18
|
+
"type": "bar",
|
|
19
|
+
"datasetId": "<preview-dataset-id>",
|
|
20
|
+
"x": "region",
|
|
21
|
+
"y": "amount"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"tables": [
|
|
25
|
+
{
|
|
26
|
+
"id": "sales-details",
|
|
27
|
+
"title": "销售明细 · 演示数据",
|
|
28
|
+
"datasetId": "<preview-dataset-id>",
|
|
29
|
+
"fields": [
|
|
30
|
+
"region",
|
|
31
|
+
"amount"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"path": "live-dashboard-examples/regional-sales/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": "region",
|
|
13
|
+
"path": [
|
|
14
|
+
"region"
|
|
15
|
+
],
|
|
16
|
+
"label": "区域",
|
|
17
|
+
"description": "Synthetic demonstration only. Unique daily dimension grain.",
|
|
18
|
+
"logicalType": "string",
|
|
19
|
+
"nullable": false,
|
|
20
|
+
"role": "dimension"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"key": "amount",
|
|
24
|
+
"path": [
|
|
25
|
+
"amount"
|
|
26
|
+
],
|
|
27
|
+
"label": "销售额",
|
|
28
|
+
"description": "Synthetic demonstration only. Whole CNY, no fractional currency.",
|
|
29
|
+
"logicalType": "int64",
|
|
30
|
+
"nullable": false,
|
|
31
|
+
"role": "measure",
|
|
32
|
+
"unit": "CNY"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"all": {
|
|
3
|
+
"amount": 50000
|
|
4
|
+
},
|
|
5
|
+
"filtered": {
|
|
6
|
+
"East": {
|
|
7
|
+
"amount": 12000
|
|
8
|
+
},
|
|
9
|
+
"West": {
|
|
10
|
+
"amount": 18000
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"checks": [
|
|
14
|
+
"All \u2192 first option \u2192 second option \u2192 all; chart, KPI and detail agree.",
|
|
15
|
+
"Refresh uses the current filter.",
|
|
16
|
+
"Empty results clear charts and show no-data; errors never show old totals as current.",
|
|
17
|
+
"Saved draft remains unpublished and belongs to current Project."
|
|
18
|
+
]
|
|
19
|
+
}
|
|
Binary file
|