@xpert-ai/plugin-live-dashboard 0.3.0 → 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 +70 -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 +14 -1
- 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 +2 -0
- package/dist/lib/assistant-template.js +7 -0
- 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 +48 -20
- package/dist/lib/monitor.d.ts +70 -0
- package/dist/lib/monitor.js +101 -0
- package/dist/lib/plugin.js +5 -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/prompt-workflows.d.ts +3 -2
- package/dist/lib/prompt-workflows.js +17 -3
- package/dist/lib/provider-options.controller.d.ts +10 -0
- package/dist/lib/provider-options.controller.js +49 -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 +118 -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 +25 -18
- package/scripts/import-prompts.mjs +108 -0
- 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,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import { realpathSync } from 'node:fs'
|
|
4
|
+
import { pathToFileURL } from 'node:url'
|
|
5
|
+
import { parseArgs } from 'node:util'
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url)
|
|
8
|
+
const { dashboardWorkspacePrompts } = require('../dist/lib/prompt-workflows.js')
|
|
9
|
+
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
10
|
+
|
|
11
|
+
/** Create-only import: no Assistant edits, broad associations, replacement or automatic publication. */
|
|
12
|
+
export async function importPrompts({ apiUrl, organizationId, assistantId, token, apply = false, request = fetch, log = console.log }) {
|
|
13
|
+
const base = new URL(apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`)
|
|
14
|
+
if (base.username || base.password || base.search || base.hash ||
|
|
15
|
+
!['http:', 'https:'].includes(base.protocol)) throw new Error('Use an HTTP(S) API base URL without credentials, query or fragment.')
|
|
16
|
+
if (base.protocol === 'http:' && !['localhost', '127.0.0.1', '[::1]'].includes(base.hostname))
|
|
17
|
+
throw new Error('Use HTTPS for a remote API. TLS certificate verification must remain enabled.')
|
|
18
|
+
if (!uuid.test(organizationId) || !uuid.test(assistantId)) throw new Error('Organization and Assistant IDs must be UUIDs.')
|
|
19
|
+
const bearer = token?.trim().replace(/^Bearer\s+/i, '')
|
|
20
|
+
if (!bearer) throw new Error('Set XPERT_TOKEN to a platform login token.')
|
|
21
|
+
const headers = { Authorization: `Bearer ${bearer}`, 'organization-id': organizationId, 'Content-Type': 'application/json' }
|
|
22
|
+
async function api(path, body) {
|
|
23
|
+
const response = await request(new URL(path, base), {
|
|
24
|
+
method: body ? 'POST' : 'GET', headers, redirect: 'error', signal: AbortSignal.timeout(30000),
|
|
25
|
+
...(body ? { body: JSON.stringify(body) } : {})
|
|
26
|
+
})
|
|
27
|
+
if (!response.ok) throw new Error(`API ${body ? 'POST' : 'GET'} failed (${response.status}). No existing data was overwritten. Rerun to inspect partial progress.`)
|
|
28
|
+
return response.json()
|
|
29
|
+
}
|
|
30
|
+
const assistant = await api(`xpert/${assistantId}`)
|
|
31
|
+
if (assistant?.id !== assistantId || !uuid.test(assistant.workspaceId ?? '') ||
|
|
32
|
+
assistant.organizationId !== organizationId || assistant.latest === false)
|
|
33
|
+
throw new Error('The Assistant must be the current version in the selected organization and an authoring workspace.')
|
|
34
|
+
const nodes = assistant.graph?.nodes
|
|
35
|
+
if (!Array.isArray(nodes)) throw new Error('The Assistant has no published graph. Configure and publish LiveDashboard first.')
|
|
36
|
+
const middlewares = nodes.filter(node => node?.type === 'workflow' && node.entity?.type === 'middleware' && node.entity.provider === 'LiveDashboard')
|
|
37
|
+
// Required middleware is always active, so prompts need no stale per-Assistant node keys.
|
|
38
|
+
if (middlewares.length !== 1 || middlewares[0].entity.required !== true)
|
|
39
|
+
throw new Error('Configure one LiveDashboard middleware as required (System middleware), publish, then rerun.')
|
|
40
|
+
async function runtimeCommands() {
|
|
41
|
+
const runtime = await api(`xpert/${assistantId}/runtime-capabilities`)
|
|
42
|
+
if (!Array.isArray(runtime?.commands)) throw new Error('Unexpected runtime command response; import stopped.')
|
|
43
|
+
return runtime.commands
|
|
44
|
+
}
|
|
45
|
+
const commands = await runtimeCommands()
|
|
46
|
+
if (!commands.some(command => command.source?.type === 'middleware' && command.source.provider === 'LiveDashboard' && command.source.nodeKey === middlewares[0].key))
|
|
47
|
+
throw new Error('Published LiveDashboard commands are unavailable. Connect the middleware to the main Agent and load the updated plugin first.')
|
|
48
|
+
function available(workflow, existing, catalog) {
|
|
49
|
+
return catalog.some(command => command.name === workflow.name && command.kind === 'prompt_workflow' &&
|
|
50
|
+
command.source?.type === 'workspace_prompt_workflow' && command.source.workflowId === existing.id &&
|
|
51
|
+
command.action?.type === 'insert_text' && command.action.template === workflow.template)
|
|
52
|
+
}
|
|
53
|
+
const workspace = encodeURIComponent(assistant.workspaceId)
|
|
54
|
+
const creates = []
|
|
55
|
+
const skipped = []
|
|
56
|
+
const conflicts = []
|
|
57
|
+
for (const workflow of dashboardWorkspacePrompts) {
|
|
58
|
+
const data = encodeURIComponent(JSON.stringify({ where: { name: workflow.name }, take: 2 }))
|
|
59
|
+
const page = await api(`prompt-workflow/by-workspace/${workspace}?data=${data}`)
|
|
60
|
+
if (!Array.isArray(page?.items) || page.total !== page.items.length || page.total > 1 ||
|
|
61
|
+
page.items.some(item => item?.name !== workflow.name)) throw new Error('Unexpected prompt lookup result; import stopped.')
|
|
62
|
+
const existing = page.items[0]
|
|
63
|
+
if (existing) {
|
|
64
|
+
const applicable = !existing.associatedXpertIds?.length || existing.associatedXpertIds.includes(assistantId)
|
|
65
|
+
if (existing.archivedAt || existing.actionType === 'insert_invocation' || existing.actionType === 'submit_prompt' ||
|
|
66
|
+
existing.template !== workflow.template || !applicable || !available(workflow, existing, commands)) conflicts.push(workflow.name)
|
|
67
|
+
else skipped.push(workflow.name)
|
|
68
|
+
} else {
|
|
69
|
+
if (commands.some(command => command.name === workflow.name)) conflicts.push(workflow.name)
|
|
70
|
+
creates.push({ ...workflow, associatedXpertIds: [assistantId] })
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (conflicts.length) throw new Error(`Resolve existing prompts in the workspace prompt library before importing (no writes made): ${[...new Set(conflicts)].join(', ')}. Associate the existing prompt with this Assistant if appropriate; user edits and archived prompts are preserved.`)
|
|
74
|
+
log(`${apply ? 'Apply' : 'Preview'}: create ${creates.length}, already available ${skipped.length}. Assistant ${assistantId}.`)
|
|
75
|
+
const created = []
|
|
76
|
+
const savedWorkflows = []
|
|
77
|
+
if (apply) for (const body of creates) {
|
|
78
|
+
const saved = await api(`prompt-workflow/workspace/${workspace}`, body)
|
|
79
|
+
if (!uuid.test(saved?.id ?? '') || saved.name !== body.name ||
|
|
80
|
+
saved.template !== body.template || !saved.associatedXpertIds?.includes(assistantId))
|
|
81
|
+
throw new Error(`Creation response did not confirm ${body.name}; inspect the prompt library before retrying.`)
|
|
82
|
+
created.push(body.name)
|
|
83
|
+
savedWorkflows.push(saved)
|
|
84
|
+
log(`Created ${body.name}`)
|
|
85
|
+
}
|
|
86
|
+
if (savedWorkflows.length) {
|
|
87
|
+
const updated = await runtimeCommands()
|
|
88
|
+
if (savedWorkflows.some(workflow => !available(workflow, workflow, updated)))
|
|
89
|
+
throw new Error('Prompts were created but runtime shortcuts are not confirmed. Inspect the Assistant command settings; existing data was not overwritten.')
|
|
90
|
+
log('Created shortcuts confirmed by the published Assistant runtime.')
|
|
91
|
+
}
|
|
92
|
+
return { planned: creates.map(item => item.name), created, skipped }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
96
|
+
try {
|
|
97
|
+
const { values } = parseArgs({ options: {
|
|
98
|
+
'api-url': { type: 'string' }, 'organization-id': { type: 'string' },
|
|
99
|
+
'assistant-id': { type: 'string' }, apply: { type: 'boolean', default: false }, help: { type: 'boolean' }
|
|
100
|
+
} })
|
|
101
|
+
if (values.help) console.log('Set XPERT_TOKEN, then run import-prompts.mjs --api-url https://host/api --organization-id UUID --assistant-id UUID [--apply]. Default: read-only preview. Requires Node 20+.')
|
|
102
|
+
else if (!values['api-url'] || !values['organization-id'] || !values['assistant-id']) throw new Error('Required: --api-url, --organization-id, --assistant-id. See --help.')
|
|
103
|
+
else await importPrompts({ apiUrl: values['api-url'], organizationId: values['organization-id'], assistantId: values['assistant-id'], token: process.env.XPERT_TOKEN, apply: values.apply })
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(error instanceof Error ? error.message : 'Prompt import failed.')
|
|
106
|
+
process.exitCode = 1
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -7,9 +7,18 @@ description: Create and continuously edit saved live dashboards, including free-
|
|
|
7
7
|
|
|
8
8
|
Use the platform SandboxFile tools to author source files. Use the dashboard tools to validate and save the business document. This skill belongs to the Live Dashboard plugin; data-source-specific query formats remain owned by each provider.
|
|
9
9
|
|
|
10
|
+
## Quick start and examples
|
|
11
|
+
|
|
12
|
+
For a first dashboard or a request for examples, read [examples/catalog.json](examples/catalog.json), [the shared workflow](examples/README.md), and only the selected recipe. Offer regional-sales (standard chart), sales-overview (HTML/ECharts), or production-quality (HTML/ECharts). Keep all files and drafts in the currently selected ChatKit Project. Explicit demo requests use labeled synthetic fixtures through a real registered file.table source; otherwise establish demo versus real data before writing. Never silently fill real-data gaps with sample values. Reuse existing authoring and draft tools; sample updates never overwrite existing dashboards.
|
|
13
|
+
|
|
14
|
+
For enterprise BI, offer [Superstore](examples/superstore/README.md): three full dashboards over the authorized **published Data Xpert SuperstoreOrders model**. This recipe uses `data-xpert.ontology`, not the file workflow above. Keep its fictional teaching-data label. Do not read the entire orders.csv into model context; follow its administrator setup and source discovery. Missing authorization/model is a setup gap, never permission to substitute another sample. Preserve its independent backend totals for distinct orders/customers.
|
|
15
|
+
|
|
16
|
+
When `dashboard_open` or `dashboard_history` returns `selection_required`, display its message and wait for the user to select a dashboard. This is a normal empty-selection state: do not retry, restore a target from conversation history, or create a replacement.
|
|
17
|
+
|
|
10
18
|
## Avoid preventable retries
|
|
11
19
|
|
|
12
20
|
- New independent dashboards use an unused source-file path. Inspect the target directory when needed; do not overwrite an existing candidate or switch to editing a historical Artifact merely because its title or filename matches.
|
|
21
|
+
- Continue the selected or checkpointed Artifact across turns. Explicit Workbench selection and New override the checkpoint; never infer identity from title or recency. If a target exists, new/copy creation requires the user's explicit request and `creationIntent: "new"` or `"copy"`.
|
|
13
22
|
- A successful save receipt already confirms persistence. Opening or reading the saved dashboard only prepares an unchanged copy; it does not require another save. Update only for an actual requested change or an observed defect. Never save an unchanged copy merely to verify that the previous save worked.
|
|
14
23
|
|
|
15
24
|
- After preview, execute every `validation.checks[].suggestedCall` before writing HTML. These suggestions use observed rows; only a successful `dashboard_test_filter` is evidence. If no value is suggested, inspect the source for a real value. A changed Binding needs fresh evidence.
|
|
@@ -28,10 +37,25 @@ Use the installed SKILL.md path shown in the skill catalog with `read_skill_file
|
|
|
28
37
|
|
|
29
38
|
Read [data-validation.md](references/data-validation.md) when creating a dashboard or changing its data/Binding. Read [html-runtime.md](references/html-runtime.md) and its lifecycle example when authoring or changing HTML/JavaScript. Style-only edits preserve the saved Bindings and do not need to reconstruct upstream queries.
|
|
30
39
|
|
|
40
|
+
## Reusable visual themes
|
|
41
|
+
|
|
42
|
+
Read the [style selection guide](themes/README.md) before choosing a visual system.
|
|
43
|
+
Trend Workspace is the pale-blue trend overview; Profit Atlas is the dark
|
|
44
|
+
profitability desk; Customer Brief is the warm editorial customer report. They
|
|
45
|
+
have different layouts and chart emphasis, not just different colors. Honor the
|
|
46
|
+
user's choice and preserve an existing theme unless redesign is requested.
|
|
47
|
+
Read the selected theme's guide and complete example, then author and save the
|
|
48
|
+
business page yourself using its installed components and exactly one pinned theme
|
|
49
|
+
library with ECharts. Do not depend on a developer, repository checkout, screenshot
|
|
50
|
+
or an already-created Artifact.
|
|
51
|
+
Use the complete Superstore pages as worked examples, not as a substitute for
|
|
52
|
+
discovering another dataset's fields and validating its metrics. For style-only
|
|
53
|
+
upgrades, follow the theme's existing-Artifact procedure and keep saved bindings.
|
|
54
|
+
|
|
31
55
|
## One file editing interface
|
|
32
56
|
|
|
33
57
|
- Create a new file with `sandbox_write_file({"file_path":"dashboards/sales.html","content":"<!doctype html>..."})`.
|
|
34
|
-
- Read an existing file with `sandbox_read_file({"file_path":"..."})`.
|
|
58
|
+
- Read an existing file with `sandbox_read_file({"file_path":"..."})`. If a long HTML line is truncated, use `dashboard_read_html` with the opened `editId` and follow `nextOffset` until `null` to inspect the saved source; do not repeat the same truncated read or overwrite the editing file with its preview. Apply exact replacements to the existing `htmlPath`.
|
|
35
59
|
- Change existing text with `sandbox_edit_file({"file_path":"...","old_string":"exact old text","new_string":"replacement"})`; use `sandbox_multi_edit_file` for multiple changes.
|
|
36
60
|
- `sandbox_write_file` does not overwrite existing files. Repair the current candidate with edits. If replacing an unsaved creation candidate with a new named file, include the earlier exact path in `supersedesPaths` on `dashboard_create_html` (or `dashboard_create` when choosing standard charts instead); only a successful save abandons that candidate, and files remain in Workspace. Do not abandon editing copies from `dashboard_open`. For a long new document, append subsequent complete segments with `sandbox_append_file` before saving the dashboard.
|
|
37
61
|
- Paths are relative to the current sandbox working directory. Use a real filename (`dashboards/sales.html`), never `.html`, whitespace-only names, absolute paths or guessed host paths. Copy returned paths exactly.
|
|
@@ -40,7 +64,7 @@ Read [data-validation.md](references/data-validation.md) when creating a dashboa
|
|
|
40
64
|
## Create a new dashboard
|
|
41
65
|
|
|
42
66
|
1. Call `dashboard_list_sources` before searching raw files, reading old scripts or creating transformations. For a named governed source, inspect that source/provider contract first; file-backed sources do not automatically require a script. Discover the authorized source and business schema. Validate the metric, period, grain, units and filters through real preview results; follow the data-validation reference.
|
|
43
|
-
2. For HTML mode, read the HTML runtime reference and call `dashboard_libraries` before using ECharts, Three.js or online dependencies such as Tailwind CDN.
|
|
67
|
+
2. For HTML mode, read the HTML runtime reference and call `dashboard_libraries` before using ECharts, Three.js or online dependencies such as Tailwind CDN. Network access is temporarily unrestricted for functional acceptance, including CDN scripts/styles/fonts, images and fetch/XHR. Browser CORS and mixed-content rules still apply. Online URLs are not entries in the packaged `libraries` array; business data must still use saved Bindings and credentials must never be embedded in HTML. Write a complete HTML page using SandboxFile. First declare `const live = window.xpertLiveArtifact` inside the page script; `live` is not a host global. Then register `live.subscribe(bindingId, renderDataset)`, await `live.ready()`, then call `live.query(bindingId, typedControls)`. There is no `window.subscribe` or `window.__datasets`. Shared filters must query every affected Binding, not just call `bindSelect` for the first one.
|
|
44
68
|
3. Call `dashboard_create_html` with `title`, the relative `path`, verified `previewIds`, matching `controls` and the complete required `libraries` list. Standard charts instead use `dashboard_create`.
|
|
45
69
|
4. Correct validation errors and retry the domain save. A file write, screenshot or `present_files` receipt does not save a live dashboard. Do not present the HTML as a static deliverable.
|
|
46
70
|
5. Check the returned `dashboard.artifactId`, `dashboard.status: "draft"` and `dashboard.draftRevision`. The successful domain receipt opens/refreshes the Workbench preview.
|
|
@@ -49,7 +73,7 @@ Read [data-validation.md](references/data-validation.md) when creating a dashboa
|
|
|
49
73
|
|
|
50
74
|
1. Call `dashboard_open({})` for the selected Workbench dashboard, or provide an exact Artifact ID from `dashboard_list`. Do not choose the most recent dashboard by guesswork or create a replacement Artifact.
|
|
51
75
|
2. Inspect `presentation.type`. For a standard dashboard, update definition fields directly; a rename needs only `title`, `editId`, `expectedRevision` and `expectedCurrentVersionId`. No HTML file exists: omit `htmlPath` and `libraries`. For a current HTML dashboard, the result includes a new `htmlPath` editing copy of the latest saved draft, plus `editId`, `expectedRevision` and `expectedCurrentVersionId`. Read this copy with SandboxFile before editing. Each open prepares a separate copy, preserving any uncommitted earlier files.
|
|
52
|
-
3. For HTML pages, apply requested changes with SandboxFile edits. Preserve data semantics, controls, query/subscription lifecycle and library locks unless the request changes them.
|
|
76
|
+
3. For HTML pages, apply requested changes with SandboxFile edits. Preserve data semantics, controls, query/subscription lifecycle, existing element IDs and library locks unless the request changes them. Assign stable IDs to new KPI/chart/table/filter components. Standard arrays and preview Bindings upsert by stable ID: omitted components remain. Use `removals` with exact binding/control/view/HTML element IDs only for user-requested deletions. HTML ID preservation checks deletion, not arbitrary semantic equivalence.
|
|
53
77
|
4. Automatically call `dashboard_update({"editId":"<returned ID>","expectedRevision":<returned revision>,"expectedCurrentVersionId":<returned UUID or null>,"title":"<new title if requested>"})` with the changed definition fields. When saving edited HTML, also supply the exact `htmlPath` of the editing file. Omit unchanged fields. Updating the visible heading does not rename the saved dashboard; pass `title` as well when renaming.
|
|
54
78
|
5. Check the saved draft receipt; for HTML file updates also check its `sourceFile` path/hash. For another batch, reopen the latest draft and use its new revisions. If the save fails, correct the definition or editing file and retry; do not claim success. If a revision conflict occurs, keep the edited file, open/read the latest state and reconcile before saving.
|
|
55
79
|
6. Historical `versionId` reads do not prepare an editable copy. Use `dashboard_read_html` to inspect historical source, then open the current draft to apply an intentional change.
|
|
@@ -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
|