dsh-generative-ui 0.0.3 → 0.0.4

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/lib/index.js CHANGED
@@ -5,7 +5,6 @@ import { join } from "node:path";
5
5
  import { createRequire } from "node:module";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import z from "@deepseek-ai/schemastery";
8
- import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
9
8
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
10
9
 
11
10
  // src/contract-assets.ts
@@ -1727,7 +1726,7 @@ var EXEC_HISTORY = `**A history is a set too.** 最近改了啥, 梳理一下 gi
1727
1726
  // src/index.ts
1728
1727
  var name = "dsh-generative-ui";
1729
1728
  var inject = ["systemPrompt"];
1730
- var SETTINGS_NAMESPACE = settingsNamespace("dsh-generative-ui");
1729
+ var SETTINGS_NAMESPACE = name;
1731
1730
  var Config = z.object({
1732
1731
  allowExec: z.boolean().default(true).description("Let generated cards run shell commands through `$dsh/exec`, under this session's own sandbox mode. Cards use it to search (`rg`, `fd`), run `lint`/`check`, and read `git`. The sandbox still applies; what does not is the per-command approval prompt, so turn this off for a session where that matters.")
1733
1732
  });
@@ -1763,8 +1762,8 @@ async function serveCanvas(liveWorkspaces, req, res) {
1763
1762
  if (!liveWorkspaces().has(cwd))
1764
1763
  return void res.writeHead(403).end();
1765
1764
  if (id === null) {
1766
- const ids = await readdir(join(cwd, CANVAS_DIR)).then((names) => names.flatMap((name2) => {
1767
- const found = canvasIdOf(`${CANVAS_DIR}/${name2}`);
1765
+ const ids = await readdir(join(cwd, CANVAS_DIR)).then((names) => names.flatMap((name) => {
1766
+ const found = canvasIdOf(`${CANVAS_DIR}/${name}`);
1768
1767
  return found === null ? [] : [found];
1769
1768
  }), () => []);
1770
1769
  res.writeHead(200, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
@@ -1813,7 +1812,7 @@ async function serveFs(ctx, liveWorkspaces, req, res) {
1813
1812
  if (req.method === "GET") {
1814
1813
  if (url.searchParams.get("list") !== null) {
1815
1814
  const entries = await ctx.fs.listDir(target);
1816
- return json(200, { entries: entries.map(({ name: name2, type, size }) => ({ name: name2, type, size })) });
1815
+ return json(200, { entries: entries.map(({ name, type, size }) => ({ name, type, size })) });
1817
1816
  }
1818
1817
  if (url.searchParams.get("bytes") !== null) {
1819
1818
  const bytes = await ctx.fs.readBytes(target, undefined, MAX_BINARY);
@@ -1986,11 +1985,13 @@ function apply(ctx, config = Config({})) {
1986
1985
  configured = { allowExec, fiber: ctx.plugin({ name: "dsh-generative-ui:configured", apply: (scoped) => applyWith(scoped, allowExec) }) };
1987
1986
  };
1988
1987
  rebuild();
1989
- installSettingsSection(ctx, SETTINGS_NAMESPACE, Config, config, {
1990
- setSource: (source) => {
1991
- current = source;
1992
- },
1993
- onChange: rebuild
1988
+ ctx.inject(["settings"], (sctx) => {
1989
+ sctx.settings.installSection(ctx, SETTINGS_NAMESPACE, Config, config, {
1990
+ setSource: (source) => {
1991
+ current = source;
1992
+ },
1993
+ onChange: rebuild
1994
+ });
1994
1995
  });
1995
1996
  ctx.effect(() => () => void configured?.fiber.dispose(), "dsh-generative-ui: settings scope");
1996
1997
  }
@@ -20,6 +20,10 @@ export type InlineFenceOptions = {
20
20
  streaming: boolean;
21
21
  last: () => boolean;
22
22
  }) => ReactElement;
23
+ /** 在预览渲染时读取当前语言,而不是捕获挂载时的翻译。 */
24
+ t: (key: "copy" | "copied") => string;
25
+ /** 语言或字典变化只刷新源码预览,不重新挂载卡片。 */
26
+ subscribeLocale: (refresh: () => void) => () => void;
23
27
  scope?: HTMLElement;
24
28
  };
25
29
  /**
@@ -47,4 +51,4 @@ export type InlineFenceOptions = {
47
51
  * store. The segment list is the transcript's own order and survives all three.
48
52
  */
49
53
  export declare const isLastSegment: (segments: readonly Ui4aSegment[], code: string) => boolean;
50
- export declare function claimInlineFences({ segments, render, scope }: InlineFenceOptions): () => void;
54
+ export declare function claimInlineFences({ segments, render, t, subscribeLocale, scope }: InlineFenceOptions): () => void;
@@ -32,7 +32,7 @@ declare module "@deepseek-ai/dsh-system-prompt" {
32
32
  export declare const name = "dsh-generative-ui";
33
33
  export declare const inject: string[];
34
34
  /** The settings section this plugin owns; the key under `dsh-generative-ui:` in settings.yaml. */
35
- export declare const SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
35
+ export declare const SETTINGS_NAMESPACE = "dsh-generative-ui";
36
36
  /**
37
37
  * Plugin settings. A schemastery schema, not a TypeScript type: the host validates the
38
38
  * `settings.yaml` section against it and builds the settings UI from it, so a plain interface
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-generative-ui",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Generative UI for DeepSeek Harness — the agent writes TSX, dsh web renders it live, inline in chat and in a canvas panel",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -56,7 +56,7 @@
56
56
  "build": "bun scripts/gen-standalone.ts && bun scripts/build.ts && tsc -p tsconfig.types.json",
57
57
  "prepare": "bun scripts/gen-standalone.ts && bun scripts/build.ts && tsc -p tsconfig.types.json",
58
58
  "typecheck": "tsc --noEmit",
59
- "test": "bun test",
59
+ "test": "node scripts/primitives-deps.mjs && bun test",
60
60
  "loads": "sh scripts/loads.sh",
61
61
  "smoke": "bun scripts/smoke.ts",
62
62
  "check": "bun run lint && bun run typecheck && bun run test && bun run test:shuffled && bun run audit && bun run replay && bun run cards && bun run paint && bun run build && bun run smoke && bun run loads",
@@ -81,45 +81,62 @@
81
81
  "partial-tsx": "~0.0.3"
82
82
  },
83
83
  "peerDependencies": {
84
- "@deepseek-ai/cordis": "^4.0.1",
85
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.7",
86
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
87
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.7",
88
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.7",
89
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.7",
90
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.7",
91
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
92
- "@deepseek-ai/dsh-skill": "^0.1.0-rc.7",
93
- "@deepseek-ai/schemastery": "^3.18.1",
84
+ "@deepseek-ai/cordis": "^4.0.2",
85
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
86
+ "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
87
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.1",
88
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
89
+ "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.1",
90
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.1",
91
+ "@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
92
+ "@deepseek-ai/dsh-skill": "^0.1.5-rc.1",
93
+ "@deepseek-ai/schemastery": "^3.18.2",
94
94
  "react": "^18.2.0"
95
95
  },
96
96
  "devDependencies": {
97
97
  "@babel/parser": "^8.0.4",
98
- "@deepseek-ai/cordis": "^4.0.1",
99
- "@deepseek-ai/dsh-client-locale": "0.1.0-rc.8",
100
- "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.8",
101
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.8",
102
- "@deepseek-ai/dsh-client-ui-layout": "0.1.0-rc.8",
103
- "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.8",
104
- "@deepseek-ai/dsh-host-webserver": "0.1.0-rc.8",
105
- "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
106
- "@deepseek-ai/dsh-settings": "0.1.0-rc.8",
107
- "@deepseek-ai/dsh-skill": "0.1.0-rc.8",
108
- "@deepseek-ai/dsh-system-prompt": "0.1.0-rc.8",
109
- "@deepseek-ai/schemastery": "^3.18.1",
98
+ "@deepseek-ai/cordis": "^4.0.2",
99
+ "@deepseek-ai/dsh-client-locale": "0.1.5-rc.1",
100
+ "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
101
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.1",
102
+ "@deepseek-ai/dsh-client-ui-layout": "0.1.5-rc.1",
103
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.1",
104
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.1",
105
+ "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.1",
106
+ "@deepseek-ai/dsh-llm": "0.1.5-rc.1",
107
+ "@deepseek-ai/dsh-settings": "0.1.5-rc.1",
108
+ "@deepseek-ai/dsh-skill": "0.1.5-rc.1",
109
+ "@deepseek-ai/dsh-system-prompt": "0.1.5-rc.1",
110
+ "@deepseek-ai/schemastery": "^3.18.2",
111
+ "@shikijs/langs": "^4.4.3",
110
112
  "@types/node": "^26.3.0",
111
113
  "@types/react": "~18.3.31",
112
114
  "@types/react-dom": "~18.3.7",
113
115
  "@types/scheduler": "^0.26.0",
116
+ "anser": "^2.3.5",
117
+ "clsx": "^2.1.1",
118
+ "katex": "^0.16.47",
119
+ "mdast-util-from-markdown": "^2.0.3",
120
+ "mdast-util-gfm": "^3.1.0",
121
+ "mdast-util-math": "^3.0.0",
122
+ "micromark-core-commonmark": "^2.0.3",
123
+ "micromark-extension-gfm": "^3.0.0",
124
+ "micromark-extension-math": "^3.1.0",
125
+ "micromark-factory-space": "^2.0.1",
126
+ "micromark-util-character": "^2.1.1",
127
+ "micromark-util-classify-character": "^2.0.1",
128
+ "micromark-util-sanitize-uri": "^2.0.1",
129
+ "micromark-util-symbol": "^2.0.1",
114
130
  "micromatch": "^4.0.8",
115
131
  "minimatch": "^10.2.6",
116
132
  "motion": "^13.1.1",
117
- "oxfmt": "^0.66.0",
133
+ "oxfmt": "^0.67.0",
118
134
  "oxlint": "^1.80.0",
119
135
  "picomatch": "^4.0.7",
120
136
  "pkg-pr-new": "^0.0.88",
121
137
  "react": "^18.3.1",
122
138
  "react-dom": "^18.3.1",
139
+ "shiki": "^4.4.3",
123
140
  "typescript": "^7.0.2"
124
141
  },
125
142
  "peerDependenciesMeta": {
@@ -7,6 +7,7 @@ import { createElement } from "react";
7
7
  import type { ClientContext } from "@deepseek-ai/dsh-client-runtime/client";
8
8
  import type {} from "@deepseek-ai/dsh-client-ui-layout/client";
9
9
  import type {} from "@deepseek-ai/dsh-client-ui-conversation/client";
10
+ import type {} from "@deepseek-ai/dsh-client-locale/client";
10
11
  import { GenUISurface } from "./runtime/GenUISurface.tsx";
11
12
  import { cancelPendingReport, cardRendered, reportCardError } from "./runtime/report-error.ts";
12
13
  import { disposeCompiler } from "./runtime/compiler.ts";
@@ -22,7 +23,7 @@ import { toolCallsOf, type CallBlock, type ToolCallView } from "./canvas/collect
22
23
  import { canvasIdOf } from "../contract.ts";
23
24
  import { CARD_ERROR_PATH } from "../contract-assets.ts";
24
25
 
25
- export const inject = ["sessions"];
26
+ export const inject = ["sessions", "locale"];
26
27
 
27
28
  /** Re-exported so `bun run smoke` can build the synthesized blob modules and parse them. */
28
29
  export { localImports };
@@ -191,6 +192,8 @@ export function apply(ctx: ClientContext): void {
191
192
  () =>
192
193
  claimInlineFences({
193
194
  segments,
195
+ t: ctx.locale.bind("common"),
196
+ subscribeLocale: (refresh) => ctx.locale.subscribe(refresh),
194
197
  // The SAME gate on both callbacks. A card that may not report a failure may not retract
195
198
  // one either — see `cardRendered`.
196
199
  render: ({ code, streaming, last }) => createElement(GenUISurface, { code, streaming, onError: (error, phase) => reportCardError(sendToModel, error.message, phase, last), onRendered: (restored) => cardRendered(restored, last) }),
@@ -116,6 +116,10 @@ export type InlineFenceOptions = {
116
116
  segments: () => readonly Ui4aSegment[];
117
117
  /** `last` answers whether this card is still the transcript's newest — asked at report time, not render time. */
118
118
  render: (props: { code: string; streaming: boolean; last: () => boolean }) => ReactElement;
119
+ /** 在预览渲染时读取当前语言,而不是捕获挂载时的翻译。 */
120
+ t: (key: "copy" | "copied") => string;
121
+ /** 语言或字典变化只刷新源码预览,不重新挂载卡片。 */
122
+ subscribeLocale: (refresh: () => void) => () => void;
119
123
  scope?: HTMLElement;
120
124
  };
121
125
 
@@ -158,7 +162,7 @@ export const isLastSegment = (segments: readonly Ui4aSegment[], code: string): b
158
162
  */
159
163
  const NEAR_VIEWPORT = "100% 0px";
160
164
 
161
- export function claimInlineFences({ segments, render, scope }: InlineFenceOptions): () => void {
165
+ export function claimInlineFences({ segments, render, t, subscribeLocale, scope }: InlineFenceOptions): () => void {
162
166
  const claims = new Map<HTMLElement, Claim>();
163
167
  const root = scope ?? document.body;
164
168
 
@@ -381,14 +385,18 @@ export function claimInlineFences({ segments, render, scope }: InlineFenceOption
381
385
  // The preview follows the SEGMENT, not the block: mid-stream the snapshot runs ahead of
382
386
  // what markdown has painted, so this is the newer text and the one the reader wants while
383
387
  // waiting. Dropped the moment the card paints.
384
- claim.preview?.root.render(createElement(CodeBlock, { code, lang: "tsx" }));
388
+ claim.preview?.root.render(createElement(CodeBlock, { code, lang: "tsx", copyLabel: t("copy"), copiedLabel: t("copied") }));
385
389
  }
386
390
  };
387
391
 
388
392
  const stop = observeTranscript(sweep);
393
+ const stopLocale = subscribeLocale(() => {
394
+ for (const claim of claims.values()) claim.preview?.root.render(createElement(CodeBlock, { code: claim.code, lang: "tsx", copyLabel: t("copy"), copiedLabel: t("copied") }));
395
+ });
389
396
 
390
397
  return () => {
391
398
  stop();
399
+ stopLocale();
392
400
  // `disconnect` rather than unobserving each: the observer is going away with us, and a
393
401
  // parked block that is never claimed would otherwise keep it alive through its target list.
394
402
  nearby?.disconnect();
package/src/index.ts CHANGED
@@ -17,7 +17,7 @@ import { createRequire } from "node:module";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import type { Context } from "@deepseek-ai/cordis";
19
19
  import z from "@deepseek-ai/schemastery";
20
- import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
20
+ import type {} from "@deepseek-ai/dsh-settings";
21
21
  import type {} from "@deepseek-ai/dsh-host-webserver";
22
22
  import type {} from "@deepseek-ai/dsh-system-prompt";
23
23
  import type {} from "@deepseek-ai/dsh-skill";
@@ -50,7 +50,7 @@ export const name = "dsh-generative-ui";
50
50
  export const inject = ["systemPrompt"];
51
51
 
52
52
  /** The settings section this plugin owns; the key under `dsh-generative-ui:` in settings.yaml. */
53
- export const SETTINGS_NAMESPACE = settingsNamespace("dsh-generative-ui");
53
+ export const SETTINGS_NAMESPACE = name;
54
54
 
55
55
  /**
56
56
  * Plugin settings. A schemastery schema, not a TypeScript type: the host validates the
@@ -541,16 +541,15 @@ export function apply(ctx: Context, config: Config = Config({})): void {
541
541
  void configured?.fiber.dispose();
542
542
  configured = { allowExec, fiber: ctx.plugin({ name: "dsh-generative-ui:configured", apply: (scoped: Context) => applyWith(scoped, allowExec) }) };
543
543
  };
544
- // Called here as well as from `onChange`, and that is not belt-and-braces: the whole of
545
- // `installSettingsSection` sits inside `ctx.inject(["settings"])`, so on a host with no settings
546
- // service — `dsh --profile headless` is one — `onChange` never fires at all and nothing would
547
- // ever mount. The `mounted` check above is what keeps this from double-mounting where it does.
544
+ // settings 服务时下面的 inject 不执行,必须先用 entry 配置挂载;rebuild 内的值比较避免重复挂载。
548
545
  rebuild();
549
- installSettingsSection(ctx, SETTINGS_NAMESPACE, Config, config, {
550
- setSource: (source) => {
551
- current = source;
552
- },
553
- onChange: rebuild,
546
+ ctx.inject(["settings"], (sctx) => {
547
+ sctx.settings.installSection(ctx, SETTINGS_NAMESPACE, Config, config, {
548
+ setSource: (source) => {
549
+ current = source;
550
+ },
551
+ onChange: rebuild,
552
+ });
554
553
  });
555
554
  ctx.effect(() => () => void configured?.fiber.dispose(), "dsh-generative-ui: settings scope");
556
555
  }