@huanlin/dsh-plugin-copilot 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,788 @@
1
+ window.__ModuleLoader__.load({ id: "@huanlin/dsh-plugin-copilot", factory: (require) => {
2
+ var module = { exports: {} }; var exports = module.exports;
3
+ //#region rolldown:runtime
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
21
+ value: mod,
22
+ enumerable: true
23
+ }) : target, mod));
24
+
25
+ //#endregion
26
+ let react = require("react");
27
+ react = __toESM(react);
28
+ let react_jsx_runtime = require("react/jsx-runtime");
29
+ react_jsx_runtime = __toESM(react_jsx_runtime);
30
+ let __deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
31
+ __deepseek_ai_dsh_client_store = __toESM(__deepseek_ai_dsh_client_store);
32
+
33
+ //#region src/client/CopilotAuthCard.tsx
34
+ const cardStyle = {
35
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.22))",
36
+ background: "var(--dsw-alias-bg-layer-3, transparent)",
37
+ borderRadius: 12,
38
+ listStyle: "none"
39
+ };
40
+ const headerStyle = {
41
+ width: "100%",
42
+ font: "inherit",
43
+ color: "inherit",
44
+ textAlign: "left",
45
+ cursor: "pointer",
46
+ background: "transparent",
47
+ border: 0,
48
+ borderRadius: 12,
49
+ alignItems: "center",
50
+ gap: 12,
51
+ padding: "14px 16px",
52
+ display: "flex",
53
+ boxSizing: "border-box"
54
+ };
55
+ const headTextStyle = {
56
+ flexDirection: "column",
57
+ flex: 1,
58
+ gap: 4,
59
+ minWidth: 0,
60
+ display: "flex"
61
+ };
62
+ const titleStyle = {
63
+ color: "var(--dsw-alias-label-primary, inherit)",
64
+ fontSize: 15,
65
+ fontWeight: 600,
66
+ lineHeight: 1.4
67
+ };
68
+ const descStyle = {
69
+ color: "var(--dsw-alias-label-tertiary, rgba(128,128,128,0.7))",
70
+ fontSize: 13,
71
+ lineHeight: 1.5
72
+ };
73
+ const badgeStyle = (tone) => ({
74
+ whiteSpace: "nowrap",
75
+ background: tone === "ok" ? "var(--dsw-alias-state-success-bg, rgba(48,209,88,0.14))" : "var(--dsw-alias-bg-module-platform, rgba(128,128,128,0.12))",
76
+ color: tone === "ok" ? "var(--dsw-alias-state-success-primary, #30d158)" : "var(--dsw-alias-label-secondary, inherit)",
77
+ borderRadius: 999,
78
+ flex: "none",
79
+ padding: "1px 8px",
80
+ fontSize: 11,
81
+ fontWeight: 500,
82
+ lineHeight: "17px"
83
+ });
84
+ const bodyStyle = {
85
+ borderTop: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.22))",
86
+ margin: "0 16px",
87
+ padding: "12px 0 4px",
88
+ display: "flex",
89
+ flexDirection: "column",
90
+ gap: 12
91
+ };
92
+ const noticeStyle = {
93
+ color: "var(--dsw-alias-label-secondary, inherit)",
94
+ margin: 0,
95
+ fontSize: 12,
96
+ lineHeight: 1.6
97
+ };
98
+ const errorStyle = {
99
+ color: "var(--dsw-alias-label-error, #ff453a)",
100
+ margin: 0,
101
+ fontSize: 12,
102
+ lineHeight: 1.6
103
+ };
104
+ const successStyle = {
105
+ color: "var(--dsw-alias-state-success-primary, #30d158)",
106
+ margin: 0,
107
+ fontSize: 12,
108
+ lineHeight: 1.6
109
+ };
110
+ const codeRowStyle = {
111
+ alignItems: "center",
112
+ gap: 10,
113
+ display: "flex",
114
+ flexWrap: "wrap"
115
+ };
116
+ const codeStyle = {
117
+ fontFamily: "var(--dsw-font-mono, ui-monospace, monospace)",
118
+ fontSize: 20,
119
+ fontWeight: 700,
120
+ letterSpacing: 2,
121
+ color: "var(--dsw-alias-label-primary, inherit)"
122
+ };
123
+ const btnBase = {
124
+ appearance: "none",
125
+ font: "inherit",
126
+ cursor: "pointer",
127
+ border: "1px solid transparent",
128
+ borderRadius: 8,
129
+ padding: "5px 14px",
130
+ fontSize: 13,
131
+ fontWeight: 500,
132
+ lineHeight: "20px",
133
+ color: "var(--dsw-alias-label-primary, inherit)",
134
+ background: "var(--dsw-alias-bg-module-platform, rgba(128,128,128,0.12))"
135
+ };
136
+ const btnPrimary = {
137
+ ...btnBase,
138
+ background: "var(--dsw-alias-brand-primary, #0a84ff)",
139
+ color: "var(--dsw-alias-bg-layer-1, #fff)"
140
+ };
141
+ const actionsStyle = {
142
+ display: "flex",
143
+ gap: 8,
144
+ flexWrap: "wrap",
145
+ paddingBottom: 10
146
+ };
147
+ const inputStyle = {
148
+ flex: 1,
149
+ padding: "6px 10px",
150
+ fontSize: 13,
151
+ borderRadius: 8,
152
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))",
153
+ background: "var(--dsw-alias-bg-layer-3, transparent)",
154
+ color: "var(--dsw-alias-label-primary, inherit)",
155
+ boxSizing: "border-box",
156
+ fontFamily: "inherit",
157
+ minWidth: 0
158
+ };
159
+ const modelsStyle = {
160
+ fontFamily: "var(--dsw-font-mono, ui-monospace, monospace)",
161
+ fontSize: 12,
162
+ color: "var(--dsw-alias-label-secondary, inherit)",
163
+ overflowWrap: "anywhere"
164
+ };
165
+ const CHEVRON_SVG = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9l6 6 6-6\"/></svg>";
166
+ /**
167
+ * Render the Copilot onboarding card.
168
+ * @param props - locale + controller inject.
169
+ * @returns a `<li>` card element.
170
+ */
171
+ function CopilotAuthCard({ t, controller, useCard }) {
172
+ const state = useCard((snapshot) => snapshot);
173
+ const [open, setOpen] = (0, react.useState)(false);
174
+ const [promptAnswer, setPromptAnswer] = (0, react.useState)("");
175
+ if (!state.loaded) controller.load();
176
+ const unsupported = state.loaded && !state.status.flowAvailable;
177
+ const loginRevealed = state.login.kind !== "idle";
178
+ const expanded = open || unsupported || loginRevealed;
179
+ const busy = state.busy;
180
+ const header = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
181
+ type: "button",
182
+ style: headerStyle,
183
+ "aria-expanded": expanded,
184
+ "aria-label": t("card.title"),
185
+ onClick: () => {
186
+ setOpen(!open);
187
+ },
188
+ children: [
189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
190
+ style: headTextStyle,
191
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
192
+ style: titleStyle,
193
+ children: t("card.title")
194
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
195
+ style: descStyle,
196
+ children: t("card.intro")
197
+ })]
198
+ }),
199
+ state.status.loggedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
200
+ style: badgeStyle("ok"),
201
+ children: t("card.signedIn")
202
+ }) : state.loaded && state.status.flowAvailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
203
+ style: badgeStyle("warn"),
204
+ children: t("card.signedOut")
205
+ }) : null,
206
+ state.status.loggedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
207
+ style: badgeStyle(state.status.profileActivated ? "ok" : "warn"),
208
+ children: t(state.status.profileActivated ? "card.routeActive" : "card.routeDormant")
209
+ }) : null,
210
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
211
+ style: {
212
+ color: "var(--dsw-alias-label-tertiary, inherit)",
213
+ flex: "none",
214
+ display: "inline-flex",
215
+ transform: expanded ? "rotate(180deg)" : "none"
216
+ },
217
+ dangerouslySetInnerHTML: { __html: CHEVRON_SVG }
218
+ })
219
+ ]
220
+ });
221
+ if (!expanded) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
222
+ style: cardStyle,
223
+ children: header
224
+ });
225
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
226
+ style: cardStyle,
227
+ children: [header, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
228
+ style: bodyStyle,
229
+ children: [
230
+ unsupported ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
231
+ style: noticeStyle,
232
+ role: "status",
233
+ children: t("card.unsupported")
234
+ }) : null,
235
+ !unsupported && state.login.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [state.status.loggedIn ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
236
+ style: actionsStyle,
237
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
238
+ type: "button",
239
+ style: btnPrimary,
240
+ disabled: busy,
241
+ onClick: () => {
242
+ controller.login();
243
+ },
244
+ children: t("action.signIn")
245
+ })
246
+ }), state.status.loggedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
247
+ style: actionsStyle,
248
+ children: [
249
+ !state.status.profileActivated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
250
+ type: "button",
251
+ style: btnPrimary,
252
+ disabled: busy,
253
+ onClick: () => {
254
+ controller.autofill();
255
+ },
256
+ children: t("action.activate")
257
+ }) : null,
258
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
259
+ type: "button",
260
+ style: btnBase,
261
+ disabled: busy,
262
+ onClick: () => {
263
+ controller.autofill();
264
+ },
265
+ children: t("action.syncModels")
266
+ }),
267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
268
+ type: "button",
269
+ style: btnBase,
270
+ disabled: busy,
271
+ onClick: () => {
272
+ controller.logout();
273
+ },
274
+ children: t("action.signOut")
275
+ })
276
+ ]
277
+ }) : null] }) : null,
278
+ state.login.kind === "running" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
279
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
280
+ style: noticeStyle,
281
+ role: "status",
282
+ children: t("state.pending")
283
+ }),
284
+ state.deviceCode !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
285
+ style: codeRowStyle,
286
+ children: [
287
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
288
+ style: noticeStyle,
289
+ children: t("notice.deviceCode")
290
+ }),
291
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
292
+ style: codeStyle,
293
+ children: state.deviceCode
294
+ }),
295
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
296
+ type: "button",
297
+ style: btnBase,
298
+ onClick: () => {
299
+ navigator.clipboard?.writeText(state.deviceCode ?? "");
300
+ controller.markCopied();
301
+ },
302
+ children: t(state.copied ? "action.copied" : "action.copyCode")
303
+ }),
304
+ state.verificationUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
305
+ type: "button",
306
+ style: btnBase,
307
+ onClick: () => {
308
+ window.open(state.verificationUrl, "_blank", "noopener,noreferrer,width=960,height=760");
309
+ },
310
+ children: t("action.openUrl")
311
+ }) : null
312
+ ]
313
+ }) : null,
314
+ state.events.filter((event) => event.kind === "notice" && event.code === void 0 && event.message).slice(-2).map((event) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
315
+ style: noticeStyle,
316
+ children: event.message
317
+ }, event.seq)),
318
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
319
+ style: actionsStyle,
320
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
321
+ type: "button",
322
+ style: btnBase,
323
+ disabled: busy,
324
+ onClick: () => {
325
+ controller.cancel();
326
+ },
327
+ children: t("action.cancel")
328
+ })
329
+ })
330
+ ] }) : null,
331
+ state.openPrompt !== void 0 && state.openPrompt.prompt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
332
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
333
+ style: noticeStyle,
334
+ role: "status",
335
+ children: t("state.pendingPrompt")
336
+ }),
337
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
338
+ style: noticeStyle,
339
+ children: state.openPrompt.prompt.message
340
+ }),
341
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
342
+ style: codeRowStyle,
343
+ children: [state.openPrompt.prompt.options === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
344
+ type: state.openPrompt.prompt.kind === "secret" ? "password" : "text",
345
+ style: inputStyle,
346
+ value: promptAnswer,
347
+ placeholder: state.openPrompt.prompt.placeholder ?? t("prompt.placeholder"),
348
+ onChange: (event) => {
349
+ setPromptAnswer(event.target.value);
350
+ }
351
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
352
+ type: "button",
353
+ style: btnPrimary,
354
+ disabled: busy,
355
+ onClick: () => {
356
+ controller.answer(promptAnswer, false);
357
+ setPromptAnswer("");
358
+ },
359
+ children: t("action.submit")
360
+ })] }) : state.openPrompt.prompt.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
361
+ type: "button",
362
+ style: btnBase,
363
+ onClick: () => {
364
+ controller.answer(option.id, false);
365
+ },
366
+ children: option.label
367
+ }, option.id)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
368
+ type: "button",
369
+ style: btnBase,
370
+ onClick: () => {
371
+ controller.answer("", true);
372
+ },
373
+ children: t("action.decline")
374
+ })]
375
+ })
376
+ ] }) : null,
377
+ state.login.kind === "success" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
378
+ style: successStyle,
379
+ role: "status",
380
+ children: t("state.success")
381
+ }) : null,
382
+ (state.login.kind === "idle" || state.login.kind === "success") && state.status.models !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
383
+ style: noticeStyle,
384
+ children: [
385
+ t("card.models"),
386
+ " ",
387
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
388
+ style: modelsStyle,
389
+ children: state.status.models.length > 0 ? state.status.models.join(", ") : "—"
390
+ })
391
+ ]
392
+ }) : null,
393
+ state.login.kind === "cancelled" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
394
+ style: noticeStyle,
395
+ role: "status",
396
+ children: [
397
+ t("state.error"),
398
+ " ",
399
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
400
+ type: "button",
401
+ style: btnBase,
402
+ disabled: busy,
403
+ onClick: () => {
404
+ controller.login();
405
+ },
406
+ children: t("action.retry")
407
+ })
408
+ ]
409
+ }) : null,
410
+ state.login.kind === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
411
+ style: errorStyle,
412
+ role: "status",
413
+ children: [
414
+ t("state.error"),
415
+ ": ",
416
+ state.login.message,
417
+ " ",
418
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
419
+ type: "button",
420
+ style: btnBase,
421
+ disabled: busy,
422
+ onClick: () => {
423
+ controller.login();
424
+ },
425
+ children: t("action.retry")
426
+ })
427
+ ]
428
+ }) : null
429
+ ]
430
+ })]
431
+ });
432
+ }
433
+
434
+ //#endregion
435
+ //#region src/client/controller.ts
436
+ /** Initial state before the first load. */
437
+ function initialState() {
438
+ return {
439
+ loaded: false,
440
+ status: {
441
+ flowAvailable: false,
442
+ loggedIn: false,
443
+ profileActivated: false,
444
+ inFlight: false,
445
+ models: void 0
446
+ },
447
+ login: { kind: "idle" },
448
+ events: [],
449
+ deviceCode: void 0,
450
+ verificationUrl: void 0,
451
+ openPrompt: void 0,
452
+ copied: false,
453
+ busy: false
454
+ };
455
+ }
456
+ /** Call one `/copilot/api/<method>` endpoint. */
457
+ async function call(method, payload = {}) {
458
+ const response = await fetch(`/copilot/api/${method}`, {
459
+ method: "POST",
460
+ headers: { "content-type": "application/json" },
461
+ body: JSON.stringify(payload)
462
+ });
463
+ const parsed = await response.json().catch(() => null);
464
+ if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === void 0) throw new Error(parsed?.error?.message ?? `HTTP ${response.status}`);
465
+ return parsed.value;
466
+ }
467
+ /**
468
+ * Controller managing the Copilot card lifecycle. Constructed once in the
469
+ * client `apply()`; polls only while a login attempt is running or the card
470
+ * holds an unanswered prompt.
471
+ */
472
+ var CopilotAuthController = class {
473
+ store;
474
+ cursor = 0;
475
+ pollTimer;
476
+ disposed = false;
477
+ constructor() {
478
+ this.store = (0, __deepseek_ai_dsh_client_store.createSnapshotStore)(initialState());
479
+ }
480
+ /** Load the joined status from the host. */
481
+ async load() {
482
+ try {
483
+ const status = await call("status");
484
+ this.store.update((s) => {
485
+ s.loaded = true;
486
+ s.status = status;
487
+ if (status.inFlight && s.login.kind === "idle") {
488
+ s.login = { kind: "running" };
489
+ this.schedulePoll(0);
490
+ }
491
+ });
492
+ } catch {
493
+ this.store.update((s) => {
494
+ s.loaded = true;
495
+ });
496
+ }
497
+ }
498
+ /** Start a login attempt, then poll events until settlement. */
499
+ async login() {
500
+ if (this.disposed) return;
501
+ this.store.update((s) => {
502
+ s.busy = true;
503
+ s.login = { kind: "running" };
504
+ s.events = [];
505
+ s.deviceCode = void 0;
506
+ s.verificationUrl = void 0;
507
+ s.openPrompt = void 0;
508
+ });
509
+ try {
510
+ this.cursor = (await call("login")).cursor ?? this.cursor;
511
+ } catch (error) {
512
+ this.finishLogin("error", error instanceof Error ? error.message : String(error));
513
+ return;
514
+ }
515
+ this.store.update((s) => {
516
+ s.busy = false;
517
+ });
518
+ this.schedulePoll(0);
519
+ }
520
+ /** Withdraw the running attempt. */
521
+ async cancel() {
522
+ this.store.update((s) => {
523
+ s.busy = true;
524
+ });
525
+ try {
526
+ await call("cancel");
527
+ } catch {}
528
+ this.store.update((s) => {
529
+ s.busy = false;
530
+ });
531
+ }
532
+ /** Answer the open prompt. */
533
+ async answer(answer, declined) {
534
+ const prompt = this.store.getSnapshot().openPrompt;
535
+ if (prompt === void 0) return;
536
+ this.store.update((s) => {
537
+ s.busy = true;
538
+ s.openPrompt = void 0;
539
+ });
540
+ try {
541
+ await call("answer", {
542
+ seq: prompt.seq,
543
+ answer,
544
+ declined
545
+ });
546
+ } catch {}
547
+ this.store.update((s) => {
548
+ s.busy = false;
549
+ });
550
+ this.schedulePoll(0);
551
+ }
552
+ /** Delete the stored credential record. */
553
+ async logout() {
554
+ this.store.update((s) => {
555
+ s.busy = true;
556
+ });
557
+ try {
558
+ await call("logout");
559
+ await this.load();
560
+ } catch {}
561
+ this.store.update((s) => {
562
+ s.busy = false;
563
+ });
564
+ }
565
+ /** Write the provider profile (idempotent on the host side). */
566
+ async autofill() {
567
+ this.store.update((s) => {
568
+ s.busy = true;
569
+ });
570
+ try {
571
+ await call("autofill");
572
+ await this.load();
573
+ } catch {
574
+ await this.load();
575
+ }
576
+ this.store.update((s) => {
577
+ s.busy = false;
578
+ });
579
+ }
580
+ /** Copy-button feedback. */
581
+ markCopied() {
582
+ this.store.update((s) => {
583
+ s.copied = true;
584
+ });
585
+ setTimeout(() => {
586
+ if (!this.disposed) this.store.update((s) => {
587
+ s.copied = false;
588
+ });
589
+ }, 1500);
590
+ }
591
+ /** Stop polling and further actions; called on fiber disposal. */
592
+ dispose() {
593
+ this.disposed = true;
594
+ if (this.pollTimer !== void 0) clearTimeout(this.pollTimer);
595
+ this.pollTimer = void 0;
596
+ }
597
+ /** Fold one settlement into the card state and refresh the join. */
598
+ finishLogin(status, message) {
599
+ this.store.update((s) => {
600
+ s.busy = false;
601
+ if (status === "authorized") {
602
+ s.login = { kind: "success" };
603
+ s.status = {
604
+ ...s.status,
605
+ loggedIn: true,
606
+ profileActivated: true
607
+ };
608
+ } else if (status === "cancelled") s.login = { kind: "cancelled" };
609
+ else s.login = {
610
+ kind: "error",
611
+ message: message ?? "Sign-in failed"
612
+ };
613
+ });
614
+ this.load();
615
+ }
616
+ /** Poll the event stream once; reschedules while the attempt runs. */
617
+ async pollOnce() {
618
+ if (this.disposed) return;
619
+ let delay = 1e3;
620
+ try {
621
+ const answer = await call("events", { since: this.cursor });
622
+ this.cursor = answer.cursor;
623
+ for (const event of answer.events) this.applyEvent(event);
624
+ if (answer.settlement !== void 0 && this.store.getSnapshot().login.kind === "running") {
625
+ this.finishLogin(answer.settlement.status, answer.settlement.message);
626
+ return;
627
+ }
628
+ if (answer.inFlight) delay = 1e3;
629
+ else {
630
+ this.load();
631
+ return;
632
+ }
633
+ } catch {
634
+ delay = 2e3;
635
+ }
636
+ this.schedulePoll(delay);
637
+ }
638
+ /** Fold one sequenced event into the card state. */
639
+ applyEvent(event) {
640
+ this.store.update((s) => {
641
+ s.events = [...s.events, event].slice(-24);
642
+ if (event.kind === "prompt") {
643
+ s.openPrompt = event;
644
+ return;
645
+ }
646
+ if (event.code !== void 0) {
647
+ s.deviceCode = event.code;
648
+ s.verificationUrl = event.url;
649
+ }
650
+ });
651
+ }
652
+ /** Schedule the next poll, collapsing overlapping timers. */
653
+ schedulePoll(delayMs) {
654
+ if (this.disposed) return;
655
+ if (this.pollTimer !== void 0) clearTimeout(this.pollTimer);
656
+ this.pollTimer = setTimeout(() => {
657
+ this.pollTimer = void 0;
658
+ this.pollOnce();
659
+ }, delayMs);
660
+ }
661
+ };
662
+
663
+ //#endregion
664
+ //#region src/client/bindSnapshotSelector.ts
665
+ /**
666
+ * Bind a React selector hook to a {@link HostObservable} snapshot source.
667
+ * @param source - the observable snapshot store.
668
+ * @returns a `useSelector(sel, eq?)` hook.
669
+ */
670
+ function bindSnapshotSelector(source) {
671
+ const subscribe = (fn) => source.subscribe(fn);
672
+ const getSnapshot = () => source.getSnapshot();
673
+ return function useSelector(sel) {
674
+ const snapshot = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
675
+ const prevSnapshotRef = (0, react.useRef)(void 0);
676
+ const prevSelectedRef = (0, react.useRef)(void 0);
677
+ if (prevSnapshotRef.current !== snapshot) {
678
+ prevSnapshotRef.current = snapshot;
679
+ prevSelectedRef.current = sel(snapshot);
680
+ }
681
+ return prevSelectedRef.current;
682
+ };
683
+ }
684
+
685
+ //#endregion
686
+ //#region src/client/locales.ts
687
+ /** The locale namespace name; matches the `locale: NS` passed at slot register. */
688
+ const NS = "dsh-plugin-copilot";
689
+ /** English dictionary. */
690
+ const en = {
691
+ "card.title": "GitHub Copilot",
692
+ "card.intro": "Sign in to GitHub Copilot and activate its model route (served by dsh-llm-pi-ai).",
693
+ "card.unsupported": "Copilot sign-in needs dsh-llm-pi-ai (0.1.2-alpha.1 or later) with the github-copilot catalog provider.",
694
+ "card.signedIn": "Signed in",
695
+ "card.signedOut": "Not signed in",
696
+ "card.routeActive": "Route active",
697
+ "card.routeDormant": "Route not activated",
698
+ "card.models": "Models available to this account:",
699
+ "action.signIn": "Sign in with GitHub",
700
+ "action.signOut": "Sign out",
701
+ "action.cancel": "Cancel",
702
+ "action.retry": "Retry",
703
+ "action.activate": "Activate route",
704
+ "action.syncModels": "Sync model list",
705
+ "action.openUrl": "Open verification page",
706
+ "action.copyCode": "Copy code",
707
+ "action.copied": "Copied",
708
+ "action.decline": "Decline",
709
+ "action.submit": "Submit",
710
+ "state.pending": "Waiting for authorization…",
711
+ "state.pendingPrompt": "Answer the question below to continue signing in — the device code appears right after.",
712
+ "state.working": "Working…",
713
+ "state.success": "Signed in. The Copilot models are ready — pick one on the Models page.",
714
+ "state.error": "Failed",
715
+ "notice.deviceCode": "Open the verification page and enter this code:",
716
+ "notice.polling": "Waiting for you to finish in the browser…",
717
+ "prompt.placeholder": "Your answer",
718
+ "prompt.answer": "GitHub asks"
719
+ };
720
+ /** Chinese dictionary. */
721
+ const zh = {
722
+ "card.title": "GitHub Copilot",
723
+ "card.intro": "登录 GitHub Copilot 并激活其模型路由(由 dsh-llm-pi-ai 提供)。",
724
+ "card.unsupported": "Copilot 登录需要 dsh-llm-pi-ai(0.1.2-alpha.1 或更高)内置的 github-copilot 供应商。",
725
+ "card.signedIn": "已登录",
726
+ "card.signedOut": "未登录",
727
+ "card.routeActive": "路由已激活",
728
+ "card.routeDormant": "路由未激活",
729
+ "card.models": "当前账号可用模型:",
730
+ "action.signIn": "使用 GitHub 登录",
731
+ "action.signOut": "退出登录",
732
+ "action.cancel": "取消",
733
+ "action.retry": "重试",
734
+ "action.activate": "激活路由",
735
+ "action.syncModels": "同步模型列表",
736
+ "action.openUrl": "打开验证页面",
737
+ "action.copyCode": "复制代码",
738
+ "action.copied": "已复制",
739
+ "action.decline": "拒绝",
740
+ "action.submit": "提交",
741
+ "state.pending": "等待授权中…",
742
+ "state.pendingPrompt": "回答下面的问题即可继续登录,随后会显示设备码和验证页面。",
743
+ "state.working": "处理中…",
744
+ "state.success": "已登录,Copilot 模型就绪 — 去 Models 页选择即可。",
745
+ "state.error": "失败",
746
+ "notice.deviceCode": "打开验证页面并输入此代码:",
747
+ "notice.polling": "等待你在浏览器中完成授权…",
748
+ "prompt.placeholder": "输入你的回答",
749
+ "prompt.answer": "GitHub 需要你回答"
750
+ };
751
+
752
+ //#endregion
753
+ //#region src/client/index.ts
754
+ /** Required services: the slot ledger and the locale dictionaries. */
755
+ const inject = ["slots", "locale"];
756
+ /**
757
+ * Client plugin body: register the locale dictionaries and the settings card.
758
+ * @param ctx - client root context.
759
+ */
760
+ function apply(ctx) {
761
+ ctx.effect(() => ctx.locale.register(NS, {
762
+ zh,
763
+ en
764
+ }), "dsh-plugin-copilot: dictionaries");
765
+ const controller = new CopilotAuthController();
766
+ ctx.effect(() => () => {
767
+ controller.dispose();
768
+ }, "dsh-plugin-copilot: card controller");
769
+ const useCard = bindSnapshotSelector(controller.store);
770
+ const injected = () => ({
771
+ controller,
772
+ useCard
773
+ });
774
+ ctx.slots.inject("settings.plugin.item", function* () {
775
+ yield ctx.slots.register({
776
+ name: "settings.plugin.item",
777
+ key: "dsh-plugin-copilot",
778
+ locale: NS,
779
+ inject: injected
780
+ }, CopilotAuthCard);
781
+ });
782
+ }
783
+
784
+ //#endregion
785
+ exports.apply = apply;
786
+ exports.inject = inject;
787
+ return module.exports; } });
788
+ //# sourceMappingURL=client.js.map