@vibedev-si/dsh-vibedev 0.2.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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/THIRD-PARTY-NOTICES.txt +566 -0
  4. package/client/client.js +1368 -0
  5. package/cordis.patch.yml +5 -0
  6. package/icon.svg +7 -0
  7. package/lib/account/index.d.ts +118 -0
  8. package/lib/anthropic-messages-KOS0z8Fy.js +13472 -0
  9. package/lib/auth/credentials.d.ts +47 -0
  10. package/lib/auth/login.d.ts +154 -0
  11. package/lib/gateway/assets.d.ts +61 -0
  12. package/lib/gateway/catalog.d.ts +126 -0
  13. package/lib/gateway/errors.d.ts +76 -0
  14. package/lib/gateway/http.d.ts +94 -0
  15. package/lib/index.d.ts +105 -0
  16. package/lib/index.js +7301 -0
  17. package/lib/llm/adapter.d.ts +129 -0
  18. package/lib/llm/catalog.d.ts +75 -0
  19. package/lib/llm/context.d.ts +54 -0
  20. package/lib/llm/index.d.ts +69 -0
  21. package/lib/llm/limits.d.ts +19 -0
  22. package/lib/llm/models.d.ts +67 -0
  23. package/lib/llm/replay.d.ts +66 -0
  24. package/lib/llm/stream.d.ts +46 -0
  25. package/lib/media/chat.d.ts +49 -0
  26. package/lib/media/sources.d.ts +46 -0
  27. package/lib/models-CAcLevEN.js +1045 -0
  28. package/lib/node-Cujw_a4_.js +2403 -0
  29. package/lib/node-vkDR3GtK.js +25 -0
  30. package/lib/openai-completions-DqTCiqpS.js +983 -0
  31. package/lib/openai-prompt-cache-kzRZVokq.js +9567 -0
  32. package/lib/openai-responses-DygWnsye.js +832 -0
  33. package/lib/pricing.d.ts +49 -0
  34. package/lib/runtime.d.ts +150 -0
  35. package/lib/search/provider.d.ts +86 -0
  36. package/lib/service.d.ts +99 -0
  37. package/lib/tasks/remote.d.ts +47 -0
  38. package/lib/tasks/store.d.ts +132 -0
  39. package/lib/tasks/tracker.d.ts +127 -0
  40. package/lib/tools/account.d.ts +21 -0
  41. package/lib/tools/audio.d.ts +13 -0
  42. package/lib/tools/common.d.ts +77 -0
  43. package/lib/tools/image.d.ts +50 -0
  44. package/lib/tools/models.d.ts +13 -0
  45. package/lib/tools/tasks.d.ts +13 -0
  46. package/lib/tools/transcribe.d.ts +19 -0
  47. package/lib/tools/video.d.ts +48 -0
  48. package/lib/transform-messages-Csvf2ItW.js +1023 -0
  49. package/lib/util/files.d.ts +80 -0
  50. package/lib/video/plan.d.ts +96 -0
  51. package/locale/en.json +6 -0
  52. package/locale/zh.json +6 -0
  53. package/package.json +127 -0
@@ -0,0 +1,1368 @@
1
+ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require) => {
2
+ var module = { exports: {} };
3
+ var exports = module.exports;
4
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
5
+ let react = require("react");
6
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ //#region src/client/account-store.ts
9
+ const ACCOUNT_ROUTE = "/api/dsh-vibedev/account";
10
+ const PENDING_POLL_MS = 2e3;
11
+ const IDLE_POLL_MS = 6e4;
12
+ /** Who to show: the nickname, else the email. */
13
+ function displayName(view) {
14
+ return view?.user?.nickname ?? view?.user?.email;
15
+ }
16
+ /** The balance as text, e.g. `¥12.50`. */
17
+ function balanceText(view) {
18
+ const balance = view?.balance;
19
+ if (balance === void 0) return void 0;
20
+ return `${balance.currency === "CNY" ? "¥" : balance.currency === "USD" ? "$" : `${balance.currency} `}${balance.amount}`;
21
+ }
22
+ /** The account store. */
23
+ var AccountStore = class {
24
+ options;
25
+ state = {
26
+ view: void 0,
27
+ loadFailed: false,
28
+ busy: void 0,
29
+ actionError: void 0
30
+ };
31
+ listeners = /* @__PURE__ */ new Set();
32
+ timer;
33
+ reading;
34
+ stopped = true;
35
+ unsubscribeFocus;
36
+ /**
37
+ * @param options - fetch, timers, window opening and focus (test seams).
38
+ */
39
+ constructor(options = {}) {
40
+ this.options = options;
41
+ }
42
+ /** Subscribe for useSyncExternalStore. */
43
+ subscribe = (listener) => {
44
+ this.listeners.add(listener);
45
+ return () => {
46
+ this.listeners.delete(listener);
47
+ };
48
+ };
49
+ /** The current state, for useSyncExternalStore. */
50
+ getSnapshot = () => this.state;
51
+ set(next) {
52
+ this.state = {
53
+ ...this.state,
54
+ ...next
55
+ };
56
+ for (const listener of this.listeners) try {
57
+ listener();
58
+ } catch {}
59
+ }
60
+ fetch(path, init) {
61
+ return (this.options.fetch ?? globalThis.fetch)(path, init);
62
+ }
63
+ /** Start reading; returns stop. */
64
+ start() {
65
+ if (!this.stopped) return () => {
66
+ this.stop();
67
+ };
68
+ this.stopped = false;
69
+ this.unsubscribeFocus = this.options.onFocus?.(() => {
70
+ this.refresh();
71
+ });
72
+ this.refresh();
73
+ return () => {
74
+ this.stop();
75
+ };
76
+ }
77
+ stop() {
78
+ this.stopped = true;
79
+ this.unsubscribeFocus?.();
80
+ this.unsubscribeFocus = void 0;
81
+ if (this.timer !== void 0) (this.options.clearTimeout ?? ((handle) => {
82
+ globalThis.clearTimeout(handle);
83
+ }))(this.timer);
84
+ this.timer = void 0;
85
+ }
86
+ schedule() {
87
+ if (this.stopped) return;
88
+ if (this.timer !== void 0) (this.options.clearTimeout ?? ((handle) => {
89
+ globalThis.clearTimeout(handle);
90
+ }))(this.timer);
91
+ const delay = this.state.view?.pending !== void 0 ? PENDING_POLL_MS : IDLE_POLL_MS;
92
+ this.timer = (this.options.setTimeout ?? ((callback, ms) => globalThis.setTimeout(callback, ms)))(() => {
93
+ this.timer = void 0;
94
+ this.refresh();
95
+ }, delay);
96
+ }
97
+ /**
98
+ * Read the account now.
99
+ * @param fresh - ask the Host to read the balance again.
100
+ */
101
+ refresh(fresh = false) {
102
+ this.reading ??= (async () => {
103
+ try {
104
+ const response = await this.fetch(fresh ? `${ACCOUNT_ROUTE}?fresh=1` : ACCOUNT_ROUTE);
105
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
106
+ this.set({
107
+ view: await response.json(),
108
+ loadFailed: false
109
+ });
110
+ } catch {
111
+ this.set({ loadFailed: true });
112
+ } finally {
113
+ this.reading = void 0;
114
+ this.schedule();
115
+ }
116
+ })();
117
+ return this.reading;
118
+ }
119
+ /** Start a sign-in: the Host opens the system browser; when it could not, the page opens the link. */
120
+ async signIn() {
121
+ this.set({
122
+ busy: "sign-in",
123
+ actionError: void 0
124
+ });
125
+ try {
126
+ const response = await this.fetch(`${ACCOUNT_ROUTE}/sign-in`, {
127
+ method: "POST",
128
+ headers: { "content-type": "application/json" },
129
+ body: JSON.stringify({ open: true })
130
+ });
131
+ const body = await response.json().catch(() => void 0);
132
+ if (!response.ok || typeof body?.url !== "string") throw new Error(typeof body?.error?.message === "string" ? body.error.message : `HTTP ${response.status}`);
133
+ if (body.opened !== true) this.options.openWindow?.(body.url);
134
+ } catch (error) {
135
+ this.set({ actionError: {
136
+ kind: "sign-in",
137
+ message: error instanceof Error ? error.message : String(error)
138
+ } });
139
+ } finally {
140
+ this.set({ busy: void 0 });
141
+ await this.refresh();
142
+ }
143
+ }
144
+ /** Open the waiting sign-in's link again (the browser did not show it). */
145
+ openPending() {
146
+ const url = this.state.view?.pending?.url;
147
+ if (url !== void 0) this.options.openWindow?.(url);
148
+ }
149
+ /**
150
+ * Open one of the gateway's pages (top-up, usage) the page's own way.
151
+ * @param url - the page.
152
+ */
153
+ openLink(url) {
154
+ this.options.openWindow?.(url);
155
+ }
156
+ /** Stop a waiting sign-in. */
157
+ async cancel() {
158
+ await this.fetch(`${ACCOUNT_ROUTE}/cancel`, { method: "POST" }).catch(() => void 0);
159
+ await this.refresh();
160
+ }
161
+ /** Sign out of the plugin's own sign-in. */
162
+ async signOut() {
163
+ this.set({
164
+ busy: "sign-out",
165
+ actionError: void 0
166
+ });
167
+ try {
168
+ const response = await this.fetch(`${ACCOUNT_ROUTE}/sign-out`, { method: "POST" });
169
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
170
+ } catch (error) {
171
+ this.set({ actionError: {
172
+ kind: "sign-out",
173
+ message: error instanceof Error ? error.message : String(error)
174
+ } });
175
+ } finally {
176
+ this.set({ busy: void 0 });
177
+ await this.refresh();
178
+ }
179
+ }
180
+ };
181
+ //#endregion
182
+ //#region src/client/locales.ts
183
+ /**
184
+ * Copy for the dsh-vibedev pages (the plugin's settings page, the VibeDev
185
+ * account section in Settings and the sidebar status), in the two languages
186
+ * the client ships. Keys are shared; `zh` is the reference dictionary.
187
+ */
188
+ const zh = {
189
+ spending: "花费",
190
+ confirmSpending: "每次付费生成前先询问",
191
+ confirmSpendingHint: "开启后,生成图片、视频、音乐或播客之前会先请你确认,并显示模型和预估金额。默认关闭。",
192
+ output: "保存位置",
193
+ outputDir: "保存目录",
194
+ outputDirHint: "相对当前工作区。图片、视频、音频和转写稿分别放在其中的 images、videos、audio、transcripts 子目录。清空后恢复为 media。",
195
+ models: "默认媒体模型",
196
+ modelsHint: "填了模型 ID 就优先用它;留空时由 Agent 按 VibeDev 模型目录自己选。想看有哪些模型和价格,可以在对话里让 Agent 列出媒体模型。",
197
+ imageModel: "生图",
198
+ videoModel: "视频",
199
+ musicModel: "音乐",
200
+ podcastModel: "播客",
201
+ transcriptionModel: "语音转写",
202
+ modelPlaceholder: "留空自动选择",
203
+ account: "账号",
204
+ accountHint: "在「设置 → VibeDev 账号」或侧边栏底部登录 VibeDev。在 VibeDev 应用里直接使用应用已登录的账号。费用从 VibeDev 余额中扣除。",
205
+ register: "前往 VibeDev 注册或充值",
206
+ loading: "正在读取设置…",
207
+ unavailable: "插件没有在运行,暂时不能修改设置。",
208
+ readOnly: "当前的配置文件是只读的。",
209
+ saveFailed: "没有保存成功,请重试。",
210
+ save: "保存",
211
+ saving: "正在保存…",
212
+ overridden: "已修改",
213
+ reset: "恢复默认",
214
+ invalid: "这个值无效",
215
+ accountNav: "VibeDev 账号",
216
+ mediaNav: "VibeDev 媒体生成",
217
+ accountTitle: "VibeDev 账号",
218
+ accountIntro: "登录 VibeDev 后,模型选择器里会多出「{name}」一组模型(如 Claude、GPT 等),也能让 Agent 生成图片、视频和音乐。费用从你的 VibeDev 余额中扣除,和 DeepSeek 账号互不影响。",
219
+ signedOutStatus: "未登录",
220
+ signIn: "登录 VibeDev",
221
+ signingIn: "正在登录…",
222
+ pendingStatus: "已在浏览器打开 VibeDev 登录页,完成登录后这里会自动更新。",
223
+ pendingLink: "浏览器没有打开?点这里打开登录页",
224
+ cancelSignIn: "取消",
225
+ registerHint: "还没有 VibeDev 账号?",
226
+ registerLink: "去注册",
227
+ signedInAs: "已登录:{who}",
228
+ signedInPlugin: "已登录 VibeDev",
229
+ hostStatus: "已通过 VibeDev 应用登录{who}",
230
+ hostHint: "这个账号由 VibeDev 应用管理,退出请到应用左下角的账号菜单。",
231
+ keyStatus: "正在使用开发用密钥(环境变量)",
232
+ balance: "余额:{amount}",
233
+ balanceUnknown: "余额暂时读取不到",
234
+ modelsListed: "模型选择器里的「{name}」:{count} 个模型",
235
+ modelsHost: "VibeDev 应用自己的模型列表里已经有这些模型",
236
+ topUp: "充值",
237
+ usage: "用量明细",
238
+ refresh: "刷新",
239
+ signOut: "退出登录",
240
+ signOutFailed: "退出没有成功,请重试。",
241
+ signInFailed: "没能开始登录:{message}",
242
+ loadFailed: "读取 VibeDev 账号状态失败,请稍后再试。",
243
+ sidebarSignedIn: "VibeDev 已登录",
244
+ sidebarSignedOut: "登录 VibeDev",
245
+ sidebarPending: "正在登录 VibeDev…",
246
+ sidebarMenu: "VibeDev 账号",
247
+ sidebarIntro: "登录后可用 VibeDev 的模型和媒体生成",
248
+ settings: "设置",
249
+ accountMenu: "VibeDev 账号菜单"
250
+ };
251
+ const en = {
252
+ spending: "Spending",
253
+ confirmSpending: "Ask before each paid generation",
254
+ confirmSpendingHint: "When on, you are asked to confirm before an image, video, song or podcast is generated, with the model and the estimated price. Off by default.",
255
+ output: "Where results go",
256
+ outputDir: "Output folder",
257
+ outputDirHint: "Relative to the workspace. Images, videos, audio and transcripts go into its images, videos, audio and transcripts folders. Clear it to go back to media.",
258
+ models: "Default media models",
259
+ modelsHint: "A model ID entered here is used first; leave it empty to let the agent choose from the VibeDev catalog. Ask the agent to list the media models to see what is available and what it costs.",
260
+ imageModel: "Images",
261
+ videoModel: "Video",
262
+ musicModel: "Music",
263
+ podcastModel: "Podcasts",
264
+ transcriptionModel: "Speech to text",
265
+ modelPlaceholder: "Empty: chosen automatically",
266
+ account: "Account",
267
+ accountHint: "Sign in to VibeDev in Settings → VibeDev account or at the bottom of the sidebar. In the VibeDev app, the account signed in to the app is used. Everything is paid from the VibeDev balance.",
268
+ register: "Register or top up at VibeDev",
269
+ loading: "Loading settings…",
270
+ unavailable: "The plugin is not running, so its settings cannot be changed right now.",
271
+ readOnly: "The configuration is read-only.",
272
+ saveFailed: "The settings were not saved. Try again.",
273
+ save: "Save",
274
+ saving: "Saving…",
275
+ overridden: "Changed",
276
+ reset: "Reset",
277
+ invalid: "Not a valid value",
278
+ accountNav: "VibeDev account",
279
+ mediaNav: "VibeDev media",
280
+ accountTitle: "VibeDev account",
281
+ accountIntro: "Once you sign in to VibeDev, the model picker gains a \"{name}\" group (Claude, GPT and more), and the agent can generate images, video and music. Everything is paid from your VibeDev balance, separately from the DeepSeek account.",
282
+ signedOutStatus: "Not signed in",
283
+ signIn: "Sign in to VibeDev",
284
+ signingIn: "Signing in…",
285
+ pendingStatus: "The VibeDev sign-in page is open in your browser; this updates once you finish there.",
286
+ pendingLink: "Browser did not open? Open the sign-in page",
287
+ cancelSignIn: "Cancel",
288
+ registerHint: "No VibeDev account yet?",
289
+ registerLink: "Register",
290
+ signedInAs: "Signed in as {who}",
291
+ signedInPlugin: "Signed in to VibeDev",
292
+ hostStatus: "Signed in through the VibeDev app{who}",
293
+ hostHint: "The VibeDev app manages this account; sign out from the account menu at the bottom of the app's sidebar.",
294
+ keyStatus: "Using a development key from the environment",
295
+ balance: "Balance: {amount}",
296
+ balanceUnknown: "The balance could not be read right now",
297
+ modelsListed: "\"{name}\" in the model picker: {count} models",
298
+ modelsHost: "The VibeDev app already lists these models itself",
299
+ topUp: "Top up",
300
+ usage: "Usage",
301
+ refresh: "Refresh",
302
+ signOut: "Sign out",
303
+ signOutFailed: "Signing out did not work. Try again.",
304
+ signInFailed: "Could not start the sign-in: {message}",
305
+ loadFailed: "The VibeDev account could not be read. Try again later.",
306
+ sidebarSignedIn: "VibeDev signed in",
307
+ sidebarSignedOut: "Sign in to VibeDev",
308
+ sidebarPending: "Signing in to VibeDev…",
309
+ sidebarMenu: "VibeDev account",
310
+ sidebarIntro: "Sign in to use the VibeDev models and media generation",
311
+ settings: "Settings",
312
+ accountMenu: "VibeDev account menu"
313
+ };
314
+ /**
315
+ * Fill `{name}` placeholders.
316
+ * @param template - the dictionary text.
317
+ * @param values - placeholder values.
318
+ * @returns the text.
319
+ */
320
+ function fill(template, values) {
321
+ return template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
322
+ }
323
+ //#endregion
324
+ //#region src/client/AccountSection.tsx
325
+ /**
326
+ * Settings → VibeDev 账号: sign in to VibeDev, see who is signed in, the
327
+ * balance and how many VibeDev models the pickers list, top up, sign out.
328
+ * The DeepSeek account keeps its own section; this one never touches it.
329
+ */
330
+ const styles$1 = {
331
+ page: {
332
+ display: "flex",
333
+ flexDirection: "column",
334
+ gap: 16,
335
+ maxWidth: 640
336
+ },
337
+ title: {
338
+ margin: 0,
339
+ fontSize: 16,
340
+ fontWeight: 600,
341
+ lineHeight: 1.5,
342
+ color: "var(--dsw-alias-label-primary)"
343
+ },
344
+ intro: {
345
+ margin: 0,
346
+ fontSize: 13,
347
+ lineHeight: 1.7,
348
+ color: "var(--dsw-alias-label-secondary)"
349
+ },
350
+ card: {
351
+ display: "flex",
352
+ flexDirection: "column",
353
+ gap: 10,
354
+ padding: 16,
355
+ borderRadius: 12,
356
+ border: "0.5px solid var(--dsw-alias-border-l2)",
357
+ background: "var(--dsw-alias-bg-l2, transparent)"
358
+ },
359
+ statusRow: {
360
+ display: "flex",
361
+ alignItems: "center",
362
+ gap: 8,
363
+ fontSize: 14,
364
+ fontWeight: 500,
365
+ color: "var(--dsw-alias-label-primary)"
366
+ },
367
+ line: {
368
+ margin: 0,
369
+ fontSize: 13,
370
+ lineHeight: 1.6,
371
+ color: "var(--dsw-alias-label-secondary)"
372
+ },
373
+ quiet: {
374
+ margin: 0,
375
+ fontSize: 12,
376
+ lineHeight: 1.6,
377
+ color: "var(--dsw-alias-label-tertiary)"
378
+ },
379
+ actions: {
380
+ display: "flex",
381
+ flexWrap: "wrap",
382
+ gap: 8,
383
+ paddingTop: 4
384
+ },
385
+ link: {
386
+ fontSize: 13,
387
+ color: "var(--dsw-alias-state-business-primary)",
388
+ cursor: "pointer",
389
+ background: "none",
390
+ border: "none",
391
+ padding: 0,
392
+ textAlign: "left"
393
+ },
394
+ error: {
395
+ margin: 0,
396
+ fontSize: 12,
397
+ lineHeight: 1.6,
398
+ color: "var(--dsw-alias-state-error-primary, #d4380d)"
399
+ }
400
+ };
401
+ /** The status dot for a state. */
402
+ function statusDot(state) {
403
+ const view = state.view;
404
+ if (view === void 0) return "idle";
405
+ if (view.pending !== void 0) return "ongoing";
406
+ return view.source === "none" ? "idle" : "done";
407
+ }
408
+ function ExternalLink(props) {
409
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
410
+ style: styles$1.link,
411
+ href: props.href,
412
+ target: "_blank",
413
+ rel: "noreferrer",
414
+ children: props.children
415
+ });
416
+ }
417
+ /**
418
+ * The section.
419
+ * @param props - the dictionary, the shared account store and the route's display name.
420
+ * @returns the section content.
421
+ */
422
+ function AccountSection({ t, store, routeName }) {
423
+ const state = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
424
+ (0, react.useEffect)(() => {
425
+ store.refresh(true);
426
+ }, [store]);
427
+ const view = state.view;
428
+ const who = displayName(view);
429
+ const balance = balanceText(view);
430
+ let status;
431
+ let details = null;
432
+ let actions = null;
433
+ if (view === void 0) status = state.loadFailed ? t("loadFailed") : t("loading");
434
+ else if (view.pending !== void 0) {
435
+ status = t("signingIn");
436
+ details = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
437
+ style: styles$1.line,
438
+ children: t("pendingStatus")
439
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
440
+ type: "button",
441
+ style: styles$1.link,
442
+ onClick: () => {
443
+ store.openPending();
444
+ },
445
+ children: t("pendingLink")
446
+ })] });
447
+ actions = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
448
+ variant: "outline",
449
+ size: "sm",
450
+ onClick: () => {
451
+ store.cancel();
452
+ },
453
+ children: t("cancelSignIn")
454
+ });
455
+ } else if (view.source === "none") {
456
+ status = t("signedOutStatus");
457
+ actions = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
458
+ variant: "primary",
459
+ size: "sm",
460
+ disabled: state.busy !== void 0,
461
+ onClick: () => {
462
+ store.signIn();
463
+ },
464
+ children: state.busy === "sign-in" ? t("signingIn") : t("signIn")
465
+ });
466
+ details = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
467
+ style: styles$1.quiet,
468
+ children: [
469
+ t("registerHint"),
470
+ " ",
471
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExternalLink, {
472
+ href: view.links.register,
473
+ children: t("registerLink")
474
+ })
475
+ ]
476
+ });
477
+ } else {
478
+ status = view.source === "host" ? fill(t("hostStatus"), { who: who === void 0 ? "" : `:${who}` }) : view.source === "key" ? t("keyStatus") : who === void 0 ? t("signedInPlugin") : fill(t("signedInAs"), { who });
479
+ details = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
480
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
481
+ style: styles$1.line,
482
+ children: balance === void 0 ? t("balanceUnknown") : fill(t("balance"), { amount: balance })
483
+ }),
484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
485
+ style: styles$1.line,
486
+ children: view.models.hidden === "host-account" ? t("modelsHost") : fill(t("modelsListed"), {
487
+ name: routeName,
488
+ count: view.models.count
489
+ })
490
+ }),
491
+ view.source === "host" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
492
+ style: styles$1.quiet,
493
+ children: t("hostHint")
494
+ }) : null
495
+ ] });
496
+ actions = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
497
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
498
+ variant: "primary",
499
+ size: "sm",
500
+ onClick: () => {
501
+ store.openLink(view.links.topUp);
502
+ },
503
+ children: t("topUp")
504
+ }),
505
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
506
+ variant: "outline",
507
+ size: "sm",
508
+ onClick: () => {
509
+ store.openLink(view.links.usage);
510
+ },
511
+ children: t("usage")
512
+ }),
513
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
514
+ variant: "ghost",
515
+ size: "sm",
516
+ onClick: () => {
517
+ store.refresh(true);
518
+ },
519
+ children: t("refresh")
520
+ }),
521
+ view.source === "plugin" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
522
+ variant: "ghost",
523
+ size: "sm",
524
+ disabled: state.busy !== void 0,
525
+ onClick: () => {
526
+ store.signOut();
527
+ },
528
+ children: t("signOut")
529
+ }) : null
530
+ ] });
531
+ }
532
+ const error = state.actionError === void 0 ? void 0 : state.actionError.kind === "sign-in" ? fill(t("signInFailed"), { message: state.actionError.message }) : t("signOutFailed");
533
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
534
+ style: styles$1.page,
535
+ "data-dsh-vibedev-account": true,
536
+ children: [
537
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
538
+ style: styles$1.title,
539
+ children: t("accountTitle")
540
+ }),
541
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
542
+ style: styles$1.intro,
543
+ children: fill(t("accountIntro"), { name: routeName })
544
+ }),
545
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
546
+ style: styles$1.card,
547
+ children: [
548
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
549
+ style: styles$1.statusRow,
550
+ role: "status",
551
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
552
+ state: statusDot(state),
553
+ size: 8
554
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: status })]
555
+ }),
556
+ details,
557
+ actions === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
558
+ style: styles$1.actions,
559
+ children: actions
560
+ }),
561
+ error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
562
+ style: styles$1.error,
563
+ role: "alert",
564
+ children: error
565
+ })
566
+ ]
567
+ })
568
+ ]
569
+ });
570
+ }
571
+ //#endregion
572
+ //#region src/client/fields.ts
573
+ /** The settings namespace: the plugin's profile entry id. */
574
+ const SETTINGS_NAMESPACE = "dsh-vibedev";
575
+ /** The model slots, in page order. */
576
+ const MODEL_FIELDS = [
577
+ "imageModel",
578
+ "videoModel",
579
+ "musicModel",
580
+ "podcastModel",
581
+ "transcriptionModel"
582
+ ];
583
+ /** Every field the page edits. */
584
+ const FIELDS = [
585
+ "confirmSpending",
586
+ "outputDir",
587
+ ...MODEL_FIELDS
588
+ ];
589
+ /**
590
+ * A switch field: the staged text is `true` or `false`, and saving always
591
+ * writes the boolean (a switch has no empty state to clear with).
592
+ * @param field - field name inside the namespace section.
593
+ * @returns the field's conversion spec.
594
+ */
595
+ function booleanField(field) {
596
+ return {
597
+ field,
598
+ format: (value) => value === true ? "true" : "false",
599
+ parse: (text) => ({
600
+ kind: "set",
601
+ value: text.trim() === "true"
602
+ })
603
+ };
604
+ }
605
+ /**
606
+ * A free-text field: an empty draft clears the field, so it falls back to the
607
+ * default (`media` for the folder, automatic choice for a model).
608
+ * @param field - field name inside the namespace section.
609
+ * @returns the field's conversion spec.
610
+ */
611
+ function textField(field) {
612
+ return {
613
+ field,
614
+ format: (value) => typeof value === "string" ? value : "",
615
+ parse: (text) => {
616
+ const trimmed = text.trim();
617
+ return trimmed === "" ? { kind: "clear" } : {
618
+ kind: "set",
619
+ value: trimmed
620
+ };
621
+ }
622
+ };
623
+ }
624
+ /** The specs of every field the page edits. */
625
+ function fieldSpecs() {
626
+ return [
627
+ booleanField("confirmSpending"),
628
+ textField("outputDir"),
629
+ ...MODEL_FIELDS.map(textField)
630
+ ];
631
+ }
632
+ //#endregion
633
+ //#region src/client/SettingsPage.tsx
634
+ /**
635
+ * The dsh-vibedev settings page on the Plugins page: spending confirmation, the
636
+ * output folder, the pinned models, and how the account works. Edits are
637
+ * staged and written together by the save, like every settings page there.
638
+ */
639
+ /** Where new users register and everyone tops up. */
640
+ const VIBEDEV_SITE = "https://vibedev.jzsaas.com";
641
+ const styles = {
642
+ section: {
643
+ padding: "12px 0",
644
+ borderTop: "0.5px solid var(--dsw-alias-border-l2)"
645
+ },
646
+ firstSection: { padding: "0 0 12px" },
647
+ sectionTitle: {
648
+ margin: "0 0 2px",
649
+ fontSize: 12,
650
+ fontWeight: 600,
651
+ lineHeight: 1.5,
652
+ color: "var(--dsw-alias-label-secondary)"
653
+ },
654
+ sectionHint: {
655
+ margin: "0 0 4px",
656
+ fontSize: 12,
657
+ lineHeight: 1.6,
658
+ color: "var(--dsw-alias-label-tertiary)"
659
+ },
660
+ switchRow: {
661
+ display: "flex",
662
+ alignItems: "center",
663
+ gap: 16,
664
+ padding: "10px 0 0"
665
+ },
666
+ switchText: {
667
+ display: "flex",
668
+ flexDirection: "column",
669
+ gap: 4,
670
+ flex: 1,
671
+ minWidth: 0
672
+ },
673
+ label: {
674
+ fontSize: 13,
675
+ fontWeight: 500,
676
+ lineHeight: 1.5,
677
+ color: "var(--dsw-alias-label-primary)"
678
+ },
679
+ hint: {
680
+ margin: 0,
681
+ fontSize: 12,
682
+ lineHeight: 1.6,
683
+ color: "var(--dsw-alias-label-secondary)"
684
+ },
685
+ link: {
686
+ display: "inline-block",
687
+ marginTop: 8,
688
+ fontSize: 12,
689
+ lineHeight: 1.5,
690
+ color: "var(--dsw-alias-state-business-primary)"
691
+ },
692
+ quiet: {
693
+ margin: 0,
694
+ fontSize: 12,
695
+ lineHeight: 1.5,
696
+ color: "var(--dsw-alias-label-tertiary)"
697
+ }
698
+ };
699
+ function Section(props) {
700
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
701
+ style: props.first === true ? styles.firstSection : styles.section,
702
+ children: [
703
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
704
+ style: styles.sectionTitle,
705
+ children: props.title
706
+ }),
707
+ props.hint === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
708
+ style: styles.sectionHint,
709
+ children: props.hint
710
+ }),
711
+ props.children
712
+ ]
713
+ });
714
+ }
715
+ /**
716
+ * Render the settings page.
717
+ * @param props - the dictionary, the form store and its actions.
718
+ * @returns the page.
719
+ */
720
+ function MediaSettingsPage({ t, store, actions }) {
721
+ const state = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
722
+ if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
723
+ style: styles.quiet,
724
+ role: "status",
725
+ children: t("loading")
726
+ });
727
+ const disabled = !state.shell.writable || state.shell.saving;
728
+ const textField = (field) => ({
729
+ text: state.fields[field].text,
730
+ overridden: state.fields[field].overridden,
731
+ invalid: state.fields[field].invalid,
732
+ overriddenLabel: t("overridden"),
733
+ resetLabel: t("reset"),
734
+ invalidLabel: t("invalid"),
735
+ disabled,
736
+ onEdit: (text) => {
737
+ actions.edit(field, text);
738
+ },
739
+ onReset: () => {
740
+ actions.resetField(field);
741
+ }
742
+ });
743
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.SettingsForm, {
744
+ labels: {
745
+ unavailable: t("unavailable"),
746
+ readOnly: t("readOnly"),
747
+ saveFailed: t("saveFailed"),
748
+ save: t("save"),
749
+ saving: t("saving")
750
+ },
751
+ state: state.shell,
752
+ onSave: actions.save,
753
+ onDiscard: actions.discard,
754
+ children: [
755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
756
+ first: true,
757
+ title: t("spending"),
758
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
759
+ style: styles.switchRow,
760
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
761
+ style: styles.switchText,
762
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
763
+ style: styles.label,
764
+ children: t("confirmSpending")
765
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
766
+ style: styles.hint,
767
+ children: t("confirmSpendingHint")
768
+ })]
769
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
770
+ checked: state.fields.confirmSpending.text === "true",
771
+ onChange: (next) => {
772
+ actions.edit("confirmSpending", next ? "true" : "false");
773
+ },
774
+ label: t("confirmSpending"),
775
+ disabled
776
+ })]
777
+ })
778
+ }),
779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
780
+ title: t("output"),
781
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SettingsValueField, {
782
+ id: "dsh-vibedev-output-dir",
783
+ label: t("outputDir"),
784
+ hint: t("outputDirHint"),
785
+ placeholder: "media",
786
+ ...textField("outputDir")
787
+ })
788
+ }),
789
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
790
+ title: t("models"),
791
+ hint: t("modelsHint"),
792
+ children: MODEL_FIELDS.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SettingsValueField, {
793
+ id: `dsh-vibedev-${field}`,
794
+ label: t(field),
795
+ placeholder: t("modelPlaceholder"),
796
+ ...textField(field)
797
+ }, field))
798
+ }),
799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
800
+ title: t("account"),
801
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
802
+ style: styles.hint,
803
+ children: t("accountHint")
804
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
805
+ style: styles.link,
806
+ href: VIBEDEV_SITE,
807
+ target: "_blank",
808
+ rel: "noreferrer",
809
+ children: t("register")
810
+ })]
811
+ })
812
+ ]
813
+ });
814
+ }
815
+ //#endregion
816
+ //#region src/client/PrimaryLauncher.tsx
817
+ /**
818
+ * The VibeDev account as the app's main account (`settings.launcher`, the row
819
+ * at the very foot of the sidebar), used where the VibeDev account leads (the
820
+ * VibeDev app, `primary: true`). Signed in: the person's initial, name and
821
+ * balance, with a menu for Settings, top-up, usage and sign-out. Signed out:
822
+ * "登录 VibeDev". It opens Settings the way the launcher it replaces does.
823
+ * Inside DeepSeek Harness the plugin keeps to its smaller sidebar entry
824
+ * (SidebarAccount.tsx) and this is not registered.
825
+ */
826
+ const row = {
827
+ display: "flex",
828
+ alignItems: "center",
829
+ gap: 10,
830
+ width: "100%",
831
+ padding: "6px 10px",
832
+ border: "none",
833
+ borderRadius: 10,
834
+ background: "transparent",
835
+ cursor: "pointer",
836
+ textAlign: "left",
837
+ color: "var(--dsw-alias-label-primary)"
838
+ };
839
+ const signedInRow = {
840
+ ...row,
841
+ minHeight: 44
842
+ };
843
+ const signedOutRow = {
844
+ ...row,
845
+ minHeight: 32,
846
+ color: "var(--dsw-alias-label-secondary)",
847
+ fontSize: 13
848
+ };
849
+ const railRow = {
850
+ ...row,
851
+ justifyContent: "center",
852
+ width: 36,
853
+ height: 36,
854
+ minHeight: 36,
855
+ padding: 0
856
+ };
857
+ const avatar = {
858
+ flex: "none",
859
+ display: "grid",
860
+ placeItems: "center",
861
+ width: 28,
862
+ height: 28,
863
+ borderRadius: "50%",
864
+ background: "var(--dsw-alias-brand-primary, #3b82f6)",
865
+ color: "#fff",
866
+ fontSize: 13,
867
+ fontWeight: 600,
868
+ lineHeight: 1
869
+ };
870
+ const lines = {
871
+ display: "flex",
872
+ flexDirection: "column",
873
+ minWidth: 0,
874
+ gap: 1
875
+ };
876
+ const ellipsis = {
877
+ overflow: "hidden",
878
+ textOverflow: "ellipsis",
879
+ whiteSpace: "nowrap"
880
+ };
881
+ const nameText = {
882
+ ...ellipsis,
883
+ fontSize: 13,
884
+ fontWeight: 500
885
+ };
886
+ const subText = {
887
+ ...ellipsis,
888
+ fontSize: 12,
889
+ color: "var(--dsw-alias-label-secondary)"
890
+ };
891
+ /**
892
+ * The first letter to show in the avatar.
893
+ * @param who - nickname or email.
894
+ * @returns one uppercase character, or "V".
895
+ */
896
+ function initialOf(who) {
897
+ const first = who?.trim().match(/\p{L}|\p{N}/u)?.[0];
898
+ return first === void 0 ? "V" : first.toUpperCase();
899
+ }
900
+ /**
901
+ * The main account row.
902
+ * @param props - the dictionary, the shared account store, and the launcher's owner props.
903
+ * @returns the row with its menu.
904
+ */
905
+ function PrimaryLauncher({ t, store, owner }) {
906
+ const state = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
907
+ const [open, setOpen] = (0, react.useState)(false);
908
+ const wide = owner.wide !== false;
909
+ const settingsWasOpen = (0, react.useRef)(false);
910
+ (0, react.useEffect)(() => {
911
+ if (owner.settingsOpen === true && !settingsWasOpen.current) store.refresh(true);
912
+ settingsWasOpen.current = owner.settingsOpen === true;
913
+ }, [owner.settingsOpen, store]);
914
+ const view = state.view;
915
+ const signedIn = view !== void 0 && view.source !== "none";
916
+ const pending = !signedIn && view?.pending !== void 0;
917
+ const who = displayName(view);
918
+ const balance = balanceText(view);
919
+ const title = signedIn ? who ?? t("sidebarSignedIn") : pending ? t("sidebarPending") : t("signIn");
920
+ const settings = {
921
+ id: "settings",
922
+ label: t("settings"),
923
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutlineMedium, { size: 16 }),
924
+ ...owner.settingsShortcut === void 0 ? {} : { shortcut: owner.settingsShortcut }
925
+ };
926
+ const items = signedIn ? [
927
+ {
928
+ type: "label",
929
+ id: "who",
930
+ text: who === void 0 ? t("signedInPlugin") : fill(t("signedInAs"), { who })
931
+ },
932
+ ...balance === void 0 ? [] : [{
933
+ type: "label",
934
+ id: "balance",
935
+ text: fill(t("balance"), { amount: balance })
936
+ }],
937
+ {
938
+ type: "separator",
939
+ id: "separator-account"
940
+ },
941
+ settings,
942
+ {
943
+ id: "top-up",
944
+ label: t("topUp")
945
+ },
946
+ {
947
+ id: "usage",
948
+ label: t("usage")
949
+ },
950
+ ...view.source === "plugin" ? [{
951
+ type: "separator",
952
+ id: "separator-sign-out"
953
+ }, {
954
+ id: "sign-out",
955
+ label: t("signOut"),
956
+ danger: true,
957
+ disabled: state.busy !== void 0
958
+ }] : []
959
+ ] : pending ? [
960
+ {
961
+ type: "label",
962
+ id: "pending",
963
+ text: t("pendingStatus")
964
+ },
965
+ {
966
+ id: "open-pending",
967
+ label: t("pendingLink")
968
+ },
969
+ {
970
+ id: "cancel",
971
+ label: t("cancelSignIn")
972
+ },
973
+ {
974
+ type: "separator",
975
+ id: "separator-settings"
976
+ },
977
+ settings
978
+ ] : [
979
+ {
980
+ type: "label",
981
+ id: "intro",
982
+ text: t("sidebarIntro")
983
+ },
984
+ {
985
+ id: "sign-in",
986
+ label: t("signIn"),
987
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUserOutlineMedium, { size: 16 }),
988
+ disabled: state.busy !== void 0
989
+ },
990
+ {
991
+ id: "register",
992
+ label: `${t("registerHint")} ${t("registerLink")}`
993
+ },
994
+ {
995
+ type: "separator",
996
+ id: "separator-settings"
997
+ },
998
+ settings
999
+ ];
1000
+ const face = signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1001
+ style: avatar,
1002
+ "aria-hidden": "true",
1003
+ children: initialOf(who)
1004
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUserOutlineMedium, { size: 16 });
1005
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1006
+ open,
1007
+ side: "top",
1008
+ align: "start",
1009
+ portal: true,
1010
+ autoFocus: true,
1011
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1012
+ type: "button",
1013
+ style: !wide ? railRow : signedIn ? signedInRow : signedOutRow,
1014
+ "aria-label": wide ? t("accountMenu") : title,
1015
+ title: wide ? void 0 : title,
1016
+ "aria-haspopup": "menu",
1017
+ "aria-expanded": open,
1018
+ "data-dsh-vibedev-launcher": true,
1019
+ "data-signed-in": signedIn,
1020
+ onClick: () => {
1021
+ setOpen((value) => !value);
1022
+ },
1023
+ children: [face, wide ? signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1024
+ style: lines,
1025
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1026
+ style: nameText,
1027
+ children: who ?? t("sidebarSignedIn")
1028
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1029
+ style: subText,
1030
+ children: balance === void 0 ? "VibeDev" : `VibeDev · ${balance}`
1031
+ })]
1032
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1033
+ style: ellipsis,
1034
+ children: title
1035
+ }) : null]
1036
+ }),
1037
+ items,
1038
+ onClose: () => {
1039
+ setOpen(false);
1040
+ },
1041
+ onSelect: (id) => {
1042
+ setOpen(false);
1043
+ if (id === "settings") owner.openSettings?.();
1044
+ else if (id === "sign-in") store.signIn();
1045
+ else if (id === "sign-out") store.signOut();
1046
+ else if (id === "cancel") store.cancel();
1047
+ else if (id === "open-pending") store.openPending();
1048
+ else if (view === void 0) return;
1049
+ else if (id === "top-up") store.openLink(view.links.topUp);
1050
+ else if (id === "usage") store.openLink(view.links.usage);
1051
+ else if (id === "register") store.openLink(view.links.register);
1052
+ }
1053
+ });
1054
+ }
1055
+ //#endregion
1056
+ //#region src/client/SidebarAccount.tsx
1057
+ /**
1058
+ * The sidebar foot's VibeDev status (`sidebar.footer.action`): "登录 VibeDev",
1059
+ * "正在登录 VibeDev…" or "VibeDev 已登录", with a menu for the account. The
1060
+ * Harness's own account entry (the DeepSeek account) stays where it is; in
1061
+ * the VibeDev app, whose account entry already is the VibeDev account, this
1062
+ * shows nothing.
1063
+ */
1064
+ const trigger = {
1065
+ display: "flex",
1066
+ alignItems: "center",
1067
+ gap: 8,
1068
+ width: "100%",
1069
+ minHeight: 32,
1070
+ padding: "0 10px",
1071
+ border: "none",
1072
+ borderRadius: 8,
1073
+ background: "transparent",
1074
+ cursor: "pointer",
1075
+ textAlign: "left",
1076
+ fontSize: 13,
1077
+ lineHeight: 1.4,
1078
+ color: "var(--dsw-alias-label-secondary)"
1079
+ };
1080
+ const railTrigger = {
1081
+ ...trigger,
1082
+ justifyContent: "center",
1083
+ padding: 0
1084
+ };
1085
+ const label = {
1086
+ minWidth: 0,
1087
+ overflow: "hidden",
1088
+ textOverflow: "ellipsis",
1089
+ whiteSpace: "nowrap"
1090
+ };
1091
+ /**
1092
+ * The status entry.
1093
+ * @param props - the dictionary, the shared account store, and whether the sidebar is wide.
1094
+ * @returns the entry, or nothing until the account is known and inside the VibeDev app.
1095
+ */
1096
+ function SidebarAccount({ t, store, wide }) {
1097
+ const state = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
1098
+ const [open, setOpen] = (0, react.useState)(false);
1099
+ const view = state.view;
1100
+ if (view === void 0 || view.source === "host") return null;
1101
+ const signedIn = view.source === "plugin" || view.source === "key";
1102
+ const pending = !signedIn && view.pending !== void 0;
1103
+ const text = pending ? t("sidebarPending") : signedIn ? t("sidebarSignedIn") : t("sidebarSignedOut");
1104
+ const who = displayName(view);
1105
+ const balance = balanceText(view);
1106
+ const items = signedIn ? [
1107
+ {
1108
+ type: "label",
1109
+ id: "who",
1110
+ text: who === void 0 ? t("signedInPlugin") : fill(t("signedInAs"), { who })
1111
+ },
1112
+ ...balance === void 0 ? [] : [{
1113
+ type: "label",
1114
+ id: "balance",
1115
+ text: fill(t("balance"), { amount: balance })
1116
+ }],
1117
+ {
1118
+ type: "separator",
1119
+ id: "separator"
1120
+ },
1121
+ {
1122
+ id: "top-up",
1123
+ label: t("topUp")
1124
+ },
1125
+ {
1126
+ id: "usage",
1127
+ label: t("usage")
1128
+ },
1129
+ ...view.source === "plugin" ? [{
1130
+ id: "sign-out",
1131
+ label: t("signOut"),
1132
+ danger: true,
1133
+ disabled: state.busy !== void 0
1134
+ }] : []
1135
+ ] : pending ? [
1136
+ {
1137
+ type: "label",
1138
+ id: "pending",
1139
+ text: t("pendingStatus")
1140
+ },
1141
+ {
1142
+ id: "open-pending",
1143
+ label: t("pendingLink")
1144
+ },
1145
+ {
1146
+ id: "cancel",
1147
+ label: t("cancelSignIn")
1148
+ }
1149
+ ] : [
1150
+ {
1151
+ type: "label",
1152
+ id: "intro",
1153
+ text: t("sidebarIntro")
1154
+ },
1155
+ {
1156
+ id: "sign-in",
1157
+ label: t("signIn"),
1158
+ disabled: state.busy !== void 0
1159
+ },
1160
+ {
1161
+ id: "register",
1162
+ label: `${t("registerHint")} ${t("registerLink")}`
1163
+ }
1164
+ ];
1165
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1166
+ open,
1167
+ side: "top",
1168
+ align: "start",
1169
+ portal: true,
1170
+ autoFocus: true,
1171
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1172
+ type: "button",
1173
+ style: wide ? trigger : railTrigger,
1174
+ "aria-label": wide ? void 0 : text,
1175
+ title: wide ? void 0 : text,
1176
+ "aria-haspopup": "menu",
1177
+ "aria-expanded": open,
1178
+ "data-dsh-vibedev-sidebar": true,
1179
+ onClick: () => {
1180
+ setOpen((value) => !value);
1181
+ },
1182
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
1183
+ state: statusDot(state),
1184
+ size: 8
1185
+ }), wide ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1186
+ style: label,
1187
+ children: text
1188
+ }) : null]
1189
+ }),
1190
+ items,
1191
+ onClose: () => {
1192
+ setOpen(false);
1193
+ },
1194
+ onSelect: (id) => {
1195
+ setOpen(false);
1196
+ if (id === "sign-in") store.signIn();
1197
+ else if (id === "sign-out") store.signOut();
1198
+ else if (id === "cancel") store.cancel();
1199
+ else if (id === "open-pending") store.openPending();
1200
+ else if (id === "top-up") store.openLink(view.links.topUp);
1201
+ else if (id === "usage") store.openLink(view.links.usage);
1202
+ else if (id === "register") store.openLink(view.links.register);
1203
+ }
1204
+ });
1205
+ }
1206
+ //#endregion
1207
+ //#region src/client/index.ts
1208
+ /**
1209
+ * dsh-vibedev browser half:
1210
+ *
1211
+ * - Settings → VibeDev 账号 (`settings.section`): sign in to VibeDev, who is
1212
+ * signed in, the balance, the VibeDev models in the pickers, top up, sign out;
1213
+ * - the sidebar foot's VibeDev status (`sidebar.footer.action`), or, where the
1214
+ * VibeDev account is the app's main account (the VibeDev app), the account row
1215
+ * at the very foot (`settings.launcher`) and the first Settings section;
1216
+ * - the plugin's settings page on the Plugins page (`plugins.bundle.config`,
1217
+ * under the bundle's package name, while the Host serves the plugin's
1218
+ * settings namespace), edited through the shared configuration form.
1219
+ *
1220
+ * The section and the sidebar share one account store, which reads the Host's
1221
+ * `/api/dsh-vibedev/account` route. The DeepSeek account keeps the Harness's
1222
+ * own section and sidebar entry; nothing here touches it.
1223
+ *
1224
+ * Built by tsdown into the `__ModuleLoader__` factory bundle at
1225
+ * client/client.js; React and the client primitives come from the Host's
1226
+ * module table. The Host surfaces used here are typed structurally, so this
1227
+ * external package depends on no monorepo-internal types.
1228
+ */
1229
+ /** The package name the Plugins page keys a bundle's configuration by. */
1230
+ const PACKAGE_NAME = "@vibedev-si/dsh-vibedev";
1231
+ /** The dictionary namespace this plugin owns. */
1232
+ const LOCALE_NAMESPACE = "dsh-vibedev";
1233
+ /** The name the model pickers show for the VibeDev models (the Host config's default `displayName`). */
1234
+ const ROUTE_NAME = "VibeDev";
1235
+ /** Where the section sits in Settings: after the DeepSeek account (-10), before General (0). */
1236
+ const SECTION_ORDER = -5;
1237
+ /** As the app's main account: before the DeepSeek account's section, so Settings opens on it. */
1238
+ const PRIMARY_SECTION_ORDER = -20;
1239
+ /** Below the default the DeepSeek account launcher registers with, so this row is the one the foot shows. */
1240
+ const LAUNCHER_PRIORITY = -1;
1241
+ const disposer = (value) => typeof value === "function" ? value : () => {};
1242
+ const name = "dsh-vibedev";
1243
+ const inject = ["slots", "locale"];
1244
+ /**
1245
+ * The browser's own ways to open a page and to hear the window come back.
1246
+ * @returns the store options for this window.
1247
+ */
1248
+ function windowOptions() {
1249
+ return {
1250
+ openWindow: (url) => {
1251
+ window.open(url, "_blank", "noopener,noreferrer");
1252
+ },
1253
+ onFocus: (listener) => {
1254
+ window.addEventListener("focus", listener);
1255
+ return () => {
1256
+ window.removeEventListener("focus", listener);
1257
+ };
1258
+ }
1259
+ };
1260
+ }
1261
+ /**
1262
+ * Register the account section, the sidebar status and the settings page.
1263
+ * @param ctx - the browser plugin context.
1264
+ */
1265
+ function apply(ctx) {
1266
+ ctx.effect(() => ctx.locale.register(LOCALE_NAMESPACE, {
1267
+ zh,
1268
+ en
1269
+ }), "dsh-vibedev: dictionaries");
1270
+ const t = ctx.locale.bind(LOCALE_NAMESPACE);
1271
+ const account = new AccountStore(windowOptions());
1272
+ ctx.effect(() => account.start(), "dsh-vibedev: account reads");
1273
+ const section = (order) => disposer(ctx.slots.inject("settings.section", () => ctx.slots.register({
1274
+ name: "settings.section",
1275
+ id: "vibedev-account",
1276
+ order,
1277
+ label: () => t("accountNav"),
1278
+ locale: LOCALE_NAMESPACE
1279
+ }, () => (0, react.createElement)(AccountSection, {
1280
+ t,
1281
+ store: account,
1282
+ routeName: ROUTE_NAME
1283
+ }))));
1284
+ const launcher = () => disposer(ctx.slots.inject("settings.launcher", () => ctx.slots.register({
1285
+ name: "settings.launcher",
1286
+ priority: LAUNCHER_PRIORITY,
1287
+ locale: LOCALE_NAMESPACE
1288
+ }, (owner = {}) => (0, react.createElement)(PrimaryLauncher, {
1289
+ t,
1290
+ store: account,
1291
+ owner
1292
+ }))));
1293
+ const status = () => disposer(ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
1294
+ name: "sidebar.footer.action",
1295
+ id: "vibedev-account",
1296
+ locale: LOCALE_NAMESPACE
1297
+ }, (owner = {}) => (0, react.createElement)(SidebarAccount, {
1298
+ t,
1299
+ store: account,
1300
+ wide: owner.wide !== false
1301
+ }))));
1302
+ ctx.effect(() => {
1303
+ let placed;
1304
+ const place = () => {
1305
+ const { view, loadFailed } = account.getSnapshot();
1306
+ if (placed !== void 0 || view === void 0 && !loadFailed) return;
1307
+ placed = view?.primary === true ? [section(PRIMARY_SECTION_ORDER), launcher()] : [section(SECTION_ORDER), status()];
1308
+ };
1309
+ const unsubscribe = account.subscribe(place);
1310
+ place();
1311
+ return () => {
1312
+ unsubscribe();
1313
+ for (const off of placed ?? []) off();
1314
+ };
1315
+ }, "dsh-vibedev: account placement");
1316
+ ctx.inject(["configForms"], (scoped) => {
1317
+ scoped.effect(() => scoped.configForms.whileServed([SETTINGS_NAMESPACE], () => {
1318
+ const scope = scoped.configForms.get(SETTINGS_NAMESPACE);
1319
+ const model = new _deepseek_ai_dsh_client_ui_primitives.SettingsFormModel(scope, fieldSpecs());
1320
+ const store = model.bind(() => ({
1321
+ status: scope.getSnapshot().status,
1322
+ shell: model.shell(),
1323
+ fields: Object.fromEntries(FIELDS.map((field) => [field, model.field(field)]))
1324
+ }));
1325
+ const actions = model.actions();
1326
+ const off = scoped.slots.inject("plugins.bundle.config", () => scoped.slots.register({
1327
+ name: "plugins.bundle.config",
1328
+ key: PACKAGE_NAME,
1329
+ locale: LOCALE_NAMESPACE,
1330
+ inject: () => ({ t })
1331
+ }, (owner = {}) => owner.view === "summary" ? null : (0, react.createElement)(MediaSettingsPage, {
1332
+ t,
1333
+ store,
1334
+ actions
1335
+ })));
1336
+ let section;
1337
+ const placeSection = () => {
1338
+ if (section !== void 0 || account.getSnapshot().view?.primary !== true) return;
1339
+ section = disposer(scoped.slots.inject("settings.section", () => scoped.slots.register({
1340
+ name: "settings.section",
1341
+ id: "vibedev-media",
1342
+ order: -19,
1343
+ label: () => t("mediaNav"),
1344
+ locale: LOCALE_NAMESPACE
1345
+ }, () => (0, react.createElement)(MediaSettingsPage, {
1346
+ t,
1347
+ store,
1348
+ actions
1349
+ }))));
1350
+ };
1351
+ const unsubscribe = account.subscribe(placeSection);
1352
+ placeSection();
1353
+ return () => {
1354
+ unsubscribe();
1355
+ section?.();
1356
+ disposer(off)();
1357
+ model.dispose();
1358
+ };
1359
+ }), "dsh-vibedev: settings page");
1360
+ });
1361
+ }
1362
+ //#endregion
1363
+ exports.apply = apply;
1364
+ exports.inject = inject;
1365
+ exports.name = name;
1366
+ return module.exports;
1367
+ }
1368
+ });