@changfenhuang/dsh-genui 0.9.1
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/CHANGELOG.md +367 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/README.zh-CN.md +261 -0
- package/SKILL.md +115 -0
- package/cordis.patch.yml +9 -0
- package/demo-prompts.md +118 -0
- package/lib/assets/echarts.js +64 -0
- package/lib/assets/mermaid.js +3591 -0
- package/lib/assets/three.js +3844 -0
- package/lib/client.js +5 -0
- package/lib/index.js +1895 -0
- package/lib/invariant.js +19 -0
- package/lib/types/client/EChartNode.d.ts +4 -0
- package/lib/types/client/ErrorBoundary.d.ts +30 -0
- package/lib/types/client/GenuiBlock.d.ts +7 -0
- package/lib/types/client/PlotBlock.d.ts +44 -0
- package/lib/types/client/action-context.d.ts +7 -0
- package/lib/types/client/asset-echarts.d.ts +15 -0
- package/lib/types/client/asset-loader.d.ts +31 -0
- package/lib/types/client/asset-mermaid.d.ts +1 -0
- package/lib/types/client/asset-three.d.ts +1 -0
- package/lib/types/client/blocks/advanced.d.ts +104 -0
- package/lib/types/client/blocks/basic.d.ts +27 -0
- package/lib/types/client/blocks/charts.d.ts +38 -0
- package/lib/types/client/blocks/diagram/geometry.d.ts +73 -0
- package/lib/types/client/blocks/diagram/index.d.ts +5 -0
- package/lib/types/client/blocks/diagram/layout.d.ts +41 -0
- package/lib/types/client/blocks/diagram/theme.d.ts +35 -0
- package/lib/types/client/blocks/forms.d.ts +83 -0
- package/lib/types/client/blocks/render-node.d.ts +10 -0
- package/lib/types/client/blocks/state.d.ts +58 -0
- package/lib/types/client/dom-fence.d.ts +14 -0
- package/lib/types/client/echarts-lazy.d.ts +17 -0
- package/lib/types/client/fence-render.d.ts +58 -0
- package/lib/types/client/gallery.d.ts +9 -0
- package/lib/types/client/guard.d.ts +126 -0
- package/lib/types/client/index.d.ts +36 -0
- package/lib/types/client/interaction-store.d.ts +43 -0
- package/lib/types/client/mermaid-core.d.ts +8 -0
- package/lib/types/client/mermaid-lazy.d.ts +9 -0
- package/lib/types/client/mermaid-safe.d.ts +52 -0
- package/lib/types/client/panel-command.d.ts +29 -0
- package/lib/types/client/panel-store.d.ts +94 -0
- package/lib/types/client/panel.d.ts +15 -0
- package/lib/types/client/parse-partial.d.ts +37 -0
- package/lib/types/client/safe-math.d.ts +43 -0
- package/lib/types/client/scene3d-core.d.ts +21 -0
- package/lib/types/client/scene3d-lazy.d.ts +8 -0
- package/lib/types/client/spec.d.ts +610 -0
- package/lib/types/client/toolview.d.ts +7 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/plugin/index.d.ts +25 -0
- package/lib/types/plugin/invariant.d.ts +15 -0
- package/lib/types/plugin/tool.d.ts +27 -0
- package/lib/types/shared/fence-repair.d.ts +67 -0
- package/package.json +121 -0
- package/src/client/EChartNode.tsx +242 -0
- package/src/client/ErrorBoundary.tsx +66 -0
- package/src/client/GenuiBlock.module.css +1263 -0
- package/src/client/GenuiBlock.tsx +166 -0
- package/src/client/PlotBlock.module.css +155 -0
- package/src/client/PlotBlock.tsx +431 -0
- package/src/client/action-context.ts +32 -0
- package/src/client/asset-echarts.ts +25 -0
- package/src/client/asset-loader.ts +79 -0
- package/src/client/asset-mermaid.ts +13 -0
- package/src/client/asset-three.ts +12 -0
- package/src/client/blocks/advanced.tsx +435 -0
- package/src/client/blocks/basic.tsx +116 -0
- package/src/client/blocks/charts.tsx +319 -0
- package/src/client/blocks/diagram/geometry.ts +151 -0
- package/src/client/blocks/diagram/index.tsx +284 -0
- package/src/client/blocks/diagram/layout.ts +199 -0
- package/src/client/blocks/diagram/theme.ts +108 -0
- package/src/client/blocks/forms.tsx +523 -0
- package/src/client/blocks/render-node.tsx +260 -0
- package/src/client/blocks/state.ts +61 -0
- package/src/client/dom-fence.tsx +641 -0
- package/src/client/echarts-lazy.ts +35 -0
- package/src/client/fence-render.tsx +230 -0
- package/src/client/gallery.ts +128 -0
- package/src/client/guard.ts +1450 -0
- package/src/client/index.tsx +195 -0
- package/src/client/interaction-store.ts +122 -0
- package/src/client/mermaid-core.ts +121 -0
- package/src/client/mermaid-lazy.ts +31 -0
- package/src/client/mermaid-safe.ts +122 -0
- package/src/client/panel-command.ts +119 -0
- package/src/client/panel-store.ts +471 -0
- package/src/client/panel.tsx +185 -0
- package/src/client/parse-partial.ts +162 -0
- package/src/client/safe-math.ts +270 -0
- package/src/client/scene3d-core.ts +192 -0
- package/src/client/scene3d-lazy.ts +26 -0
- package/src/client/spec.ts +767 -0
- package/src/client/toolview.tsx +66 -0
- package/src/css-modules.d.ts +5 -0
- package/src/index.ts +12 -0
- package/src/plugin/index.ts +173 -0
- package/src/plugin/invariant.ts +30 -0
- package/src/plugin/node-shims.d.ts +31 -0
- package/src/plugin/tool.ts +330 -0
- package/src/shared/fence-repair.ts +244 -0
- package/tsconfig.json +35 -0
- package/tsdown.config.ts +193 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Root } from 'react-dom/client';
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client';
|
|
4
|
+
/** Override the React root factory (tests / tuning). */
|
|
5
|
+
export declare function setDomRootFactory(factory: (container: HTMLElement) => Root): void;
|
|
6
|
+
/**
|
|
7
|
+
* Install the DOM render channel. Returns a disposer that restores every
|
|
8
|
+
* taken-over block and disconnects the observers.
|
|
9
|
+
*
|
|
10
|
+
* @param ctx - the client context (sessions service for the current session).
|
|
11
|
+
* @param sendAction - plugin-owned relay: (sessionId, action, payload) → the
|
|
12
|
+
* scoped conversation send carrying the `[genui-action]` prompt.
|
|
13
|
+
*/
|
|
14
|
+
export declare function installDomFenceRenderer(ctx: Context, sendAction: (sessionId: SessionId, action: string, payload: Record<string, unknown>) => void): () => void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** The ECharts instance surface (the subset the component uses). */
|
|
2
|
+
export interface EChartsInstance {
|
|
3
|
+
setOption: (opt: unknown, notMerge?: boolean) => void;
|
|
4
|
+
resize: () => void;
|
|
5
|
+
dispose: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Create an ECharts instance on `el` with the given option (engine loaded on
|
|
9
|
+
* demand). The caller owns the returned instance and must dispose it.
|
|
10
|
+
* @param el - the DOM node to host the chart canvas.
|
|
11
|
+
* @param option - the ECharts option object.
|
|
12
|
+
* @param opts - optional height override.
|
|
13
|
+
* @returns the ECharts instance (setOption/resize/dispose).
|
|
14
|
+
*/
|
|
15
|
+
export declare function createChart(el: HTMLElement, option: unknown, opts?: {
|
|
16
|
+
height?: number;
|
|
17
|
+
}): Promise<EChartsInstance>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dsh-ui fence render pipeline, shared by both render channels:
|
|
3
|
+
*
|
|
4
|
+
* - **Registry channel** (contract hosts): the host's MarkdownText resolves
|
|
5
|
+
* ```dsh-ui fences through the fence-registry extension point and calls
|
|
6
|
+
* {@link renderGenuiFence} with a session-scoped context (sessionId + the
|
|
7
|
+
* settled source identity). An unrepairable body renders {@link FenceFallback}.
|
|
8
|
+
* - **DOM channel** (pristine hosts, no extension point): the DOM observer
|
|
9
|
+
* (`dom-fence.ts`) finds stock code blocks labelled `dsh-ui` and mounts
|
|
10
|
+
* {@link renderResolvedFenceNode} into its own React root, wrapped in the
|
|
11
|
+
* plugin-owned action context. An unrepairable body returns `null` so the
|
|
12
|
+
* stock code block stays visible.
|
|
13
|
+
*
|
|
14
|
+
* Structural types are declared locally on purpose: the context contract is
|
|
15
|
+
* a data shape, and pristine hosts do not export the host-side type names.
|
|
16
|
+
*/
|
|
17
|
+
import { type Key, type ReactNode } from 'react';
|
|
18
|
+
import type { GenuiSpec } from './spec.ts';
|
|
19
|
+
/** Settled fence source identity (data shape, host-independent). */
|
|
20
|
+
export interface GenuiFenceSource {
|
|
21
|
+
/** Stable structural id, e.g. `['assistant', seq, block, fence]` or `dom:<anchor>:<i>`. */
|
|
22
|
+
readonly id: string;
|
|
23
|
+
/** Three-part order: [messageSeq, textBlockIndex, fenceIndex]. */
|
|
24
|
+
readonly order: readonly [number, number, number];
|
|
25
|
+
}
|
|
26
|
+
/** Context a fence renderer receives beside the raw source and React key. */
|
|
27
|
+
export interface GenuiFenceContext {
|
|
28
|
+
/** Owning session route; absent outside a session-scoped render. */
|
|
29
|
+
readonly sessionId?: string;
|
|
30
|
+
/** Present only for settled/interrupted renders with a stable identity. */
|
|
31
|
+
readonly source?: GenuiFenceSource;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a raw fence body to a guarded spec.
|
|
35
|
+
*
|
|
36
|
+
* - Tier-1 repair (quote escape + trailing commas): safe at any time —
|
|
37
|
+
* adopted only when the whole body parses, so a still-growing streaming
|
|
38
|
+
* half keeps falling back to the code block, never flashing a banner.
|
|
39
|
+
* - Tier-2 completion (missing quotes/brackets): settled renders only —
|
|
40
|
+
* `context.source` exists exclusively once the message finished, so
|
|
41
|
+
* streaming halves are never completed early.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveGenuiSpec(raw: string, context?: GenuiFenceContext): GenuiSpec | null;
|
|
44
|
+
/**
|
|
45
|
+
* The resolved fence render for the DOM channel: `null` when the body is
|
|
46
|
+
* unrepairable (the stock code block stays visible), otherwise the panel
|
|
47
|
+
* publisher (`panel:true`; renders nothing in the flow — mounted as an empty
|
|
48
|
+
* root so the taken-over block is hidden) or the inline GenuiBlock tree.
|
|
49
|
+
* Shared verbatim by both channels.
|
|
50
|
+
*/
|
|
51
|
+
export declare function renderResolvedFenceNode(raw: string, key: Key, context?: GenuiFenceContext): ReactNode | null;
|
|
52
|
+
/**
|
|
53
|
+
* Registry-channel fence renderer (contract hosts): like the resolved node,
|
|
54
|
+
* but an unrepairable body renders the fallback code block + settled
|
|
55
|
+
* diagnostic — the host replaced its own block with our output — and an
|
|
56
|
+
* unpublishable `panel:true` fence renders `null` (nothing in the flow).
|
|
57
|
+
*/
|
|
58
|
+
export declare function renderGenuiFence(raw: string, key: Key, context?: GenuiFenceContext): ReactNode;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenUI gallery: one spec exercising every white-listed node type, used as
|
|
3
|
+
* the canonical full-vocabulary sample (tests render it and assert every
|
|
4
|
+
* component family appears; docs and demos reuse it as the "show everything"
|
|
5
|
+
* fence).
|
|
6
|
+
*/
|
|
7
|
+
import type { GenuiSpec } from './spec.ts';
|
|
8
|
+
/** A single spec covering all 40 node types in the vocabulary. */
|
|
9
|
+
export declare const gallerySpec: GenuiSpec;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenUI spec guard: resource limits, structural validation, and deterministic
|
|
3
|
+
* repair for ```dsh-ui fence specs.
|
|
4
|
+
*
|
|
5
|
+
* The renderer path runs every fence body through `repairGenuiSpec` before
|
|
6
|
+
* rendering, so a pathological or hostile spec — deep nesting, thousands of
|
|
7
|
+
* nodes, oversized strings, out-of-range numbers — degrades gracefully instead
|
|
8
|
+
* of stalling the UI. Repair is deterministic and prefix-stable: a component
|
|
9
|
+
* that survives repair of a partial stream keeps its position when later
|
|
10
|
+
* chunks arrive, so streaming re-renders stay consistent.
|
|
11
|
+
*
|
|
12
|
+
* Policy:
|
|
13
|
+
* - Unknown node `type`s pass through untouched (plugin-registered custom
|
|
14
|
+
* components via `registerGenuiComponent` are opaque to this package).
|
|
15
|
+
* - Known types: required fields must have the right type or the node is
|
|
16
|
+
* dropped; numbers are clamped into range; strings truncated; arrays
|
|
17
|
+
* sliced to their caps; containers recursed with a depth budget.
|
|
18
|
+
* - The whole spec carries a node budget; once exhausted, remaining siblings
|
|
19
|
+
* are elided.
|
|
20
|
+
*/
|
|
21
|
+
import type { GenuiSpec } from './spec.ts';
|
|
22
|
+
/** Hard resource limits enforced by repair (and mirrored at render time). */
|
|
23
|
+
export declare const GENUI_LIMITS: {
|
|
24
|
+
/** Maximum nesting depth of the component tree. */
|
|
25
|
+
readonly maxDepth: 8;
|
|
26
|
+
/** Maximum total nodes across the whole spec. */
|
|
27
|
+
readonly maxNodes: 200;
|
|
28
|
+
/** Maximum length of any plain string field. */
|
|
29
|
+
readonly maxString: 2000;
|
|
30
|
+
/** Maximum length of a `code` body. */
|
|
31
|
+
readonly maxCode: 12000;
|
|
32
|
+
/** Maximum length of a mermaid source. */
|
|
33
|
+
readonly maxMermaid: 8000;
|
|
34
|
+
/** Maximum `grid` columns. */
|
|
35
|
+
readonly maxGridCols: 12;
|
|
36
|
+
/** Maximum `tabs` count. */
|
|
37
|
+
readonly maxTabs: 12;
|
|
38
|
+
/** Maximum `accordion` items. */
|
|
39
|
+
readonly maxAccordionItems: 24;
|
|
40
|
+
/** Maximum `list` items. */
|
|
41
|
+
readonly maxListItems: 50;
|
|
42
|
+
/** Maximum `select`/`radio` options. */
|
|
43
|
+
readonly maxOptions: 50;
|
|
44
|
+
/** Maximum `table` rows / columns. */
|
|
45
|
+
readonly maxTableRows: 50;
|
|
46
|
+
readonly maxTableCols: 12;
|
|
47
|
+
/** Maximum `chart` data points per series. */
|
|
48
|
+
readonly maxChartPoints: 60;
|
|
49
|
+
/** Maximum `plot` series and per-series parameters. */
|
|
50
|
+
readonly maxPlotSeries: 8;
|
|
51
|
+
readonly maxPlotParams: 6;
|
|
52
|
+
/** Maximum `scene3d` meshes. */
|
|
53
|
+
readonly maxMeshes: 5;
|
|
54
|
+
/** Maximum `quiz` options. */
|
|
55
|
+
readonly maxQuizOptions: 8;
|
|
56
|
+
/** Maximum `steps` / `timeline` / `breadcrumb` / `keyvalue` entries. */
|
|
57
|
+
readonly maxSteps: 24;
|
|
58
|
+
readonly maxTimelineItems: 24;
|
|
59
|
+
readonly maxBreadcrumbItems: 12;
|
|
60
|
+
readonly maxKeyValuePairs: 24;
|
|
61
|
+
/** Maximum `file-tree` nesting. */
|
|
62
|
+
readonly maxTreeDepth: 6;
|
|
63
|
+
/** Maximum `diagram` nodes / edges / zones / focal accents (editorial
|
|
64
|
+
* complexity budget, mirroring diagram-design's §7 limits). */
|
|
65
|
+
readonly maxDiagramNodes: 9;
|
|
66
|
+
readonly maxDiagramEdges: 12;
|
|
67
|
+
readonly maxDiagramZones: 3;
|
|
68
|
+
readonly maxDiagramFocal: 2;
|
|
69
|
+
readonly maxDiagramLabel: 14;
|
|
70
|
+
/** Maximum depth of an `echart` option object (prevents pathological nested
|
|
71
|
+
* ECharts configs from stalling the guard walk). */
|
|
72
|
+
readonly maxEChartOptionDepth: 10;
|
|
73
|
+
/** Maximum length of any single array inside an `echart` option (prevents
|
|
74
|
+
* a model from stalling rendering with `series.data` of hundreds of
|
|
75
|
+
* thousands of points). */
|
|
76
|
+
readonly maxEChartArrayLen: 500;
|
|
77
|
+
/** Maximum total entries (object keys + array elements) traversed while
|
|
78
|
+
* sanitizing an `echart` option. Bounds the walk so a pathologically
|
|
79
|
+
* large option object cannot stall the guard. */
|
|
80
|
+
readonly maxEChartOptionNodes: 2000;
|
|
81
|
+
};
|
|
82
|
+
/** Result of `validateGenuiSpec`. */
|
|
83
|
+
export interface GenuiValidation {
|
|
84
|
+
ok: boolean;
|
|
85
|
+
/** Human-readable problems, empty when `ok`. */
|
|
86
|
+
errors: string[];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Deterministically repair a raw spec value into a renderable GenuiSpec.
|
|
90
|
+
* Returns null only when the root is not an object with an `items` array
|
|
91
|
+
* (a bare component root is wrapped into a col first — the documented fence
|
|
92
|
+
* vocabulary allows single-component bodies); every other defect is healed by
|
|
93
|
+
* dropping/clamping/truncating. Idempotent: repairing a repaired spec is a
|
|
94
|
+
* no-op.
|
|
95
|
+
*/
|
|
96
|
+
export declare function repairGenuiSpec(value: unknown): GenuiSpec | null;
|
|
97
|
+
/**
|
|
98
|
+
* Count the nodes of a spec tree (every item, descending into tabs /
|
|
99
|
+
* accordion / file-tree / list containers — the same descent
|
|
100
|
+
* `validateGenuiSpec` walks). Shared by the panel fold (node-budget gate)
|
|
101
|
+
* and validation, so the panel never runs a second, divergent traversal.
|
|
102
|
+
* `cap` bounds the walk for hostile inputs; the panel passes
|
|
103
|
+
* `PANEL_LIMITS.maxNodes + 1` to detect overflow without counting the whole
|
|
104
|
+
* tree.
|
|
105
|
+
*/
|
|
106
|
+
export declare function countGenuiNodes(value: unknown, cap?: number): number;
|
|
107
|
+
/** Every white-listed node `type`. Keep in sync with the repairNode switch —
|
|
108
|
+
* validate_dsh_ui uses it to tell declared GenUI nodes apart from unrelated
|
|
109
|
+
* `"type"` strings (e.g. file-tree's `{type:'file'}` children). */
|
|
110
|
+
export declare const GENUI_NODE_TYPES: ReadonlySet<string>;
|
|
111
|
+
/**
|
|
112
|
+
* Count DECLARED nodes in a raw spec tree: objects whose `type` is a
|
|
113
|
+
* white-listed string, descending the same containers `countGenuiNodes`
|
|
114
|
+
* walks. `validate_dsh_ui` compares this with the repaired count to surface
|
|
115
|
+
* children the repair silently dropped (blank-render class of bugs, issue
|
|
116
|
+
* #42) instead of reporting a green check on a half-empty tree.
|
|
117
|
+
*/
|
|
118
|
+
export declare function countDeclaredGenuiNodes(value: unknown, cap?: number): number;
|
|
119
|
+
/**
|
|
120
|
+
* Validate a raw spec value against the white list and limits, collecting
|
|
121
|
+
* human-readable problems. Unlike repair this never mutates: it is a
|
|
122
|
+
* diagnostic for tests and tooling. Unknown `type`s are reported (a plugin
|
|
123
|
+
* custom type is valid only when a renderer is registered — the guard cannot
|
|
124
|
+
* know, so it flags them as warnings).
|
|
125
|
+
*/
|
|
126
|
+
export declare function validateGenuiSpec(value: unknown): GenuiValidation;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-genui browser half: the ```dsh-ui fence renderer, the keyed toolview
|
|
3
|
+
* for the `render_ui` tool, and the session panel dock.
|
|
4
|
+
*
|
|
5
|
+
* Fence rendering is dual-mode, chosen at boot:
|
|
6
|
+
* - **Registry channel** (contract hosts): the host's MarkdownText resolves
|
|
7
|
+
* ```dsh-ui fences through the fence-registry extension point; this package
|
|
8
|
+
* registers `renderGenuiFence`. Action callbacks ride the host-installed
|
|
9
|
+
* GenuiActionContext.
|
|
10
|
+
* - **DOM channel** (pristine hosts): no extension point exists, so
|
|
11
|
+
* `dom-fence.ts` observes the conversation DOM, finds settled stock code
|
|
12
|
+
* blocks labelled `dsh-ui`, and mounts the same render pipeline in its own
|
|
13
|
+
* React roots — wrapped in the plugin-owned GenuiActionContext that relays
|
|
14
|
+
* `[genui-action]` through the scoped conversation send. Either way the
|
|
15
|
+
* deployment is the STOCK DSH snapshot plus this plugin.
|
|
16
|
+
*
|
|
17
|
+
* The renderer parses the fence body with the partial parser: while the reply
|
|
18
|
+
* streams, every FINISHED component appears the moment its JSON object
|
|
19
|
+
* closes, so the UI assembles top-down before the fence (or reply) completes.
|
|
20
|
+
* A body with no finished component yet falls back to a plain code block.
|
|
21
|
+
* @module @changfenhuang/dsh-genui/client
|
|
22
|
+
*/
|
|
23
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
24
|
+
import { renderGenuiFence } from './fence-render.tsx';
|
|
25
|
+
/** Add low-priority prefetch links for the lazy engine assets (mermaid/three).
|
|
26
|
+
* Browser-dependent: some engines ignore `<link rel=prefetch>`; harmless
|
|
27
|
+
* either way — the on-demand loader still covers a cache miss. Exported for
|
|
28
|
+
* tests. */
|
|
29
|
+
export declare function prefetchGenuiAssets(): void;
|
|
30
|
+
/** Cordis client entry: register the fence renderer on boot, the keyed
|
|
31
|
+
* toolview for the render_ui tool, and the session panel dock; returning the
|
|
32
|
+
* disposers lets cordis tear all registrations down on plugin unload. */
|
|
33
|
+
export declare function apply(ctx: Context): () => void;
|
|
34
|
+
export declare const inject: string[];
|
|
35
|
+
export { renderGenuiFence };
|
|
36
|
+
export type { GenuiFenceContext } from './fence-render.tsx';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction-state store: durable per-block GenUI interaction state
|
|
3
|
+
* (radio answers, submit lock, input/textarea values) in localStorage.
|
|
4
|
+
*
|
|
5
|
+
* LOCAL-FIRST persistence: a ```dsh-ui block's interactive state survives
|
|
6
|
+
* page refresh and session reopen because it is keyed by
|
|
7
|
+
* `session + block slot + content fingerprint` — replaying the same message
|
|
8
|
+
* (same content) restores the exact state, while NEW content (换题, edited
|
|
9
|
+
* spec) gets a fresh key and thus a clean slate. Different messages never
|
|
10
|
+
* share state because their content fingerprints differ.
|
|
11
|
+
*
|
|
12
|
+
* Bounded: at most MAX_BLOCKS entries, LRU-evicted on write; each block's
|
|
13
|
+
* payload is small (answers map + a few field values).
|
|
14
|
+
* @module @changfenhuang/dsh-genui/client/interaction-store
|
|
15
|
+
*/
|
|
16
|
+
/** Durable state of one UI block. */
|
|
17
|
+
export interface BlockInteractionState {
|
|
18
|
+
/** group → chosen option label (radio aggregation answers). */
|
|
19
|
+
answers?: Record<string, string>;
|
|
20
|
+
/** True after a local grading: the paper stays graded across refresh. */
|
|
21
|
+
locked?: boolean;
|
|
22
|
+
/** field id → current value (input/textarea with an `id`). */
|
|
23
|
+
fields?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
/** Load a block's durable state, or null when absent/corrupt. */
|
|
26
|
+
export declare function loadBlockState(stateKey: string): BlockInteractionState | null;
|
|
27
|
+
/** Save a block's durable state (LRU: touched keys move to the front). */
|
|
28
|
+
export declare function saveBlockState(stateKey: string, state: BlockInteractionState): void;
|
|
29
|
+
/** Forget a block's durable state (e.g. after a reset-to-empty). */
|
|
30
|
+
export declare function clearBlockState(stateKey: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Deterministic content fingerprint (djb2) for a block's raw fence body.
|
|
33
|
+
* Two render passes of the SAME content share a fingerprint (state restores);
|
|
34
|
+
* edited content gets a new one (fresh state). Not a security hash — the
|
|
35
|
+
* store only uses it for equality/identity.
|
|
36
|
+
*/
|
|
37
|
+
export declare function fingerprint(raw: string): string;
|
|
38
|
+
/** Build the durable state key for a fence block: session + fence slot + content fingerprint. */
|
|
39
|
+
export declare function fenceStateKey(sessionId: string, fenceKey: number | string, raw: string): string;
|
|
40
|
+
/** Build the durable state key for a panel publish (content-keyed). */
|
|
41
|
+
export declare function panelStateKey(sessionId: string, raw: string): string;
|
|
42
|
+
/** Build the durable state key for a render_ui tool card (call-keyed). */
|
|
43
|
+
export declare function toolStateKey(sessionId: string, callId: string): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render mermaid source to an SVG string.
|
|
3
|
+
* @param code - the mermaid diagram source.
|
|
4
|
+
* @returns the rendered SVG markup (verified free of script/event handlers).
|
|
5
|
+
* @throws when the kind is not whitelisted, rendering fails, or the output
|
|
6
|
+
* fails the sanitization check.
|
|
7
|
+
*/
|
|
8
|
+
export declare function renderMermaid(code: string): Promise<string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { assertSafeSvg, ensureFlowchartKind, repairMermaidSource } from './mermaid-safe.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Render mermaid source to an SVG string (engine loaded on demand).
|
|
4
|
+
* @param code - the mermaid diagram source.
|
|
5
|
+
* @returns the rendered SVG markup (verified free of script/event handlers).
|
|
6
|
+
* @throws when the asset cannot load, the kind is not whitelisted, rendering
|
|
7
|
+
* fails, or the output fails the sanitization check.
|
|
8
|
+
*/
|
|
9
|
+
export declare function renderMermaid(code: string): Promise<string>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure mermaid source utilities — no mermaid import, so this module can ship
|
|
3
|
+
* in the main client bundle while the heavy mermaid engine lives in a lazy
|
|
4
|
+
* asset bundle (`lib/assets/mermaid.js`, loaded on demand by mermaid-lazy).
|
|
5
|
+
* @module @changfenhuang/dsh-genui/client/mermaid-safe
|
|
6
|
+
*/
|
|
7
|
+
/** Throws when `svg` carries script, event-handler attributes, or
|
|
8
|
+
* `javascript:` URIs. Exported for tests; `renderMermaid` is the only caller
|
|
9
|
+
* in production. */
|
|
10
|
+
export declare function assertSafeSvg(svg: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Best-effort repair of common model-authored label mistakes in graph /
|
|
13
|
+
* flowchart sources, used only when the original fails to render:
|
|
14
|
+
* - drop backticks — lexically illegal in mermaid labels (models embed
|
|
15
|
+
* ```dsh-ui style fences, which break the parser with "Lexical error");
|
|
16
|
+
* - strip `<br/>` tags, which require htmlLabels (never enabled here);
|
|
17
|
+
* - quote unquoted node labels containing CJK, spaces, or other characters
|
|
18
|
+
* mermaid's bare-label grammar rejects (observed live: `A[模型生成 spec]`).
|
|
19
|
+
* Conservative by design: already-quoted labels, plain ASCII labels without
|
|
20
|
+
* spaces, and non-flowchart kinds are left untouched (apart from the
|
|
21
|
+
* backtick/`<br/>` sanitation above, which is harmless everywhere in a
|
|
22
|
+
* flowchart).
|
|
23
|
+
*
|
|
24
|
+
* Labeled-edge spans (`-- 文本 -->`, `== 文本 ==>`, `-. 文本 .->`) are free
|
|
25
|
+
* text and must never be quoted: wrapping them breaks the parser
|
|
26
|
+
* ("Expecting 'LINK' … got 'STR'"), observed live with
|
|
27
|
+
* `H -- 否(流式中) --> J`. Each span is swapped for a bracket-free
|
|
28
|
+
* placeholder before quoting and restored verbatim afterwards, so the quote
|
|
29
|
+
* pass can neither corrupt it nor be thrown off by inserted quotes
|
|
30
|
+
* elsewhere on the line.
|
|
31
|
+
*
|
|
32
|
+
* Pipe-style edge labels (`-->|文本|`) are the opposite: an UNQUOTED pipe
|
|
33
|
+
* label containing `[` or `]` breaks the parser ("Parse error", observed
|
|
34
|
+
* live with `-->|6. 用户交互 → [genui-action]|`) because the brackets are
|
|
35
|
+
* node grammar. Quoting the label text is display-neutral (mermaid renders
|
|
36
|
+
* `-->|"文本"|` without the quotes) and parses. These spans are masked too,
|
|
37
|
+
* so the node-label quote pass below cannot reach inside them and produce
|
|
38
|
+
* nested quotes, and restored last.
|
|
39
|
+
*/
|
|
40
|
+
export declare function repairMermaidSource(code: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Lenient repair for sources that omit the mandatory diagram-type
|
|
43
|
+
* declaration line — the #1 failure mode of model-generated mermaid (chat
|
|
44
|
+
* assistants routinely emit `A --> B` without the leading `graph TD`).
|
|
45
|
+
*
|
|
46
|
+
* When the first token is not an already-declared `graph`/`flowchart` kind
|
|
47
|
+
* but the body contains flowchart edge arrows, prepend `graph TD` so the
|
|
48
|
+
* diagram renders instead of tripping the kind gate. Anything else — an
|
|
49
|
+
* explicitly declared kind, a sequence/gantt/pie body (no flowchart edges),
|
|
50
|
+
* or plain text — is returned unchanged: we never guess at the diagram type.
|
|
51
|
+
*/
|
|
52
|
+
export declare function ensureFlowchartKind(code: string): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /panel slash command + the default panel spec.
|
|
3
|
+
*
|
|
4
|
+
* The session panel dock is an always-present seat, but it only becomes
|
|
5
|
+
* visible once a spec has been published — both existing publish paths
|
|
6
|
+
* (render_ui tool result, panel:true fence) are model-driven. The /panel
|
|
7
|
+
* command gives a deterministic, client-side entry point:
|
|
8
|
+
* - `/panel` — publishes the default spec and requests the dock to expand
|
|
9
|
+
* instantly, with zero model round-trip;
|
|
10
|
+
* - `/panel clear` (off/close) — empties the panel so the dock retracts;
|
|
11
|
+
* - `/panel <指令>` — shows the default spec for immediate feedback, then
|
|
12
|
+
* relays the instruction to the model, which replaces the panel with
|
|
13
|
+
* content tailored to the request (panel:true fence).
|
|
14
|
+
* Panel updates afterwards still flow through the model (say "更新面板" or
|
|
15
|
+
* re-run render_ui) or through another /panel.
|
|
16
|
+
*/
|
|
17
|
+
import type { InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
|
|
18
|
+
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client';
|
|
19
|
+
import type { GenuiSpec } from './spec.ts';
|
|
20
|
+
/** Default panel content published by `/panel`: the component overview. */
|
|
21
|
+
export declare const DEFAULT_PANEL_SPEC: GenuiSpec;
|
|
22
|
+
/**
|
|
23
|
+
* The /panel source. Menu group `genui` under the '/' trigger; the panel
|
|
24
|
+
* candidate claims the line so both the menu pick and a bare `/panel` enter
|
|
25
|
+
* resolve to the same command. `matchEnter` is implemented so the command
|
|
26
|
+
* works without opening the menu (leading-token adjudication), and it also
|
|
27
|
+
* catches `/panel clear` style args.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createPanelSlashSource(sendInstruction: (sessionId: SessionId, instruction: string) => void): InputTriggerSource;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session GenUI panel store: folds stable per-source panel operations into
|
|
3
|
+
* one read-only snapshot per session. The toolview and the settled-fence
|
|
4
|
+
* publisher submit operations (`replace`/`append`) carrying a stable
|
|
5
|
+
* sourceId and a three-part order; the store deduplicates by source, sorts
|
|
6
|
+
* by order, applies a deterministic fold under a node/append budget, and
|
|
7
|
+
* notifies subscribers only when the folded snapshot actually changes.
|
|
8
|
+
*
|
|
9
|
+
* Ordering: no "last caller wins", no Infinity. Operations from DIFFERENT
|
|
10
|
+
* sources always both apply; replays of the same source are idempotent; a
|
|
11
|
+
* later replace resets everything earlier; an append beyond the budget is
|
|
12
|
+
* remembered as an overflow barrier so an out-of-order earlier replace can
|
|
13
|
+
* re-fold deterministically. A local `/panel` override (default panel or
|
|
14
|
+
* clear) is the fold base and blocks every replay at/below the highest
|
|
15
|
+
* message seq seen so far — old history can never resurrect the panel.
|
|
16
|
+
*
|
|
17
|
+
* Scale limits live in `PANEL_LIMITS` (adjustable defaults, not law — see
|
|
18
|
+
* docs/plans/2026-08-12-dsh-genui-design-optimization.md). Budget
|
|
19
|
+
* rejections are DETERMINISTIC (no LRU eviction: eviction would make the
|
|
20
|
+
* fold depend on arrival order).
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
import type { GenuiSpec } from './spec.ts';
|
|
24
|
+
/** Panel scale limits — adjustable defaults (design doc: scale limits are
|
|
25
|
+
* configurable, not law). Folds read the CURRENT limits, so tuning applies
|
|
26
|
+
* from the next operation on. */
|
|
27
|
+
export declare const PANEL_LIMITS: {
|
|
28
|
+
/** Max nodes in the folded panel snapshot. */
|
|
29
|
+
readonly maxNodes: 200;
|
|
30
|
+
/** Max append operations kept after the latest replace. */
|
|
31
|
+
readonly maxAppends: 200;
|
|
32
|
+
};
|
|
33
|
+
type PanelLimits = {
|
|
34
|
+
maxNodes: number;
|
|
35
|
+
maxAppends: number;
|
|
36
|
+
};
|
|
37
|
+
/** Override the panel limits (operator tuning / tests). Applied at the next fold. */
|
|
38
|
+
export declare function setPanelLimits(next: Partial<PanelLimits>): void;
|
|
39
|
+
/** Three-part stable order: [messageSeq, textBlockIndex, fenceIndex]. */
|
|
40
|
+
export type PanelOrder = readonly [number, number, number];
|
|
41
|
+
/** One persistent panel operation from a settled source. */
|
|
42
|
+
export interface PanelOperation {
|
|
43
|
+
/** Stable source identity (fence source id or `['render_ui', callId]`). */
|
|
44
|
+
sourceId: string;
|
|
45
|
+
order: PanelOrder;
|
|
46
|
+
mode: 'replace' | 'append';
|
|
47
|
+
spec: GenuiSpec;
|
|
48
|
+
}
|
|
49
|
+
export type PanelOperationStatus = 'accepted' | 'idempotent' | 'blocked' | 'overflow';
|
|
50
|
+
/**
|
|
51
|
+
* Submit one panel operation. Idempotent per sourceId; ordering and budget
|
|
52
|
+
* per the module doc. Returns the outcome so callers can diagnose once per
|
|
53
|
+
* source (e.g. "panel at its node/appends budget — send a replace").
|
|
54
|
+
*/
|
|
55
|
+
export declare function applyPanelOperation(sessionId: string, op: PanelOperation): PanelOperationStatus;
|
|
56
|
+
/**
|
|
57
|
+
* Apply the local `/panel` override: `spec` (the default panel) or null
|
|
58
|
+
* (clear) becomes the fold base, and every operation at/below the highest
|
|
59
|
+
* message seq seen so far is dead — old history replays cannot resurrect
|
|
60
|
+
* the panel. A later real operation (order[0] > barrier) replaces or merges
|
|
61
|
+
* into the override as usual.
|
|
62
|
+
*/
|
|
63
|
+
export declare function setLocalPanel(sessionId: string, spec: GenuiSpec | null): void;
|
|
64
|
+
/** Log one budget-overflow diagnostic per source (replays stay silent). */
|
|
65
|
+
export declare function diagnosePanelBudget(sessionId: string, sourceId: string): void;
|
|
66
|
+
/** Tear down a session's panel state (session destroy / hard clear). Memory
|
|
67
|
+
* only: drops the in-memory record, the session's expand token, and overflow
|
|
68
|
+
* diagnostics, so a long-lived app never accumulates per-session state for
|
|
69
|
+
* closed sessions. The localStorage entry SURVIVES — reopening the session
|
|
70
|
+
* restores the panel instantly (that is the persistence feature); an explicit
|
|
71
|
+
* user clear goes through setLocalPanel(null), which persists the cleared
|
|
72
|
+
* state + barrier. */
|
|
73
|
+
export declare function clearSessionPanel(sessionId: string): void;
|
|
74
|
+
/** Current folded spec for a session (useSyncExternalStore getSnapshot).
|
|
75
|
+
* Lazily hydrates from localStorage on first access after a reload. */
|
|
76
|
+
export declare function getPanelSpec(sessionId: string): GenuiSpec | null;
|
|
77
|
+
/** Subscribe to panel changes. Returns the disposer. */
|
|
78
|
+
export declare function subscribePanel(listener: () => void): () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Merge an `append` spec into the current panel spec:
|
|
81
|
+
* - both sides single-tabs containers → merge BY TAB LABEL: items of
|
|
82
|
+
* same-labelled tabs are appended, new labels are added (order preserved);
|
|
83
|
+
* - otherwise → plain item lists are appended to the tail.
|
|
84
|
+
* The previous title wins unless it was absent. `next` is returned as-is when
|
|
85
|
+
* there is nothing to merge into. Export for tests.
|
|
86
|
+
*/
|
|
87
|
+
export declare function mergePanelSpecs(prev: GenuiSpec | null, next: GenuiSpec): GenuiSpec;
|
|
88
|
+
/** Request the panel dock to expand for a session (e.g. the /panel command). */
|
|
89
|
+
export declare function requestPanelExpand(sessionId: string): void;
|
|
90
|
+
/** Current expand token for a session (useSyncExternalStore getSnapshot). */
|
|
91
|
+
export declare function getPanelExpandToken(sessionId: string): number;
|
|
92
|
+
/** Subscribe to expand requests. Returns the disposer. */
|
|
93
|
+
export declare function subscribePanelExpand(listener: () => void): () => void;
|
|
94
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type GenuiActionHandler } from './action-context.ts';
|
|
2
|
+
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
+
/** Injection face built per session in apply (scoped conversation send). */
|
|
4
|
+
export interface GenuiPanelInjected {
|
|
5
|
+
sessionId: string;
|
|
6
|
+
sendGenuiAction: GenuiActionHandler;
|
|
7
|
+
}
|
|
8
|
+
export type GenuiPanelProps = PropsRuntime<'conversation.input.dock'> & GenuiPanelInjected;
|
|
9
|
+
/**
|
|
10
|
+
* Panel dock entry. Renders nothing until the session's toolview published a
|
|
11
|
+
* spec; afterwards the SAME block re-renders on every publish. Collapsed by
|
|
12
|
+
* default so the dock never steals the message flow's scroll room; the
|
|
13
|
+
* header always shows the current panel title.
|
|
14
|
+
*/
|
|
15
|
+
export declare function GenuiPanel({ sessionId, sendGenuiAction }: GenuiPanelProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type GenuiSpec } from './spec.ts';
|
|
2
|
+
/** Default repair-candidate budget (adjustable; see the design doc). */
|
|
3
|
+
export declare const MAX_PARTIAL_REPAIR_ATTEMPTS = 32;
|
|
4
|
+
/** Override the repair-candidate budget (tests / tuning). */
|
|
5
|
+
export declare function setMaxPartialRepairAttempts(n: number): void;
|
|
6
|
+
/** One repair candidate: a balanced prefix of the body ending at `end`,
|
|
7
|
+
* plus the closing brackets to append (empty when already balanced). */
|
|
8
|
+
export interface PartialCandidate {
|
|
9
|
+
end: number;
|
|
10
|
+
closingSuffix: string;
|
|
11
|
+
}
|
|
12
|
+
/** Single left-to-right pass over the raw body. Tracks the bracket stack
|
|
13
|
+
* (skipping strings/escapes correctly) and records:
|
|
14
|
+
* - every position where the prefix is fully balanced (trailing comma or
|
|
15
|
+
* fence tail) — candidate with an empty closing suffix;
|
|
16
|
+
* - every `}` object close whose remaining depth fits the spec budget —
|
|
17
|
+
* candidate with the remaining brackets closed (an unfinished trailing
|
|
18
|
+
* element).
|
|
19
|
+
* Candidates are ring-buffered to the attempts budget (the LAST pushes are
|
|
20
|
+
* the longest), then returned longest-first, deduplicated by end. The scan
|
|
21
|
+
* stops at the first unbalanced close — earlier balanced prefixes remain
|
|
22
|
+
* valid candidates.
|
|
23
|
+
*
|
|
24
|
+
* Exposed for tests (the `scannedChars` diagnostic); not exported from the
|
|
25
|
+
* package entry. The parser calls this exactly once per parse.
|
|
26
|
+
*/
|
|
27
|
+
export declare function collectPartialCandidates(raw: string): {
|
|
28
|
+
candidates: PartialCandidate[];
|
|
29
|
+
scannedChars: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Parse a possibly incomplete genui spec body.
|
|
33
|
+
* @param raw - the fence body as accumulated so far.
|
|
34
|
+
* @returns a spec containing only finished components, or null when nothing
|
|
35
|
+
* usable has been written yet.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parsePartialGenuiSpec(raw: string): GenuiSpec | null;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SafeMath: a restricted math expression evaluator for GenUI plot specs.
|
|
3
|
+
*
|
|
4
|
+
* The model supplies an expression string like "sin(x) * 2 + 1". It is parsed
|
|
5
|
+
* and evaluated here WITHOUT eval / new Function: a hand-written recursive
|
|
6
|
+
* descent parser over a white-listed function/constant vocabulary. Unknown
|
|
7
|
+
* identifiers, object property access, and call syntax other than the
|
|
8
|
+
* white-listed functions are parse errors, so an adversarial expression
|
|
9
|
+
* cannot reach globals, prototypes, or side effects.
|
|
10
|
+
*
|
|
11
|
+
* Supported grammar:
|
|
12
|
+
* expr := term (('+' | '-') term)*
|
|
13
|
+
* term := unary (('*' | '/' | '%') unary)*
|
|
14
|
+
* unary := ('-' | '+') unary | power
|
|
15
|
+
* power := atom ('^' atom)?
|
|
16
|
+
* atom := number | variable | constant | func '(' expr ')' | '(' expr ')'
|
|
17
|
+
* variable: 'x' (and any single-letter variable present in vars)
|
|
18
|
+
* constant: 'pi' | 'e' | 'tau'
|
|
19
|
+
* func : sin cos tan asin acos atan sqrt cbrt exp log ln abs floor ceil round min max pow
|
|
20
|
+
*/
|
|
21
|
+
export interface SafeMathOptions {
|
|
22
|
+
/** Variable values; only names present here may be referenced as variables. */
|
|
23
|
+
vars?: Record<string, number>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Compile an expression into an evaluable function.
|
|
27
|
+
* @param expr - the math expression. `x` is the sampling variable; any other
|
|
28
|
+
* name in `vars` is a parameter whose captured value the evaluator returns
|
|
29
|
+
* (recompile with new vars to change a parameter — expressions are short).
|
|
30
|
+
* @param options - parameter values; `x` is reserved and ignored here.
|
|
31
|
+
* @returns evaluator, or null when the expression is invalid.
|
|
32
|
+
*/
|
|
33
|
+
export declare function compileMathExpr(expr: string, options?: SafeMathOptions): ((x: number) => number) | null;
|
|
34
|
+
/**
|
|
35
|
+
* Sample an expression over a range.
|
|
36
|
+
* @param expr - math expression in x, with optional parameters.
|
|
37
|
+
* @param xMin - inclusive start.
|
|
38
|
+
* @param xMax - inclusive end.
|
|
39
|
+
* @param samples - number of samples (>= 2).
|
|
40
|
+
* @param params - parameter values (e.g. { a: 2, b: 3 }); recompile per change.
|
|
41
|
+
* @returns [x, y] pairs; non-finite samples are dropped.
|
|
42
|
+
*/
|
|
43
|
+
export declare function sampleExpr(expr: string, xMin: number, xMax: number, samples?: number, params?: Record<string, number>): Array<[number, number]>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* three.js scene renderer — the HEAVY half. This module imports three and is
|
|
3
|
+
* bundled ONLY into the lazy asset bundle `lib/assets/three.js`, loaded on
|
|
4
|
+
* demand by the scene3d-lazy loader when a spec contains a `scene3d` node;
|
|
5
|
+
* the main client bundle stays small.
|
|
6
|
+
*
|
|
7
|
+
* Security: the model only supplies white-listed primitive shapes, numeric
|
|
8
|
+
* transforms, and colors. No textures, no external URLs, no scripts, no
|
|
9
|
+
* custom shaders — everything is geometry + material colors constructed
|
|
10
|
+
* locally. The renderer mounts into a caller-owned container and returns a
|
|
11
|
+
* disposer that tears down the WebGL context.
|
|
12
|
+
* @module @changfenhuang/dsh-genui/client/scene3d-core
|
|
13
|
+
*/
|
|
14
|
+
import type { GenuiScene3D } from './spec.ts';
|
|
15
|
+
/**
|
|
16
|
+
* Mount a GenUI 3D scene into `container`.
|
|
17
|
+
* @param container - the DOM node to host the WebGL canvas.
|
|
18
|
+
* @param scene - the declarative scene spec.
|
|
19
|
+
* @returns a disposer that removes the renderer and its context.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mountScene(container: HTMLElement, scene: GenuiScene3D): Promise<() => void>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { GenuiScene3D } from './spec.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Mount a GenUI 3D scene into `container` (engine loaded on demand).
|
|
4
|
+
* @param container - the DOM node to host the WebGL canvas.
|
|
5
|
+
* @param scene - the declarative scene spec.
|
|
6
|
+
* @returns a disposer that removes the renderer and its context.
|
|
7
|
+
*/
|
|
8
|
+
export declare function mountScene(container: HTMLElement, scene: GenuiScene3D): Promise<() => void>;
|