@jcy2387/dsh-conversation-ui 0.1.0-rc.12
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/LICENSE +21 -0
- package/README.en.md +123 -0
- package/README.md +123 -0
- package/conversation-ui-off.yml +3 -0
- package/cordis.patch.yml +10 -0
- package/lib/client.js +3672 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +302 -0
- package/package.json +182 -0
- package/src/boot-config.ts +28 -0
- package/src/client/AnimatedDisclosure.tsx +89 -0
- package/src/client/ConversationCard.module.css +253 -0
- package/src/client/ConversationCard.tsx +122 -0
- package/src/client/DeliverablesCard.module.css +187 -0
- package/src/client/DeliverablesCard.tsx +142 -0
- package/src/client/FollowHost.tsx +25 -0
- package/src/client/ToolActivityGroup.tsx +341 -0
- package/src/client/TurnPreludeUserNodeView.tsx +258 -0
- package/src/client/TurnProcessMember.tsx +92 -0
- package/src/client/TypewriterAssistantNodeView.module.css +1260 -0
- package/src/client/TypewriterAssistantNodeView.tsx +390 -0
- package/src/client/TypewriterToolNodeView.tsx +916 -0
- package/src/client/conversation-ui-card-controller.ts +162 -0
- package/src/client/conversation-ui-settings-api.ts +65 -0
- package/src/client/deliverables.ts +263 -0
- package/src/client/index.ts +242 -0
- package/src/client/locales.ts +74 -0
- package/src/client/teleprompterGlide.ts +590 -0
- package/src/client/turnElapsed.ts +27 -0
- package/src/client/turnProcessFold.ts +224 -0
- package/src/client/useConversationContent.ts +471 -0
- package/src/client/useFpsGuard.ts +84 -0
- package/src/config.ts +44 -0
- package/src/css-modules.d.ts +9 -0
- package/src/index.ts +2 -0
- package/src/package-meta.ts +39 -0
- package/src/plugin.ts +155 -0
- package/src/profile-installation.ts +121 -0
- package/src/settings-api.ts +33 -0
- package/src/settings.ts +30 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
//#region src/config.ts
|
|
8
|
+
/** Defaults shared by the Host schema and the client-side fallback. */
|
|
9
|
+
const DEFAULT_CONVERSATION_CONFIG = {
|
|
10
|
+
mode: "teleprompter",
|
|
11
|
+
preset: "balanced",
|
|
12
|
+
revealCharsPerSec: 80,
|
|
13
|
+
scrollSpeedPxPerSec: 48,
|
|
14
|
+
maxScrollSpeedPxPerSec: 1e3
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Window global the Host writes into the served index HTML. The browser boot
|
|
18
|
+
* graph carries no per-entry config, so this inline script is the only
|
|
19
|
+
* Host-to-client configuration channel for a composed web plugin.
|
|
20
|
+
*/
|
|
21
|
+
const CONVERSATION_BOOT_GLOBAL = "__DSH_CONVERSATION_UI_CONFIG__";
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/boot-config.ts
|
|
24
|
+
/**
|
|
25
|
+
* Host-rendered configuration bootstrap for the browser half: each index
|
|
26
|
+
* response embeds the schema-validated plugin config as a window global the
|
|
27
|
+
* client entry reads at apply time. Same pattern as ui-theme's boot theme.
|
|
28
|
+
*/
|
|
29
|
+
/** Build the inline script assigning the validated config to the boot global. */
|
|
30
|
+
function bootConfigScript(config) {
|
|
31
|
+
return `<script>window[${JSON.stringify(CONVERSATION_BOOT_GLOBAL)}]=${JSON.stringify(config)}<\/script>`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Insert the config bootstrap immediately after the opening body tag, before
|
|
35
|
+
* any plugin bundle runs. Body-less fragments receive it at the end, where
|
|
36
|
+
* the HTML parser has already synthesized a body.
|
|
37
|
+
* @param html - Raw application index HTML.
|
|
38
|
+
* @param config - Schema-validated plugin configuration.
|
|
39
|
+
* @returns HTML containing the config bootstrap.
|
|
40
|
+
*/
|
|
41
|
+
function injectConversationConfig(html, config) {
|
|
42
|
+
const script = bootConfigScript(config);
|
|
43
|
+
const body = /<body(?:\s[^>]*)?>/i.exec(html);
|
|
44
|
+
if (body === null) return `${html}${script}`;
|
|
45
|
+
const at = body.index + body[0].length;
|
|
46
|
+
return `${html.slice(0, at)}${script}${html.slice(at)}`;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/package-meta.ts
|
|
50
|
+
/** Host-only package metadata. The package manifest remains the source of truth. */
|
|
51
|
+
function packageManifestPath() {
|
|
52
|
+
try {
|
|
53
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
54
|
+
} catch {}
|
|
55
|
+
return join(process.cwd(), "package.json");
|
|
56
|
+
}
|
|
57
|
+
const manifest = JSON.parse(readFileSync(packageManifestPath(), "utf8"));
|
|
58
|
+
function readRequiredString(field) {
|
|
59
|
+
const value = manifest[field];
|
|
60
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`dsh-conversation-ui: package.json must contain a non-empty ${field}`);
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
/** npm package name, read from this plugin's manifest. */
|
|
64
|
+
const CONVERSATION_PACKAGE_NAME = readRequiredString("name");
|
|
65
|
+
/** Version of the code currently loaded by the Host, read from its manifest. */
|
|
66
|
+
const CONVERSATION_PACKAGE_VERSION = readRequiredString("version");
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/profile-installation.ts
|
|
69
|
+
/** Host-only profile source inspection and fixed npm update runner. */
|
|
70
|
+
function record(value) {
|
|
71
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
72
|
+
}
|
|
73
|
+
function profileDirectory(baseUrl) {
|
|
74
|
+
if (baseUrl === void 0) return void 0;
|
|
75
|
+
try {
|
|
76
|
+
const url = new URL(baseUrl);
|
|
77
|
+
return url.protocol === "file:" ? fileURLToPath(url) : void 0;
|
|
78
|
+
} catch {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function hasBundle(manifest, packageName) {
|
|
83
|
+
const bundles = record(record(manifest.dsh)?.profile)?.bundles;
|
|
84
|
+
return Array.isArray(bundles) && bundles.includes(packageName);
|
|
85
|
+
}
|
|
86
|
+
function isLocalSpecifier(specifier) {
|
|
87
|
+
return specifier.startsWith("link:") || specifier.startsWith("file:") || specifier.startsWith(".") || isAbsolute(specifier) || /^[A-Za-z]:[\\/]/.test(specifier);
|
|
88
|
+
}
|
|
89
|
+
function isRegistrySpecifier(specifier) {
|
|
90
|
+
return specifier.length > 0 && !specifier.includes(":") && !/[\\/]/.test(specifier);
|
|
91
|
+
}
|
|
92
|
+
function isNpmSpecifier(specifier, packageName) {
|
|
93
|
+
if (!specifier.startsWith("npm:")) return isRegistrySpecifier(specifier);
|
|
94
|
+
const aliased = specifier.slice(4);
|
|
95
|
+
if (aliased === packageName) return true;
|
|
96
|
+
const prefix = `${packageName}@`;
|
|
97
|
+
return aliased.startsWith(prefix) && isRegistrySpecifier(aliased.slice(prefix.length));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Read only the profile manifest anchored by the current Cordis config tree.
|
|
101
|
+
* A malformed or unrelated tree never receives an update affordance.
|
|
102
|
+
*/
|
|
103
|
+
function inspectProfileInstallation(baseUrl, packageName) {
|
|
104
|
+
const profileDir = profileDirectory(baseUrl);
|
|
105
|
+
if (profileDir === void 0) return { kind: "unmanaged" };
|
|
106
|
+
let manifest;
|
|
107
|
+
try {
|
|
108
|
+
manifest = JSON.parse(readFileSync(join(profileDir, "package.json"), "utf8"));
|
|
109
|
+
} catch {
|
|
110
|
+
return { kind: "unmanaged" };
|
|
111
|
+
}
|
|
112
|
+
const specifier = record(manifest.dependencies)?.[packageName];
|
|
113
|
+
if (typeof specifier !== "string" || !hasBundle(manifest, packageName)) return { kind: "unmanaged" };
|
|
114
|
+
if (isLocalSpecifier(specifier)) return { kind: "development" };
|
|
115
|
+
if (!isNpmSpecifier(specifier, packageName)) return { kind: "unmanaged" };
|
|
116
|
+
return {
|
|
117
|
+
kind: "npm",
|
|
118
|
+
profileDir,
|
|
119
|
+
profileName: basename(profileDir)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** Run the same fixed package update operation that a profile user would invoke. */
|
|
123
|
+
function updateNpmProfilePackage(profileDir, packageName) {
|
|
124
|
+
const command = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const child = spawn(command, ["update", packageName], {
|
|
127
|
+
cwd: profileDir,
|
|
128
|
+
stdio: "ignore",
|
|
129
|
+
shell: false,
|
|
130
|
+
windowsHide: true
|
|
131
|
+
});
|
|
132
|
+
child.once("error", reject);
|
|
133
|
+
child.once("exit", (code, signal) => {
|
|
134
|
+
if (code === 0) resolve();
|
|
135
|
+
else reject(/* @__PURE__ */ new Error(`dsh-conversation-ui: pnpm update failed (${signal ?? String(code)})`));
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/settings-api.ts
|
|
141
|
+
/** Shared wire vocabulary for the plugin-owned settings RPC channel. */
|
|
142
|
+
/** Dedicated, loopback-only RPC channel registered by the Host half. */
|
|
143
|
+
const CONVERSATION_SETTINGS_RPC_CHANNEL = "/conversation-ui";
|
|
144
|
+
/** Endpoints accepted by {@link CONVERSATION_SETTINGS_RPC_CHANNEL}. */
|
|
145
|
+
const CONVERSATION_SETTINGS_RPC = {
|
|
146
|
+
read: "settings.read",
|
|
147
|
+
write: "settings.write",
|
|
148
|
+
upgrade: "plugin.upgrade"
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/settings.ts
|
|
152
|
+
/**
|
|
153
|
+
* User-owned settings for the conversation-ui plugin, exposed to the Host
|
|
154
|
+
* settings service and edited from the Web Settings "plugin configuration"
|
|
155
|
+
* page. This is the runtime-editable complement to {@link ConversationConfig}: that
|
|
156
|
+
* contract is composed at load and bridged once through the boot global, while
|
|
157
|
+
* these preferences live in the durable user-settings document and take effect
|
|
158
|
+
* live.
|
|
159
|
+
*/
|
|
160
|
+
/** Settings namespace registered by the Host and served through the plugin RPC. */
|
|
161
|
+
const CONVERSATION_SETTINGS_NS = "conversation-ui";
|
|
162
|
+
/** Defaults shared by the Host schema and the client-side fallback. */
|
|
163
|
+
const DEFAULT_CONVERSATION_SETTINGS = { thinkAutoExpand: true };
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/plugin.ts
|
|
166
|
+
/** Display name shown by the Host loader while the plugin is mounted. */
|
|
167
|
+
const name = "dsh-conversation-ui";
|
|
168
|
+
const Config = Schema.object({
|
|
169
|
+
mode: Schema.union(["typewriter", "teleprompter"]).default(DEFAULT_CONVERSATION_CONFIG.mode),
|
|
170
|
+
preset: Schema.union([
|
|
171
|
+
"realtime",
|
|
172
|
+
"balanced",
|
|
173
|
+
"silky"
|
|
174
|
+
]).default(DEFAULT_CONVERSATION_CONFIG.preset),
|
|
175
|
+
revealCharsPerSec: Schema.number().min(5).max(200).default(DEFAULT_CONVERSATION_CONFIG.revealCharsPerSec),
|
|
176
|
+
scrollSpeedPxPerSec: Schema.number().min(1).max(200).default(DEFAULT_CONVERSATION_CONFIG.scrollSpeedPxPerSec),
|
|
177
|
+
maxScrollSpeedPxPerSec: Schema.number().min(1).max(2e3).default(DEFAULT_CONVERSATION_CONFIG.maxScrollSpeedPxPerSec)
|
|
178
|
+
});
|
|
179
|
+
/**
|
|
180
|
+
* Schema of the user-owned settings section. The Host keeps it in the durable
|
|
181
|
+
* settings provider while the browser edits it through the plugin RPC below.
|
|
182
|
+
*/
|
|
183
|
+
const ConversationSettingsSchema = Schema.object({ thinkAutoExpand: Schema.boolean().default(DEFAULT_CONVERSATION_SETTINGS.thinkAutoExpand) });
|
|
184
|
+
/**
|
|
185
|
+
* Host half: log the resolved configuration and bridge it to the browser
|
|
186
|
+
* half. The web boot graph carries no per-entry config, so the validated
|
|
187
|
+
* value is injected into every served index response as a boot global the
|
|
188
|
+
* client entry reads at apply time.
|
|
189
|
+
* @param ctx - Host context carrying the web server service when composed.
|
|
190
|
+
* @param config - Schema-validated configuration with defaults filled.
|
|
191
|
+
*/
|
|
192
|
+
function apply(ctx, config) {
|
|
193
|
+
console.log(`[dsh-conversation-ui] plugin loaded! mode=${config.mode} preset=${config.preset} seed=${config.revealCharsPerSec}cps scroll=${config.scrollSpeedPxPerSec}px/s maxScroll=${config.maxScrollSpeedPxPerSec}px/s`);
|
|
194
|
+
ctx.inject(["webServer"], (httpCtx) => {
|
|
195
|
+
httpCtx.effect(() => httpCtx.webServer.tapIndex((html) => injectConversationConfig(html, config)), "dsh-conversation-ui: boot config bridge");
|
|
196
|
+
});
|
|
197
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
198
|
+
const scope = settingsCtx.settings.register(settingsNamespace(CONVERSATION_SETTINGS_NS), ConversationSettingsSchema, { applies: "live" });
|
|
199
|
+
settingsCtx.inject(["connection"], (connectionCtx) => {
|
|
200
|
+
let upgrade;
|
|
201
|
+
const view = () => {
|
|
202
|
+
const installation = inspectProfileInstallation(connectionCtx.baseUrl, CONVERSATION_PACKAGE_NAME);
|
|
203
|
+
return {
|
|
204
|
+
version: CONVERSATION_PACKAGE_VERSION,
|
|
205
|
+
installation: installation.kind,
|
|
206
|
+
writable: connectionCtx.settings.writable,
|
|
207
|
+
thinkAutoExpand: scope.get().thinkAutoExpand,
|
|
208
|
+
canUpgrade: installation.kind === "npm"
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
const handle = async (endpoint, payload) => {
|
|
212
|
+
if (endpoint === CONVERSATION_SETTINGS_RPC.read) return {
|
|
213
|
+
ok: true,
|
|
214
|
+
value: view()
|
|
215
|
+
};
|
|
216
|
+
if (endpoint === CONVERSATION_SETTINGS_RPC.write) {
|
|
217
|
+
if (typeof payload !== "object" || payload === null || typeof payload.thinkAutoExpand !== "boolean") return {
|
|
218
|
+
ok: false,
|
|
219
|
+
error: {
|
|
220
|
+
code: "settings-rejected",
|
|
221
|
+
message: "thinkAutoExpand must be a boolean",
|
|
222
|
+
details: { ns: CONVERSATION_SETTINGS_NS }
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
if (!connectionCtx.settings.writable) return {
|
|
226
|
+
ok: false,
|
|
227
|
+
error: {
|
|
228
|
+
code: "settings-rejected",
|
|
229
|
+
message: "conversation-ui settings are read-only",
|
|
230
|
+
details: { ns: CONVERSATION_SETTINGS_NS }
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
try {
|
|
234
|
+
await scope.update({ thinkAutoExpand: payload.thinkAutoExpand });
|
|
235
|
+
} catch {
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
error: {
|
|
239
|
+
code: "settings-rejected",
|
|
240
|
+
message: "conversation-ui settings update failed",
|
|
241
|
+
details: { ns: CONVERSATION_SETTINGS_NS }
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
ok: true,
|
|
247
|
+
value: view()
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (endpoint === CONVERSATION_SETTINGS_RPC.upgrade) {
|
|
251
|
+
const installation = inspectProfileInstallation(connectionCtx.baseUrl, CONVERSATION_PACKAGE_NAME);
|
|
252
|
+
if (installation.kind !== "npm") return {
|
|
253
|
+
ok: false,
|
|
254
|
+
error: {
|
|
255
|
+
code: "internal",
|
|
256
|
+
message: "conversation-ui is not an npm profile dependency",
|
|
257
|
+
details: {}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
if (upgrade !== void 0) return {
|
|
261
|
+
ok: false,
|
|
262
|
+
error: {
|
|
263
|
+
code: "internal",
|
|
264
|
+
message: "conversation-ui update is already running",
|
|
265
|
+
details: {}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
upgrade = updateNpmProfilePackage(installation.profileDir, CONVERSATION_PACKAGE_NAME);
|
|
269
|
+
try {
|
|
270
|
+
await upgrade;
|
|
271
|
+
} catch {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
error: {
|
|
275
|
+
code: "internal",
|
|
276
|
+
message: "conversation-ui update failed",
|
|
277
|
+
details: {}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
} finally {
|
|
281
|
+
upgrade = void 0;
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
ok: true,
|
|
285
|
+
value: { restartRequired: true }
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
error: {
|
|
291
|
+
code: "internal",
|
|
292
|
+
message: `unknown conversation-ui endpoint ${JSON.stringify(endpoint)}`,
|
|
293
|
+
details: {}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
connectionCtx.effect(() => connectionCtx.connection.rpc.handle(CONVERSATION_SETTINGS_RPC_CHANNEL, handle, { authority: "loopback" }), "dsh-conversation-ui: settings RPC");
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
//#endregion
|
|
302
|
+
export { Config, apply, name };
|
package/package.json
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jcy2387/dsh-conversation-ui",
|
|
3
|
+
"version": "0.1.0-rc.12",
|
|
4
|
+
"description": "DeepSeek Harness (dsh) plugin: Codex-style progress, tool activity, and final answers.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"registry": "https://registry.npmjs.org/"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/DamonBao/dsh-codex-suite.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/DamonBao/dsh-codex-suite/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/DamonBao/dsh-codex-suite#readme",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"dsh-plugin",
|
|
24
|
+
"deepseek-harness",
|
|
25
|
+
"deepseek",
|
|
26
|
+
"dsh",
|
|
27
|
+
"cordis",
|
|
28
|
+
"streaming",
|
|
29
|
+
"conversation-ui",
|
|
30
|
+
"typewriter",
|
|
31
|
+
"typewriter-effect",
|
|
32
|
+
"markdown",
|
|
33
|
+
"chat-ui",
|
|
34
|
+
"web-ui",
|
|
35
|
+
"plugin",
|
|
36
|
+
"react"
|
|
37
|
+
],
|
|
38
|
+
"main": "lib/index.js",
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./src/index.ts",
|
|
42
|
+
"default": "./lib/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./client": {
|
|
45
|
+
"types": "./src/client/index.ts",
|
|
46
|
+
"default": "./lib/client.js"
|
|
47
|
+
},
|
|
48
|
+
"./src/*": "./src/*",
|
|
49
|
+
"./package.json": "./package.json"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"lib/**/*.js",
|
|
53
|
+
"lib/**/*.js.map",
|
|
54
|
+
"src/**/*.ts",
|
|
55
|
+
"src/**/*.tsx",
|
|
56
|
+
"src/**/*.css",
|
|
57
|
+
"cordis.patch.yml",
|
|
58
|
+
"conversation-ui-off.yml",
|
|
59
|
+
"README.md",
|
|
60
|
+
"README.en.md",
|
|
61
|
+
"LICENSE"
|
|
62
|
+
],
|
|
63
|
+
"dsh": {
|
|
64
|
+
"bundle": {
|
|
65
|
+
"patch": "./cordis.patch.yml"
|
|
66
|
+
},
|
|
67
|
+
"client": {
|
|
68
|
+
"platform": "web",
|
|
69
|
+
"inject": [
|
|
70
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
72
|
+
"@deepseek-ai/dsh-client-locale",
|
|
73
|
+
"@deepseek-ai/dsh-client-connection",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"dependencies": {
|
|
80
|
+
"@deepseek-ai/dsh-settings": "0.1.0-rc.8",
|
|
81
|
+
"react-icons": "5.5.0"
|
|
82
|
+
},
|
|
83
|
+
"peerDependencies": {
|
|
84
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
85
|
+
"@deepseek-ai/dsh-client-connection": "*",
|
|
86
|
+
"@deepseek-ai/dsh-client-locale": "*",
|
|
87
|
+
"@deepseek-ai/dsh-client-runtime": "*",
|
|
88
|
+
"@deepseek-ai/dsh-client-ui-attachment": "*",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-conversation": "*",
|
|
90
|
+
"@deepseek-ai/dsh-client-ui-primitives": "*",
|
|
91
|
+
"@deepseek-ai/dsh-client-ui-settings": "*",
|
|
92
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "*",
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-slots": "*",
|
|
94
|
+
"@deepseek-ai/dsh-host-webserver": "*",
|
|
95
|
+
"@deepseek-ai/schemastery": "*",
|
|
96
|
+
"react": "^18.2.0",
|
|
97
|
+
"react-dom": "^18.2.0"
|
|
98
|
+
},
|
|
99
|
+
"peerDependenciesMeta": {
|
|
100
|
+
"@deepseek-ai/dsh-client-locale": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"@deepseek-ai/dsh-client-runtime": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"@deepseek-ai/dsh-client-ui-attachment": {
|
|
107
|
+
"optional": true
|
|
108
|
+
},
|
|
109
|
+
"@deepseek-ai/dsh-client-connection": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"@deepseek-ai/dsh-client-ui-conversation": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
115
|
+
"@deepseek-ai/dsh-client-ui-primitives": {
|
|
116
|
+
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"@deepseek-ai/dsh-client-ui-settings": {
|
|
119
|
+
"optional": true
|
|
120
|
+
},
|
|
121
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": {
|
|
122
|
+
"optional": true
|
|
123
|
+
},
|
|
124
|
+
"@deepseek-ai/dsh-client-ui-slots": {
|
|
125
|
+
"optional": true
|
|
126
|
+
},
|
|
127
|
+
"@deepseek-ai/dsh-host-webserver": {
|
|
128
|
+
"optional": true
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"devDependencies": {
|
|
132
|
+
"@deepseek-ai/cordis": "4.0.1",
|
|
133
|
+
"@deepseek-ai/dsh-client-connection": "0.1.0-rc.8",
|
|
134
|
+
"@deepseek-ai/dsh-client-locale": "0.1.0-rc.8",
|
|
135
|
+
"@deepseek-ai/dsh-client-runtime": "0.1.0-rc.8",
|
|
136
|
+
"@deepseek-ai/dsh-client-ui-attachment": "0.1.0-rc.8",
|
|
137
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.8",
|
|
138
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.8",
|
|
139
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.8",
|
|
140
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.0-rc.8",
|
|
141
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.8",
|
|
142
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.0-rc.8",
|
|
143
|
+
"@deepseek-ai/dsh-settings": "0.1.0-rc.8",
|
|
144
|
+
"@deepseek-ai/schemastery": "3.18.1",
|
|
145
|
+
"@shikijs/langs": "^4.4.3",
|
|
146
|
+
"@testing-library/react": "^16.3.0",
|
|
147
|
+
"@types/node": "^22.20.0",
|
|
148
|
+
"@types/react": "~18.3.1",
|
|
149
|
+
"@types/react-dom": "~18.3.0",
|
|
150
|
+
"anser": "^2.3.5",
|
|
151
|
+
"clsx": "^2.1.1",
|
|
152
|
+
"immer": "^10.1.1",
|
|
153
|
+
"jsdom": "^26.1.0",
|
|
154
|
+
"katex": "^0.16.47",
|
|
155
|
+
"lightningcss": "^1.30.1",
|
|
156
|
+
"mdast-util-from-markdown": "^2.0.3",
|
|
157
|
+
"mdast-util-gfm": "^3.1.0",
|
|
158
|
+
"mdast-util-math": "^3.0.0",
|
|
159
|
+
"micromark-core-commonmark": "^2.0.3",
|
|
160
|
+
"micromark-extension-gfm": "^3.0.0",
|
|
161
|
+
"micromark-extension-math": "^3.1.0",
|
|
162
|
+
"micromark-factory-space": "^2.0.1",
|
|
163
|
+
"micromark-util-character": "^2.1.1",
|
|
164
|
+
"micromark-util-classify-character": "^2.0.1",
|
|
165
|
+
"micromark-util-sanitize-uri": "^2.0.1",
|
|
166
|
+
"micromark-util-symbol": "^2.0.1",
|
|
167
|
+
"micromark-util-types": "^2.0.2",
|
|
168
|
+
"react": "^18.3.1",
|
|
169
|
+
"react-dom": "^18.3.1",
|
|
170
|
+
"shiki": "^4.4.3",
|
|
171
|
+
"tsdown": "^0.22.2",
|
|
172
|
+
"typescript": "^5.9.2",
|
|
173
|
+
"vitest": "^4.1.8",
|
|
174
|
+
"zod": "^4.4.3",
|
|
175
|
+
"zustand": "~4.4.7"
|
|
176
|
+
},
|
|
177
|
+
"scripts": {
|
|
178
|
+
"build": "tsdown",
|
|
179
|
+
"test": "vitest run",
|
|
180
|
+
"typecheck": "tsc -p tsconfig.json --pretty false"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-rendered configuration bootstrap for the browser half: each index
|
|
3
|
+
* response embeds the schema-validated plugin config as a window global the
|
|
4
|
+
* client entry reads at apply time. Same pattern as ui-theme's boot theme.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CONVERSATION_BOOT_GLOBAL, type ConversationConfig } from './config.ts'
|
|
8
|
+
|
|
9
|
+
/** Build the inline script assigning the validated config to the boot global. */
|
|
10
|
+
function bootConfigScript(config: ConversationConfig): string {
|
|
11
|
+
return `<script>window[${JSON.stringify(CONVERSATION_BOOT_GLOBAL)}]=${JSON.stringify(config)}</script>`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Insert the config bootstrap immediately after the opening body tag, before
|
|
16
|
+
* any plugin bundle runs. Body-less fragments receive it at the end, where
|
|
17
|
+
* the HTML parser has already synthesized a body.
|
|
18
|
+
* @param html - Raw application index HTML.
|
|
19
|
+
* @param config - Schema-validated plugin configuration.
|
|
20
|
+
* @returns HTML containing the config bootstrap.
|
|
21
|
+
*/
|
|
22
|
+
export function injectConversationConfig(html: string, config: ConversationConfig): string {
|
|
23
|
+
const script = bootConfigScript(config)
|
|
24
|
+
const body = /<body(?:\s[^>]*)?>/i.exec(html)
|
|
25
|
+
if (body === null) return `${html}${script}`
|
|
26
|
+
const at = body.index + body[0].length
|
|
27
|
+
return `${html.slice(0, at)}${script}${html.slice(at)}`
|
|
28
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// AnimatedDisclosure: the harness DisclosureRow chrome (24px row, trailing
|
|
2
|
+
// chevron, whole-row click/Enter/Space toggle) with one deliberate
|
|
3
|
+
// difference - the expanded body stays mounted while collapsed and animates
|
|
4
|
+
// between 0fr and 1fr grid tracks, so expand/collapse glides instead of
|
|
5
|
+
// snapping. The harness primitive unmounts closed children (`{open &&
|
|
6
|
+
// children}`) and a plugin cannot change that in place, so this fork mirrors
|
|
7
|
+
// its DOM shape and `data-disclosure-row` contract: host styling keyed on the
|
|
8
|
+
// row attribute and the Think chrome keep working unchanged.
|
|
9
|
+
|
|
10
|
+
import { type KeyboardEvent, type ReactNode } from 'react'
|
|
11
|
+
import {
|
|
12
|
+
IconChevronDownOutline14,
|
|
13
|
+
IconChevronRightOutline14,
|
|
14
|
+
} from '@deepseek-ai/dsh-client-ui-primitives'
|
|
15
|
+
import css from './TypewriterAssistantNodeView.module.css'
|
|
16
|
+
|
|
17
|
+
/** Class-name join for optional overlay classes over the chrome defaults. */
|
|
18
|
+
function cx(...parts: Array<string | undefined>): string {
|
|
19
|
+
return parts.filter(part => part !== undefined && part !== '').join(' ')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AnimatedDisclosureProps {
|
|
23
|
+
icon: ReactNode
|
|
24
|
+
title: string
|
|
25
|
+
open: boolean
|
|
26
|
+
onToggle: () => void
|
|
27
|
+
collapsedContent?: ReactNode
|
|
28
|
+
children?: ReactNode
|
|
29
|
+
rowClassName?: string | undefined
|
|
30
|
+
leadingClassName?: string | undefined
|
|
31
|
+
titleClassName?: string | undefined
|
|
32
|
+
chevronClassName?: string | undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Render one disclosure header whose expanded body is height-animated.
|
|
37
|
+
* @param props - Visual content, controlled open state, and the toggle
|
|
38
|
+
* callback fired by row click and Enter/Space.
|
|
39
|
+
* @returns the animated disclosure row.
|
|
40
|
+
*/
|
|
41
|
+
export function AnimatedDisclosure({
|
|
42
|
+
icon,
|
|
43
|
+
title,
|
|
44
|
+
open,
|
|
45
|
+
onToggle,
|
|
46
|
+
collapsedContent,
|
|
47
|
+
children,
|
|
48
|
+
rowClassName,
|
|
49
|
+
leadingClassName,
|
|
50
|
+
titleClassName,
|
|
51
|
+
chevronClassName,
|
|
52
|
+
}: AnimatedDisclosureProps) {
|
|
53
|
+
const toggleFromKeyboard = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
54
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
55
|
+
event.preventDefault()
|
|
56
|
+
onToggle()
|
|
57
|
+
}
|
|
58
|
+
return (
|
|
59
|
+
<div className={css.disclosureRoot} data-open={open || undefined}>
|
|
60
|
+
<div
|
|
61
|
+
className={cx(css.disclosureRow, rowClassName)}
|
|
62
|
+
data-disclosure-row
|
|
63
|
+
data-expandable=""
|
|
64
|
+
role="button"
|
|
65
|
+
tabIndex={0}
|
|
66
|
+
aria-expanded={open}
|
|
67
|
+
onClick={onToggle}
|
|
68
|
+
onKeyDown={toggleFromKeyboard}
|
|
69
|
+
>
|
|
70
|
+
<span className={cx(css.disclosureLeading, leadingClassName)} data-disclosure-icon>
|
|
71
|
+
{icon}
|
|
72
|
+
</span>
|
|
73
|
+
<span className={cx(css.disclosureTitle, titleClassName)}>{title}</span>
|
|
74
|
+
{!open && collapsedContent}
|
|
75
|
+
<span
|
|
76
|
+
className={cx(css.disclosureTrailing, chevronClassName)}
|
|
77
|
+
data-disclosure-chevron
|
|
78
|
+
data-state={open ? 'expanded' : 'collapsed'}
|
|
79
|
+
aria-hidden
|
|
80
|
+
>
|
|
81
|
+
{open ? <IconChevronDownOutline14 size={14} /> : <IconChevronRightOutline14 size={14} />}
|
|
82
|
+
</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div className={css.disclosureContent} data-disclosure-content data-collapsed={open ? undefined : ''}>
|
|
85
|
+
{children}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
}
|