@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.
- package/LICENSE +21 -0
- package/README.md +155 -0
- package/THIRD-PARTY-NOTICES.txt +566 -0
- package/client/client.js +1368 -0
- package/cordis.patch.yml +5 -0
- package/icon.svg +7 -0
- package/lib/account/index.d.ts +118 -0
- package/lib/anthropic-messages-KOS0z8Fy.js +13472 -0
- package/lib/auth/credentials.d.ts +47 -0
- package/lib/auth/login.d.ts +154 -0
- package/lib/gateway/assets.d.ts +61 -0
- package/lib/gateway/catalog.d.ts +126 -0
- package/lib/gateway/errors.d.ts +76 -0
- package/lib/gateway/http.d.ts +94 -0
- package/lib/index.d.ts +105 -0
- package/lib/index.js +7301 -0
- package/lib/llm/adapter.d.ts +129 -0
- package/lib/llm/catalog.d.ts +75 -0
- package/lib/llm/context.d.ts +54 -0
- package/lib/llm/index.d.ts +69 -0
- package/lib/llm/limits.d.ts +19 -0
- package/lib/llm/models.d.ts +67 -0
- package/lib/llm/replay.d.ts +66 -0
- package/lib/llm/stream.d.ts +46 -0
- package/lib/media/chat.d.ts +49 -0
- package/lib/media/sources.d.ts +46 -0
- package/lib/models-CAcLevEN.js +1045 -0
- package/lib/node-Cujw_a4_.js +2403 -0
- package/lib/node-vkDR3GtK.js +25 -0
- package/lib/openai-completions-DqTCiqpS.js +983 -0
- package/lib/openai-prompt-cache-kzRZVokq.js +9567 -0
- package/lib/openai-responses-DygWnsye.js +832 -0
- package/lib/pricing.d.ts +49 -0
- package/lib/runtime.d.ts +150 -0
- package/lib/search/provider.d.ts +86 -0
- package/lib/service.d.ts +99 -0
- package/lib/tasks/remote.d.ts +47 -0
- package/lib/tasks/store.d.ts +132 -0
- package/lib/tasks/tracker.d.ts +127 -0
- package/lib/tools/account.d.ts +21 -0
- package/lib/tools/audio.d.ts +13 -0
- package/lib/tools/common.d.ts +77 -0
- package/lib/tools/image.d.ts +50 -0
- package/lib/tools/models.d.ts +13 -0
- package/lib/tools/tasks.d.ts +13 -0
- package/lib/tools/transcribe.d.ts +19 -0
- package/lib/tools/video.d.ts +48 -0
- package/lib/transform-messages-Csvf2ItW.js +1023 -0
- package/lib/util/files.d.ts +80 -0
- package/lib/video/plan.d.ts +96 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +127 -0
package/client/client.js
ADDED
|
@@ -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
|
+
});
|