@klarkxy/dsh-zhihu 0.1.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,2246 @@
1
+ function __createDshNodeShims() {
2
+ class EventEmitter {
3
+ constructor() {
4
+ this._e = Object.create(null)
5
+ }
6
+ on(type, fn) {
7
+ (this._e[type] ||= []).push(fn)
8
+ return this
9
+ }
10
+ addListener(type, fn) {
11
+ return this.on(type, fn)
12
+ }
13
+ once(type, fn) {
14
+ const wrap = (...args) => {
15
+ this.off(type, wrap)
16
+ fn(...args)
17
+ }
18
+ return this.on(type, wrap)
19
+ }
20
+ off(type, fn) {
21
+ const list = this._e[type]
22
+ if (list) this._e[type] = list.filter((item) => item !== fn)
23
+ return this
24
+ }
25
+ removeListener(type, fn) {
26
+ return this.off(type, fn)
27
+ }
28
+ emit(type, ...args) {
29
+ const list = this._e[type]
30
+ if (!list) return false
31
+ for (const fn of list.slice()) fn(...args)
32
+ return true
33
+ }
34
+ removeAllListeners(type) {
35
+ if (type) delete this._e[type]
36
+ else this._e = Object.create(null)
37
+ return this
38
+ }
39
+ listeners(type) {
40
+ return (this._e[type] || []).slice()
41
+ }
42
+ setMaxListeners() {
43
+ return this
44
+ }
45
+ }
46
+
47
+ class Buffer extends Uint8Array {
48
+ static from(value, encodingOrOffset, length) {
49
+ if (typeof value === 'string') return new Buffer(new TextEncoder().encode(value))
50
+ if (value instanceof ArrayBuffer) return new Buffer(new Uint8Array(value, encodingOrOffset, length))
51
+ if (ArrayBuffer.isView(value)) return new Buffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
52
+ if (Array.isArray(value) || value && typeof value.length === 'number') return new Buffer(Uint8Array.from(value))
53
+ return new Buffer(0)
54
+ }
55
+ static alloc(size, fill) {
56
+ const buf = new Buffer(size)
57
+ if (fill !== undefined && fill !== 0) {
58
+ if (typeof fill === 'string') {
59
+ const bytes = new TextEncoder().encode(fill)
60
+ for (let i = 0; i < size; i += 1) buf[i] = bytes[i % bytes.length]
61
+ } else buf.fill(fill)
62
+ }
63
+ return buf
64
+ }
65
+ static allocUnsafe(size) {
66
+ return new Buffer(size)
67
+ }
68
+ static allocUnsafeSlow(size) {
69
+ return new Buffer(size)
70
+ }
71
+ static concat(list, totalLength) {
72
+ const length = totalLength ?? list.reduce((sum, item) => sum + item.length, 0)
73
+ const out = new Buffer(length)
74
+ let offset = 0
75
+ for (const item of list) {
76
+ out.set(item, offset)
77
+ offset += item.length
78
+ }
79
+ return out
80
+ }
81
+ static isBuffer(value) {
82
+ return value instanceof Buffer
83
+ }
84
+ static byteLength(value) {
85
+ if (typeof value === 'string') return new TextEncoder().encode(value).length
86
+ return value?.byteLength ?? value?.length ?? 0
87
+ }
88
+ static isEncoding(encoding) {
89
+ return /^(utf8|utf-8|ascii|latin1|binary|hex|base64|base64url)$/i.test(String(encoding || ''))
90
+ }
91
+ toString(encoding) {
92
+ if (encoding === 'hex') return [...this].map((byte) => byte.toString(16).padStart(2, '0')).join('')
93
+ if (encoding === 'base64') {
94
+ let binary = ''
95
+ this.forEach((byte) => {
96
+ binary += String.fromCharCode(byte)
97
+ })
98
+ return btoa(binary)
99
+ }
100
+ return new TextDecoder().decode(this)
101
+ }
102
+ slice(start, end) {
103
+ return Buffer.from(super.subarray(start, end))
104
+ }
105
+ copy(target, targetStart = 0, start = 0, end = this.length) {
106
+ const sliced = this.subarray(start, end)
107
+ target.set(sliced, targetStart)
108
+ return sliced.length
109
+ }
110
+ write(string, offset = 0) {
111
+ const bytes = new TextEncoder().encode(string)
112
+ const written = Math.min(bytes.length, this.length - offset)
113
+ this.set(bytes.subarray(0, written), offset)
114
+ return written
115
+ }
116
+ }
117
+
118
+ function inherits(ctor, superCtor) {
119
+ ctor.super_ = superCtor
120
+ ctor.prototype = Object.create(superCtor.prototype, {
121
+ constructor: { value: ctor, writable: true, configurable: true },
122
+ })
123
+ }
124
+
125
+ if (!globalThis.Buffer) globalThis.Buffer = Buffer
126
+
127
+ return {
128
+ buffer: { Buffer, SlowBuffer: Buffer, INSPECT_MAX_BYTES: 50, kMaxLength: 0x7fffffff },
129
+ stream: {
130
+ Stream: EventEmitter,
131
+ Readable: EventEmitter,
132
+ Writable: EventEmitter,
133
+ Duplex: EventEmitter,
134
+ Transform: EventEmitter,
135
+ PassThrough: EventEmitter,
136
+ },
137
+ events: { EventEmitter },
138
+ util: {
139
+ deprecate(fn) {
140
+ return fn
141
+ },
142
+ inherits,
143
+ inspect() {
144
+ return ''
145
+ },
146
+ format(value) {
147
+ return String(value)
148
+ },
149
+ types: { isBuffer: Buffer.isBuffer },
150
+ },
151
+ }
152
+ }
153
+
154
+ window.__ModuleLoader__.load({
155
+ id: "@klarkxy/dsh-zhihu",
156
+ factory: (dshRequire) => {
157
+ var __nodeShims;
158
+ var process = globalThis.process || { env: {}, nextTick: function (fn) { var args = [].slice.call(arguments, 1); queueMicrotask(function () { fn.apply(null, args); }); } };
159
+ var require = function (id) {
160
+ if (id === 'buffer' || id === 'stream' || id === 'util' || id === 'events') {
161
+ if (!__nodeShims) __nodeShims = __createDshNodeShims();
162
+ return __nodeShims[id];
163
+ }
164
+ return dshRequire(id);
165
+ };
166
+ var module = { exports: {} };
167
+ var exports = module.exports;
168
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
169
+ let react = require("react");
170
+ let react_jsx_runtime = require("react/jsx-runtime");
171
+ //#region src/contracts.ts
172
+ const ZHIHU_RPC_CHANNEL = "/zhihu";
173
+ const ZHIHU_CREDENTIAL_REF = "ZHIHU_ACCESS_TOKEN";
174
+ //#endregion
175
+ //#region src/client-state.ts
176
+ /**
177
+ * Stale-result guard for the zhihu panel: a response must only be applied to
178
+ * the exact query/mode revision it was issued from. Editing aborts the
179
+ * in-flight request outright — its results target a query that no longer
180
+ * exists. Request identity handles superseded and cancelled calls, and a late
181
+ * resolution after a tab change or panel close is dropped.
182
+ */
183
+ function createZhihuClientState() {
184
+ let revision = 0;
185
+ let request = 0;
186
+ let controller = null;
187
+ return {
188
+ revision: () => revision,
189
+ noteInput: () => {
190
+ controller?.abort();
191
+ controller = null;
192
+ request += 1;
193
+ return ++revision;
194
+ },
195
+ begin: () => {
196
+ controller?.abort();
197
+ controller = new AbortController();
198
+ request += 1;
199
+ return {
200
+ ticket: {
201
+ revision,
202
+ request
203
+ },
204
+ signal: controller.signal
205
+ };
206
+ },
207
+ isCurrent: (ticket) => ticket.revision === revision && ticket.request === request,
208
+ cancel: () => {
209
+ controller?.abort();
210
+ controller = null;
211
+ request += 1;
212
+ }
213
+ };
214
+ }
215
+ //#endregion
216
+ //#region src/client-styles.ts
217
+ const standaloneOutsideTheme = ":not(.radix-themes *)";
218
+ const zhihuClientStyles = `
219
+ .zhihu-dock {
220
+ position: var(--dsh-ext-dock-position, absolute);
221
+ right: var(--dsh-ext-dock-right, var(--space-4, 16px));
222
+ bottom: var(--dsh-ext-dock-bottom, calc(var(--space-4, 16px) + 44px));
223
+ pointer-events: auto;
224
+ }
225
+ .zhihu-settings-embed {
226
+ font: 400 var(--font-size-2)/1.5 var(--default-font-family);
227
+ color: var(--gray-12);
228
+ display: flex;
229
+ flex-direction: column;
230
+ min-width: 0;
231
+ min-height: 0;
232
+ gap: 0;
233
+ }
234
+ .zhihu-settings-embed .zhihu-tabs {
235
+ margin: 0 0 var(--space-2);
236
+ }
237
+ .zhihu-settings-embed .zhihu-panel-body {
238
+ padding: var(--space-4) 0 0;
239
+ overflow: visible;
240
+ }
241
+ .zhihu-toggle {
242
+ pointer-events: auto;
243
+ font: inherit;
244
+ font-family: var(--default-font-family);
245
+ font-size: var(--font-size-2);
246
+ min-height: var(--control-h, 34px);
247
+ color: var(--gray-11);
248
+ background: var(--color-panel-solid);
249
+ border: 1px solid var(--gray-6);
250
+ border-radius: 999px;
251
+ padding: 0 14px;
252
+ cursor: pointer;
253
+ box-shadow: var(--shadow-3);
254
+ }
255
+ .zhihu-toggle:hover { border-color: var(--accent-9); color: var(--accent-11); background: var(--gray-a3); }
256
+ .zhihu-toggle:focus-visible,
257
+ .zhihu-panel button:focus-visible,
258
+ .zhihu-panel input:focus-visible,
259
+ .zhihu-panel select:focus-visible,
260
+ .zhihu-panel a:focus-visible {
261
+ box-shadow: 0 0 0 2px var(--accent-a8);
262
+ }
263
+ .zhihu-dock .zhihu-panel {
264
+ position: absolute;
265
+ top: var(--dsh-ext-panel-top, auto);
266
+ right: 0;
267
+ bottom: var(--dsh-ext-panel-bottom, calc(100% + 6px));
268
+ z-index: 10;
269
+ width: min(440px, calc(100vw - 32px));
270
+ max-height: min(72vh, 620px);
271
+ display: flex;
272
+ flex-direction: column;
273
+ pointer-events: auto;
274
+ font-family: var(--default-font-family);
275
+ font-size: var(--font-size-2);
276
+ color: var(--gray-12);
277
+ background: var(--color-panel-solid);
278
+ border: 1px solid var(--gray-6);
279
+ border-radius: var(--radius-3);
280
+ box-shadow: var(--shadow-4);
281
+ overflow: hidden;
282
+ }
283
+ .zhihu-panel {
284
+ font-family: var(--default-font-family);
285
+ font-size: var(--font-size-2);
286
+ color: var(--gray-12);
287
+ background: var(--color-panel-solid);
288
+ }
289
+ .zhihu-panel-header {
290
+ display: flex;
291
+ align-items: center;
292
+ justify-content: space-between;
293
+ padding: var(--space-2) var(--space-3);
294
+ border-bottom: 1px solid var(--gray-6);
295
+ }
296
+ .zhihu-panel-title {
297
+ margin: 0;
298
+ font-size: var(--font-size-2);
299
+ font-weight: var(--font-weight-medium);
300
+ font-family: var(--default-font-family);
301
+ }
302
+ .zhihu-panel-close {
303
+ font: inherit; font-size: var(--font-size-2); min-height: 32px; color: var(--gray-11); background: none; border: none;
304
+ border-radius: var(--radius-2); padding: 0 8px; cursor: pointer;
305
+ }
306
+ .zhihu-panel-close:hover { color: var(--gray-12); background: var(--gray-a3); }
307
+ .zhihu-panel-close:disabled { opacity: 0.5; cursor: default; }
308
+ .zhihu-tabs {
309
+ display: inline-flex;
310
+ gap: 2px;
311
+ padding: 2px;
312
+ margin: var(--space-2) var(--space-3) 0;
313
+ border-radius: var(--radius-3);
314
+ background: var(--gray-3);
315
+ box-shadow: 0 0 0 1px var(--gray-a5);
316
+ width: max-content;
317
+ }
318
+ .zhihu-tab {
319
+ font: inherit;
320
+ font-size: var(--font-size-2);
321
+ min-height: 28px;
322
+ padding: 0 var(--space-3);
323
+ border: 0;
324
+ border-radius: var(--radius-2);
325
+ background: transparent;
326
+ color: var(--gray-11);
327
+ cursor: pointer;
328
+ }
329
+ .zhihu-tab:hover { color: var(--gray-12); background: var(--gray-a3); }
330
+ .zhihu-tab[aria-selected="true"] {
331
+ background: var(--accent-a3);
332
+ color: var(--accent-12, var(--accent-11));
333
+ box-shadow: 0 0 0 1px var(--gray-a5);
334
+ font-weight: var(--font-weight-medium);
335
+ transform: scale(1.04);
336
+ }
337
+ .zhihu-panel-body {
338
+ padding: var(--space-3);
339
+ display: flex;
340
+ flex-direction: column;
341
+ gap: var(--space-2);
342
+ overflow-y: auto;
343
+ }
344
+ .zhihu-field { display: flex; flex-direction: column; gap: 4px; }
345
+ .zhihu-field-label { color: var(--gray-11); font-weight: var(--font-weight-medium); }
346
+ .zhihu-input, .zhihu-select {
347
+ width: 100%;
348
+ min-height: var(--control-h, 34px);
349
+ box-sizing: border-box;
350
+ font: inherit;
351
+ font-family: var(--default-font-family);
352
+ font-size: var(--font-size-2);
353
+ color: var(--gray-12);
354
+ background: var(--color-surface);
355
+ border: 1px solid var(--gray-6);
356
+ border-radius: var(--radius-2);
357
+ padding: 0 8px;
358
+ }
359
+ .zhihu-row { display: flex; align-items: center; gap: var(--space-2); }
360
+ .zhihu-button {
361
+ font: inherit; font-size: var(--font-size-2); min-height: var(--control-h, 34px); color: var(--gray-11); background: none;
362
+ border: 1px solid var(--gray-6); border-radius: var(--radius-2);
363
+ padding: 0 12px; cursor: pointer;
364
+ }
365
+ .zhihu-button:hover { background: var(--gray-a3); }
366
+ .zhihu-button:disabled { opacity: 0.5; cursor: default; }
367
+ .zhihu-button-primary {
368
+ color: var(--accent-contrast);
369
+ background: var(--accent-9);
370
+ border-color: var(--accent-9);
371
+ }
372
+ .zhihu-button-primary:hover:not(:disabled) { background: var(--accent-10); }
373
+ .zhihu-button-danger { color: var(--red-11); }
374
+ .zhihu-hint { color: var(--gray-11); font-size: var(--font-size-2); margin: 0; }
375
+ .zhihu-intro { margin: 0; color: var(--gray-11); line-height: 1.6; }
376
+ .zhihu-field > .zhihu-intro { margin-bottom: var(--space-2); }
377
+ .zhihu-settings, .zhihu-knowledge { display: flex; flex-direction: column; gap: var(--space-3); }
378
+ .zhihu-knowledge > p { margin: 0; }
379
+ .zhihu-status { color: var(--gray-11); margin: 0; padding: var(--space-2) 0; }
380
+ .zhihu-error { color: var(--red-11); margin: 0; padding: var(--space-2) 0; }
381
+ .zhihu-warning { color: var(--red-11); margin: 0; }
382
+ .zhihu-saved { color: var(--green-11); margin: 0; }
383
+ .zhihu-link { color: var(--accent-11); }
384
+ /* Ordinary DSH paints a light fill on bare <a>. Keep plugin links
385
+ transparent so computed contrast falls through to the panel surface. */
386
+ .zhihu-panel .zhihu-link, .zhihu-panel .zhihu-result-title { background: transparent; }
387
+ .zhihu-panel a.zhihu-link,
388
+ .zhihu-panel a.zhihu-result-title {
389
+ background-color: transparent;
390
+ }
391
+ .zhihu-stale {
392
+ color: var(--gray-11);
393
+ background: var(--gray-a3);
394
+ border-radius: var(--radius-2);
395
+ padding: var(--space-2);
396
+ }
397
+ .zhihu-results { display: flex; flex-direction: column; gap: var(--space-2); }
398
+ .zhihu-results-summary { color: var(--gray-11); font-weight: var(--font-weight-medium); margin: 0; }
399
+ .zhihu-result-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
400
+ .zhihu-result-item {
401
+ border: 1px solid var(--gray-6);
402
+ border-radius: var(--radius-2);
403
+ padding: var(--space-2);
404
+ display: flex;
405
+ flex-direction: column;
406
+ gap: 4px;
407
+ background: var(--color-panel-solid);
408
+ }
409
+ .zhihu-result-item:hover { background: var(--gray-3); }
410
+ .zhihu-result-title { font-weight: var(--font-weight-medium); color: var(--gray-12); }
411
+ .zhihu-result-meta { color: var(--gray-11); font-size: var(--font-size-2); }
412
+ .zhihu-result-summary { color: var(--gray-11); }
413
+ .zhihu-result-snippet {
414
+ color: var(--gray-11);
415
+ background: var(--gray-3);
416
+ border-radius: var(--radius-2);
417
+ padding: 4px 8px;
418
+ }
419
+ .zhihu-ask-content { white-space: pre-wrap; line-height: 1.7; color: var(--gray-12); }
420
+ .zhihu-ask-reasoning { color: var(--gray-11); font-size: var(--font-size-2); }
421
+ .zhihu-status-grid { display: flex; gap: var(--space-2); margin: 0; align-items: baseline; }
422
+ .zhihu-status-label { color: var(--gray-11); font-weight: var(--font-weight-medium); }
423
+ .zhihu-status-value { margin: 0; display: flex; align-items: center; gap: 6px; }
424
+ .zhihu-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
425
+ .zhihu-dot-configured { background: var(--green-11); }
426
+ .zhihu-dot-missing { background: var(--gray-9); }
427
+ .zhihu-dot-locked { background: var(--red-9); }
428
+ .zhihu-field-heading { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
429
+ .zhihu-settings .zhihu-guide { border: 0; padding: 0; color: var(--gray-11); font-size: var(--font-size-1); }
430
+ .zhihu-settings .zhihu-guide summary { cursor: pointer; width: fit-content; }
431
+ .zhihu-settings .zhihu-guide p { margin-top: var(--space-2); font-size: var(--font-size-1); }
432
+ .zhihu-settings :is(.zhihu-field-heading a,.zhihu-status-grid) { font-size: var(--font-size-1); }
433
+ .zhihu-settings :is(a,summary):focus-visible { outline: 2px solid var(--accent-8); outline-offset: 2px; }
434
+ .zhihu-guide {
435
+ border: 1px solid var(--gray-6);
436
+ border-radius: var(--radius-2);
437
+ padding: var(--space-2);
438
+ }
439
+ .zhihu-guide-title { margin: 0 0 4px; font-size: var(--font-size-2); font-weight: var(--font-weight-medium); font-family: var(--default-font-family); }
440
+ .zhihu-guide-steps { margin: 0; padding-left: 18px; color: var(--gray-11); line-height: 1.7; }
441
+ .zhihu-scopes { display: flex; gap: var(--space-3); flex-wrap: wrap; }
442
+ .zhihu-scope {
443
+ display: inline-flex; align-items: center; gap: 4px; color: var(--gray-11);
444
+ font: inherit; background: none; border: 1px solid var(--gray-6); border-radius: var(--radius-2);
445
+ padding: 0 10px; min-height: var(--control-h, 34px); cursor: pointer;
446
+ }
447
+ .zhihu-scope.is-on, .zhihu-scope[aria-pressed="true"] {
448
+ color: var(--accent-11); background: var(--accent-a3); border-color: var(--accent-a6);
449
+ }
450
+ .zhihu-input.ui-input { width: 100%; }
451
+ .zhihu-usage { display: flex; flex-direction: column; gap: var(--space-3); }
452
+ .zhihu-usage-intro { margin: 0; color: var(--gray-11); line-height: 1.6; }
453
+ .zhihu-usage-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: var(--space-2); }
454
+ .zhihu-usage-card { display: flex; flex-direction: column; gap: 4px; min-width: 0; padding: var(--space-2); border: 1px solid var(--gray-6); border-radius: var(--radius-2); background: var(--color-panel-solid); }
455
+ .zhihu-usage-card:hover { background: var(--gray-a3); }
456
+ .zhihu-usage-card-label { color: var(--gray-11); font-size: var(--font-size-2); }
457
+ .zhihu-usage-card-value { font-weight: var(--font-weight-medium); font-variant-numeric: tabular-nums; color: var(--gray-12); }
458
+ .zhihu-usage-card-unit { margin-left: 4px; color: var(--gray-11); font-size: var(--font-size-2); font-weight: 400; }
459
+ .zhihu-usage-heading { margin: 0; font-size: var(--font-size-2); font-weight: var(--font-weight-medium); color: var(--gray-11); font-family: var(--default-font-family); }
460
+ .zhihu-usage-legend { display: flex; flex-wrap: wrap; gap: 6px var(--space-3); margin: 0; padding: 0; list-style: none; color: var(--gray-11); font-size: var(--font-size-2); }
461
+ .zhihu-usage-legend li { display: inline-flex; align-items: center; gap: 6px; }
462
+ .zhihu-chart-chip { width: 8px; height: 8px; border-radius: 2px; flex: none; }
463
+ .zhihu-chart-chip-ok { background: var(--accent-9); }
464
+ .zhihu-chart-chip-fail { background: var(--red-9); }
465
+ .zhihu-chart { width: 100%; height: auto; aspect-ratio: 600 / 176; display: block; }
466
+ .zhihu-chart-bar-ok { fill: var(--accent-9); }
467
+ .zhihu-chart-bar-fail { fill: var(--red-9); }
468
+ .zhihu-chart-hit { fill: transparent; }
469
+ .zhihu-chart-grid { stroke: var(--gray-6); stroke-width: 1; }
470
+ .zhihu-chart-axis { fill: var(--gray-11); font-size: var(--font-size-1); font-family: var(--code-font-family); }
471
+ .zhihu-chart-tick { fill: var(--gray-11); font-size: var(--font-size-1); font-family: var(--code-font-family); }
472
+ .zhihu-chart-value { fill: var(--gray-11); font-size: var(--font-size-1); font-family: var(--code-font-family); }
473
+ .zhihu-usage-table-wrap { overflow-x: auto; }
474
+ .zhihu-usage-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
475
+ .zhihu-usage-table th, .zhihu-usage-table td { padding: 4px 6px; text-align: right; border-bottom: 1px solid var(--gray-6); color: var(--gray-11); }
476
+ .zhihu-usage-table th:first-child, .zhihu-usage-table td:first-child { text-align: left; }
477
+ .zhihu-usage-table th { color: var(--gray-11); font-weight: var(--font-weight-medium); }
478
+ .zhihu-file {
479
+ position: absolute;
480
+ width: 1px;
481
+ height: 1px;
482
+ padding: 0;
483
+ margin: -1px;
484
+ overflow: hidden;
485
+ clip: rect(0, 0, 0, 0);
486
+ white-space: nowrap;
487
+ border: 0;
488
+ }
489
+ .zhihu-upload-confirm {
490
+ border: 1px solid var(--gray-6);
491
+ border-radius: var(--radius-2);
492
+ padding: var(--space-2);
493
+ color: var(--gray-11);
494
+ }
495
+ /* 活动反馈:三点呼吸(pulse-dots),参数改写自 Amicro(MIT License,
496
+ Copyright (c) 2026 Syed Subhan Uddin);装饰元素 aria-hidden。 */
497
+ @keyframes zhihu-activity-pulse { 0%, 100% { opacity: .2; } 50% { opacity: 1; } }
498
+ .zhihu-dots { display: inline-flex; align-items: center; gap: 3px; margin-inline-end: .4em; vertical-align: middle; color: var(--accent-9); }
499
+ .zhihu-dots i { width: .32em; height: .32em; min-width: 3px; min-height: 3px; border-radius: 50%; background: currentColor; animation: zhihu-activity-pulse 1.4s ease infinite; }
500
+ .zhihu-dots i:nth-child(2) { animation-delay: .2s; }
501
+ .zhihu-dots i:nth-child(3) { animation-delay: .4s; }
502
+ @media (prefers-reduced-motion: reduce) {
503
+ .zhihu-dock, .zhihu-dock *, .zhihu-panel, .zhihu-panel * {
504
+ animation: none !important; transition: none !important;
505
+ transform: none !important; filter: none !important;
506
+ }
507
+ .zhihu-dots i { animation: none; }
508
+ }
509
+ /* Local tokens when this overlay is not under .radix-themes. Host Theme
510
+ and --dsh-ext-* rail overrides still win inside the Theme tree. Dark
511
+ follows body[data-ds-dark-theme] / :root[data-theme=dark], not OS scheme. */
512
+ .zhihu-dock${standaloneOutsideTheme},
513
+ .zhihu-toggle${standaloneOutsideTheme},
514
+ .zhihu-panel${standaloneOutsideTheme} {
515
+
516
+ --default-font-family: "Noto Sans SC", "PingFang SC", "Microsoft YaHei UI", system-ui, sans-serif;
517
+ --code-font-family: ui-monospace, "SF Mono", "JetBrains Mono", Consolas, monospace;
518
+ --font-weight-medium: 500;
519
+ --font-size-1: 13px;
520
+ --font-size-2: 14px;
521
+ --font-size-3: 16px;
522
+ --font-size-4: 18px;
523
+ --font-size-5: 20px;
524
+ --font-size-6: 24px;
525
+ --font-size-7: 28px;
526
+ --font-size-8: 35px;
527
+ --font-size-9: 60px;
528
+ --space-1: 4px;
529
+ --space-2: 8px;
530
+ --space-3: 12px;
531
+ --space-4: 16px;
532
+ --space-5: 24px;
533
+ --radius-2: 4px;
534
+ --radius-3: 6px;
535
+ --control-h: 34px;
536
+ --gray-1: #fcfcfc;
537
+ --gray-2: #f9f9f9;
538
+ --gray-3: #f0f0f0;
539
+ --gray-4: #e8e8e8;
540
+ --gray-5: #e0e0e0;
541
+ --gray-6: #d9d9d9;
542
+ --gray-7: #cecece;
543
+ --gray-8: #bbbbbb;
544
+ --gray-9: #8d8d8d;
545
+ --gray-10: #838383;
546
+ --gray-11: #646464;
547
+ --gray-12: #202020;
548
+ --gray-a2: #00000006;
549
+ --gray-a3: #0000000f;
550
+ --gray-a4: #00000017;
551
+ --gray-a5: #0000001f;
552
+ --gray-a6: #00000026;
553
+ --accent-8: #8da4ef;
554
+ --accent-9: #3e63dd;
555
+ --accent-10: #3358d4;
556
+ --accent-11: #3a5bc7;
557
+ --accent-a3: #0047f112;
558
+ --accent-a4: #0044ff1e;
559
+ --accent-a8: #0034dc72;
560
+ --accent-contrast: #ffffff;
561
+ --red-9: #e5484d;
562
+ --red-11: #ce2c31;
563
+ --green-9: #30a46c;
564
+ --green-11: #218358;
565
+ --amber-a4: #ffd40063;
566
+ --amber-a6: #eab5008c;
567
+ --color-background: #ffffff;
568
+ --color-panel-solid: #ffffff;
569
+ --color-surface: rgba(255, 255, 255, 0.85);
570
+ --shadow-2: 0 0 0 1px #0000000f, 0 0 0 0.5px rgba(0, 0, 0, 0.05), 0 1px 1px 0 #00000006, 0 2px 1px -1px rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.05);
571
+ --shadow-3: 0 0 0 1px #0000000f, 0 2px 3px -2px #0000000f, 0 3px 12px -4px rgba(0, 0, 0, 0.1), 0 4px 16px -8px rgba(0, 0, 0, 0.1);
572
+ --shadow-4: 0 0 0 1px #0000000f, 0 8px 40px rgba(0, 0, 0, 0.05), 0 12px 32px -16px #0000000f;
573
+ color-scheme: light;
574
+ }
575
+ html:not([data-theme]) body[data-ds-dark-theme] .zhihu-dock${standaloneOutsideTheme},
576
+ html:not([data-theme]) body[data-ds-dark-theme] .zhihu-toggle${standaloneOutsideTheme},
577
+ html:not([data-theme]) body[data-ds-dark-theme] .zhihu-panel${standaloneOutsideTheme},
578
+ :root[data-theme="dark"] .zhihu-dock${standaloneOutsideTheme},
579
+ :root[data-theme="dark"] .zhihu-toggle${standaloneOutsideTheme},
580
+ :root[data-theme="dark"] .zhihu-panel${standaloneOutsideTheme} {
581
+
582
+ --default-font-family: "Noto Sans SC", "PingFang SC", "Microsoft YaHei UI", system-ui, sans-serif;
583
+ --code-font-family: ui-monospace, "SF Mono", "JetBrains Mono", Consolas, monospace;
584
+ --font-weight-medium: 500;
585
+ --font-size-1: 13px;
586
+ --font-size-2: 14px;
587
+ --font-size-3: 16px;
588
+ --font-size-4: 18px;
589
+ --font-size-5: 20px;
590
+ --font-size-6: 24px;
591
+ --font-size-7: 28px;
592
+ --font-size-8: 35px;
593
+ --font-size-9: 60px;
594
+ --space-1: 4px;
595
+ --space-2: 8px;
596
+ --space-3: 12px;
597
+ --space-4: 16px;
598
+ --space-5: 24px;
599
+ --radius-2: 4px;
600
+ --radius-3: 6px;
601
+ --control-h: 34px;
602
+ --gray-1: #111111;
603
+ --gray-2: #191919;
604
+ --gray-3: #222222;
605
+ --gray-4: #2a2a2a;
606
+ --gray-5: #313131;
607
+ --gray-6: #3a3a3a;
608
+ --gray-7: #484848;
609
+ --gray-8: #606060;
610
+ --gray-9: #6e6e6e;
611
+ --gray-10: #7b7b7b;
612
+ --gray-11: #b4b4b4;
613
+ --gray-12: #eeeeee;
614
+ --gray-a2: #ffffff09;
615
+ --gray-a3: #ffffff12;
616
+ --gray-a4: #ffffff1b;
617
+ --gray-a5: #ffffff22;
618
+ --gray-a6: #ffffff2c;
619
+ --accent-8: #435db1;
620
+ --accent-9: #3e63dd;
621
+ --accent-10: #5472e4;
622
+ --accent-11: #9eb1ff;
623
+ --accent-a3: #2f62ff3c;
624
+ --accent-a4: #3566ff57;
625
+ --accent-a8: #5b81feac;
626
+ --accent-contrast: #ffffff;
627
+ --red-9: #e5484d;
628
+ --red-11: #ff9592;
629
+ --green-9: #30a46c;
630
+ --green-11: #3dd68c;
631
+ --amber-a4: #fc820032;
632
+ --amber-a6: #fd9b0051;
633
+ --color-background: #111111;
634
+ --color-panel-solid: #191919;
635
+ --color-surface: rgba(0, 0, 0, 0.25);
636
+ --shadow-2: 0 0 0 1px #ffffff2c, 0 0 0 0.5px rgba(0, 0, 0, 0.15), 0 1px 1px 0 rgba(0, 0, 0, 0.4), 0 2px 1px -1px rgba(0, 0, 0, 0.4), 0 1px 3px 0 rgba(0, 0, 0, 0.3);
637
+ --shadow-3: 0 0 0 1px #ffffff2c, 0 2px 3px -2px rgba(0, 0, 0, 0.15), 0 3px 8px -2px rgba(0, 0, 0, 0.4), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
638
+ --shadow-4: 0 0 0 1px #ffffff2c, 0 8px 40px rgba(0, 0, 0, 0.15), 0 12px 32px -16px rgba(0, 0, 0, 0.3);
639
+ color-scheme: dark;
640
+ }
641
+ `;
642
+ /** React KeyboardEvent: isComposing/keyCode live on nativeEvent, not the synthetic event. */
643
+ function nativeKeyFlags(event) {
644
+ const native = event.nativeEvent;
645
+ return {
646
+ isComposing: Boolean(native?.isComposing),
647
+ keyCode: typeof native?.keyCode === "number" ? native.keyCode : 0
648
+ };
649
+ }
650
+ function guardImeEnter(event) {
651
+ const { isComposing, keyCode } = nativeKeyFlags(event);
652
+ const enter = event.key === "Enter" || keyCode === 13;
653
+ if (!(isComposing && enter || keyCode === 229)) return false;
654
+ if (enter) event.preventDefault();
655
+ return true;
656
+ }
657
+ function zhihuQueryKeyDown(event, run) {
658
+ if (guardImeEnter(event)) return;
659
+ if (event.key === "Enter" && !run.disabled) {
660
+ event.preventDefault();
661
+ run.search();
662
+ }
663
+ }
664
+ function dockEscapeKeyDown(event, run) {
665
+ if (event.key !== "Escape") return;
666
+ event.stopPropagation();
667
+ if (run.loading) return;
668
+ run.close();
669
+ }
670
+ function hostComponentsFromRenderProps(props) {
671
+ if (!props || typeof props !== "object") return {};
672
+ const record = props;
673
+ const sources = [record];
674
+ if (record.owner && typeof record.owner === "object") sources.push(record.owner);
675
+ let Select;
676
+ let Dialog;
677
+ let Button;
678
+ let Input;
679
+ for (const source of sources) {
680
+ if (typeof source.Select === "function") Select = source.Select;
681
+ if (typeof source.Dialog === "function") Dialog = source.Dialog;
682
+ if (typeof source.Button === "function") Button = source.Button;
683
+ if (typeof source.Input === "function") Input = source.Input;
684
+ }
685
+ return {
686
+ Select,
687
+ Dialog,
688
+ Button,
689
+ Input
690
+ };
691
+ }
692
+ function renderInput(Input, props) {
693
+ if (Input) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, { ...props });
694
+ const { onChange, ...rest } = props;
695
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
696
+ ...rest,
697
+ onChange: (event) => onChange(event.target.value)
698
+ });
699
+ }
700
+ function renderSelect(Select, props, className) {
701
+ if (Select) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, { ...props });
702
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
703
+ className,
704
+ value: props.value,
705
+ disabled: props.disabled,
706
+ "aria-label": props["aria-label"],
707
+ onChange: (event) => props.onChange(event.target.value),
708
+ children: props.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
709
+ value: option.value,
710
+ children: option.label
711
+ }, option.value === "" ? "__empty" : option.value))
712
+ });
713
+ }
714
+ //#endregion
715
+ //#region ../dsh-editor-seats/src/seat-button.tsx
716
+ const SeatButton = (0, react.forwardRef)(function SeatButton(props, ref) {
717
+ const { host, variant, className, ...rest } = props;
718
+ if (host) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(host, {
719
+ ref,
720
+ variant,
721
+ className,
722
+ ...rest
723
+ });
724
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
725
+ ref,
726
+ type: "button",
727
+ className: [variant === "primary" ? "primary-action" : variant === "danger" ? "danger-action" : variant === "icon" ? "icon-button" : "", className].filter(Boolean).join(" "),
728
+ ...rest
729
+ });
730
+ });
731
+ //#endregion
732
+ //#region src/client.tsx
733
+ const name = "dsh-zhihu-client";
734
+ const inject = [
735
+ "slots",
736
+ "connection",
737
+ "remote",
738
+ "remote.credentials"
739
+ ];
740
+ const SLOT_ID = "zhihu";
741
+ const SLOT_ORDER = 120;
742
+ const SLOT_LABEL = "知乎资料";
743
+ const activityDots = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
744
+ className: "zhihu-dots",
745
+ "aria-hidden": "true",
746
+ children: [
747
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {})
750
+ ]
751
+ });
752
+ function wrapCredentials(remote) {
753
+ return {
754
+ async describe({ refs }) {
755
+ const result = await remote.describe(refs);
756
+ if (!result.ok) return result;
757
+ return {
758
+ ok: true,
759
+ value: { credentials: result.value }
760
+ };
761
+ },
762
+ set: ({ ref, value }) => remote.set(ref, value),
763
+ unset: ({ ref }) => remote.unset(ref)
764
+ };
765
+ }
766
+ const ZHIHU_CONSOLE_URL = "https://developer.zhihu.com";
767
+ const KB_MANAGE_URL = "https://zhida.zhihu.com/repositories/square";
768
+ function injectStyles() {
769
+ if (typeof document === "undefined") return null;
770
+ const style = document.createElement("style");
771
+ style.setAttribute("data-plugin", "@klarkxy/dsh-zhihu");
772
+ style.setAttribute("data-dsh-zhihu-styles", "");
773
+ style.textContent = zhihuClientStyles;
774
+ document.head.appendChild(style);
775
+ return style;
776
+ }
777
+ /** Only http(s) links may render as anchors; anything else degrades to text. */
778
+ function safeUrl(url) {
779
+ return /^https?:\/\//i.test(url) ? url : null;
780
+ }
781
+ /** Credential absence and transport failure read differently from a plain bad request. */
782
+ function failureOf(code, message) {
783
+ if (code === "token-missing") return {
784
+ kind: "credential",
785
+ text: "未配置知乎 Access Secret 或凭证不可用,请到「设置」页完成配置。"
786
+ };
787
+ return {
788
+ kind: "request",
789
+ text: `请求失败:${message}`
790
+ };
791
+ }
792
+ function networkFailure(cause) {
793
+ return {
794
+ kind: "network",
795
+ text: `网络或连接失败:${cause instanceof Error ? cause.message : String(cause)}`
796
+ };
797
+ }
798
+ const MODE_LABEL = {
799
+ search: "站内搜索",
800
+ global: "全网搜索",
801
+ hot: "知乎热榜",
802
+ knowledge: "知识库检索",
803
+ ask: "直答"
804
+ };
805
+ const MODE_ENDPOINT = {
806
+ search: "search",
807
+ global: "global.search",
808
+ hot: "hot.list",
809
+ knowledge: "knowledge.search",
810
+ ask: "ask"
811
+ };
812
+ const ASK_MODELS = [
813
+ {
814
+ value: "zhida-thinking-1p5",
815
+ label: "思考"
816
+ },
817
+ {
818
+ value: "zhida-fast-1p5",
819
+ label: "快速"
820
+ },
821
+ {
822
+ value: "zhida-agent",
823
+ label: "智能体"
824
+ }
825
+ ];
826
+ const SCOPE_OPTIONS = [
827
+ {
828
+ value: "public",
829
+ label: "公开库"
830
+ },
831
+ {
832
+ value: "personal",
833
+ label: "个人库"
834
+ },
835
+ {
836
+ value: "subscription",
837
+ label: "订阅库"
838
+ }
839
+ ];
840
+ function objectItems(value) {
841
+ if (typeof value !== "object" || value === null) return null;
842
+ const items = value.items;
843
+ if (!Array.isArray(items)) return null;
844
+ return items.filter((item) => !!item && typeof item === "object" && !Array.isArray(item));
845
+ }
846
+ function text(value, fallback = "") {
847
+ return typeof value === "string" ? value : fallback;
848
+ }
849
+ function parseOutcome(mode, value) {
850
+ if (mode === "ask") {
851
+ if (typeof value !== "object" || value === null) return null;
852
+ const row = value;
853
+ if (typeof row.content !== "string") return null;
854
+ return {
855
+ mode: "ask",
856
+ answer: {
857
+ query: text(row.query),
858
+ model: text(row.model),
859
+ content: row.content,
860
+ reasoning: text(row.reasoning)
861
+ }
862
+ };
863
+ }
864
+ const items = objectItems(value);
865
+ if (items === null) return null;
866
+ if (mode === "hot") return {
867
+ mode: "hot",
868
+ items: items.map((item) => ({
869
+ title: text(item.title, "(无标题)"),
870
+ url: text(item.url),
871
+ summary: text(item.summary)
872
+ }))
873
+ };
874
+ if (mode === "knowledge") return {
875
+ mode: "knowledge",
876
+ items: items.map((item) => ({
877
+ docName: text(item.docName, "(未命名文档)"),
878
+ originUrl: text(item.originUrl),
879
+ snippets: Array.isArray(item.snippets) ? item.snippets.filter((s) => typeof s === "string") : []
880
+ }))
881
+ };
882
+ return {
883
+ mode,
884
+ emptyReason: typeof value.emptyReason === "string" ? value.emptyReason : void 0,
885
+ items: items.map((item) => ({
886
+ title: text(item.title, "(无标题)"),
887
+ type: text(item.type, "内容"),
888
+ url: text(item.url),
889
+ summary: text(item.summary),
890
+ votes: typeof item.votes === "number" ? item.votes : 0,
891
+ comments: typeof item.comments === "number" ? item.comments : 0,
892
+ author: text(item.author, "匿名"),
893
+ editTime: text(item.editTime)
894
+ }))
895
+ };
896
+ }
897
+ function LinkOrText(props) {
898
+ const url = safeUrl(props.url);
899
+ if (!url) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
900
+ className: props.className,
901
+ children: props.label
902
+ });
903
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
904
+ className: props.className ?? "zhihu-link",
905
+ href: url,
906
+ target: "_blank",
907
+ rel: "noreferrer",
908
+ children: props.label
909
+ });
910
+ }
911
+ function OutcomeView(props) {
912
+ const { outcome, stale } = props;
913
+ let body = null;
914
+ let summary = "";
915
+ if (outcome.mode === "ask") {
916
+ summary = `直答(${outcome.answer.model || "默认模型"})`;
917
+ body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [outcome.answer.reasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
918
+ className: "zhihu-ask-reasoning",
919
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: "思考过程" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: outcome.answer.reasoning })]
920
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
921
+ className: "zhihu-ask-content",
922
+ children: outcome.answer.content
923
+ })] });
924
+ } else if (outcome.items.length === 0) {
925
+ const reason = outcome.mode === "search" || outcome.mode === "global" ? outcome.emptyReason : void 0;
926
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
927
+ "data-testid": "zhihu-results",
928
+ className: "zhihu-results",
929
+ children: [stale ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
930
+ className: "zhihu-stale",
931
+ role: "status",
932
+ children: "查询已变化,以下内容对应旧查询,请重新搜索。"
933
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
934
+ className: "zhihu-results-summary",
935
+ children: `未找到相关结果${reason ? `(${reason})` : ""}。`
936
+ })]
937
+ });
938
+ } else if (outcome.mode === "knowledge") {
939
+ summary = `知识库检索共 ${outcome.items.length} 条`;
940
+ body = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
941
+ className: "zhihu-result-list",
942
+ children: outcome.items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
943
+ className: "zhihu-result-item",
944
+ children: [
945
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
946
+ className: "zhihu-result-title",
947
+ children: item.docName
948
+ }),
949
+ item.originUrl && safeUrl(item.originUrl) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkOrText, {
950
+ url: item.originUrl,
951
+ label: item.originUrl
952
+ }) : null,
953
+ item.snippets.map((snippet, snippetIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
954
+ className: "zhihu-result-snippet",
955
+ children: snippet
956
+ }, snippetIndex))
957
+ ]
958
+ }, `${item.docName}:${index}`))
959
+ });
960
+ } else if (outcome.mode === "hot") {
961
+ summary = `知乎热榜共 ${outcome.items.length} 条`;
962
+ body = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
963
+ className: "zhihu-result-list",
964
+ children: outcome.items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
965
+ className: "zhihu-result-item",
966
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkOrText, {
967
+ url: item.url,
968
+ label: item.title,
969
+ className: "zhihu-result-title zhihu-link"
970
+ }), item.summary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
971
+ className: "zhihu-result-summary",
972
+ children: item.summary
973
+ }) : null]
974
+ }, `${index}`))
975
+ });
976
+ } else {
977
+ summary = `${MODE_LABEL[outcome.mode]}共 ${outcome.items.length} 条`;
978
+ body = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
979
+ className: "zhihu-result-list",
980
+ children: outcome.items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
981
+ className: "zhihu-result-item",
982
+ children: [
983
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkOrText, {
984
+ url: item.url,
985
+ label: item.title,
986
+ className: "zhihu-result-title zhihu-link"
987
+ }),
988
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
989
+ className: "zhihu-result-meta",
990
+ children: `类型:${item.type} 作者:${item.author} 赞同 ${item.votes} 评论 ${item.comments}${item.editTime ? ` 时间:${item.editTime}` : ""}`
991
+ }),
992
+ item.summary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
993
+ className: "zhihu-result-summary",
994
+ children: item.summary
995
+ }) : null
996
+ ]
997
+ }, `${item.title}:${index}`))
998
+ });
999
+ }
1000
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1001
+ "data-testid": "zhihu-results",
1002
+ className: "zhihu-results",
1003
+ children: [
1004
+ stale ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1005
+ className: "zhihu-stale",
1006
+ role: "status",
1007
+ children: "查询已变化,以下内容对应旧查询,请重新搜索。"
1008
+ }) : null,
1009
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1010
+ className: "zhihu-results-summary",
1011
+ "aria-live": "polite",
1012
+ children: summary
1013
+ }),
1014
+ body
1015
+ ]
1016
+ });
1017
+ }
1018
+ const LEGAL_API_KEY = /^[\x21-\x7E]+$/;
1019
+ const ENV_LINE = /^[A-Z][A-Z0-9_]*=[^=]/;
1020
+ function keyFailure(draft) {
1021
+ if (draft.length === 0) return void 0;
1022
+ const value = draft.trim();
1023
+ if (value.length === 0) return "blank";
1024
+ if (ENV_LINE.test(value) || !LEGAL_API_KEY.test(value)) return "illegal";
1025
+ }
1026
+ /**
1027
+ * Track mount lifetime so late resolves after tab change/close never touch
1028
+ * state. Setup re-marks alive so StrictMode effect replay (mount → cleanup →
1029
+ * setup) does not leave the flag stuck at false. Used only where the callee
1030
+ * (the credentials API) takes no AbortSignal — sections whose rpc.call accepts
1031
+ * a signal use a per-section gate instead.
1032
+ */
1033
+ function useAlive() {
1034
+ const alive = (0, react.useRef)(false);
1035
+ (0, react.useEffect)(() => {
1036
+ alive.current = true;
1037
+ return () => {
1038
+ alive.current = false;
1039
+ };
1040
+ }, []);
1041
+ return alive;
1042
+ }
1043
+ function CapabilitiesNote() {
1044
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
1045
+ className: "zhihu-guide",
1046
+ "data-testid": "zhihu-capabilities",
1047
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: "可用能力" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1048
+ className: "zhihu-hint",
1049
+ children: "站内搜索、全网搜索、热榜、直答和知识库检索。全网搜索可在「网络搜索」中启用。"
1050
+ })]
1051
+ });
1052
+ }
1053
+ function SettingsShell(props) {
1054
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1055
+ className: "zhihu-settings",
1056
+ "data-testid": "zhihu-settings",
1057
+ "aria-label": "知乎凭证设置",
1058
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CapabilitiesNote, {}), props.children]
1059
+ });
1060
+ }
1061
+ function SettingsSection(props) {
1062
+ const { credentials } = props;
1063
+ const alive = useAlive();
1064
+ const [state, setState] = (0, react.useState)({ status: "loading" });
1065
+ const [keyDraft, setKeyDraft] = (0, react.useState)("");
1066
+ const [busy, setBusy] = (0, react.useState)(false);
1067
+ const [failure, setFailure] = (0, react.useState)(void 0);
1068
+ const [note, setNote] = (0, react.useState)(void 0);
1069
+ const load = (0, react.useCallback)(async () => {
1070
+ setState((current) => current.status === "ready" ? current : { status: "loading" });
1071
+ try {
1072
+ const result = await credentials.describe({ refs: [ZHIHU_CREDENTIAL_REF] });
1073
+ if (!alive.current) return;
1074
+ if (!result.ok) {
1075
+ setState({
1076
+ status: "error",
1077
+ error: result.error.message
1078
+ });
1079
+ return;
1080
+ }
1081
+ setState({
1082
+ status: "ready",
1083
+ credential: result.value.credentials[ZHIHU_CREDENTIAL_REF]
1084
+ });
1085
+ } catch (cause) {
1086
+ if (!alive.current) return;
1087
+ setState({
1088
+ status: "error",
1089
+ error: cause instanceof Error ? cause.message : String(cause)
1090
+ });
1091
+ }
1092
+ }, [credentials, alive]);
1093
+ (0, react.useEffect)(() => {
1094
+ load();
1095
+ }, [load]);
1096
+ if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1097
+ className: "zhihu-status",
1098
+ role: "status",
1099
+ children: [activityDots(), "正在读取凭证状态…"]
1100
+ }) });
1101
+ if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1102
+ className: "zhihu-error",
1103
+ role: "alert",
1104
+ children: [`读取失败:${state.error} `, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
1105
+ host: props.Button,
1106
+ className: "zhihu-button",
1107
+ onClick: () => void load(),
1108
+ children: "重试"
1109
+ })]
1110
+ }) });
1111
+ const credential = state.credential;
1112
+ const keyLocked = credential?.writable === false;
1113
+ const configured = credential?.configured === true;
1114
+ const draftFailure = keyFailure(keyDraft);
1115
+ const keyValue = keyDraft.trim();
1116
+ const statusText = keyLocked ? "由环境变量提供(只读)" : configured ? `已保存${credential?.source ? `(来源:${credential.source})` : ""}` : "未配置";
1117
+ const dotClass = keyLocked ? "zhihu-dot zhihu-dot-locked" : configured ? "zhihu-dot zhihu-dot-configured" : "zhihu-dot zhihu-dot-missing";
1118
+ const placeholder = keyLocked ? "由环境变量提供,无法在界面修改" : configured ? "已保存,输入新密钥可覆盖" : "粘贴知乎开放平台 Access Secret";
1119
+ const save = async () => {
1120
+ if (keyLocked || draftFailure !== void 0 || keyValue.length === 0 || busy) return;
1121
+ setBusy(true);
1122
+ setFailure(void 0);
1123
+ try {
1124
+ const result = await credentials.set({
1125
+ ref: ZHIHU_CREDENTIAL_REF,
1126
+ value: keyValue
1127
+ });
1128
+ if (!alive.current) return;
1129
+ if (!result.ok) {
1130
+ setFailure(result.error.message);
1131
+ return;
1132
+ }
1133
+ setKeyDraft("");
1134
+ setNote("已保存。");
1135
+ await load();
1136
+ } catch (cause) {
1137
+ if (!alive.current) return;
1138
+ setFailure(cause instanceof Error ? cause.message : String(cause));
1139
+ } finally {
1140
+ if (alive.current) setBusy(false);
1141
+ }
1142
+ };
1143
+ const clear = async () => {
1144
+ if (keyLocked || !configured || busy) return;
1145
+ setBusy(true);
1146
+ setFailure(void 0);
1147
+ try {
1148
+ const result = await credentials.unset({ ref: ZHIHU_CREDENTIAL_REF });
1149
+ if (!alive.current) return;
1150
+ if (!result.ok) {
1151
+ setFailure(result.error.message);
1152
+ return;
1153
+ }
1154
+ setKeyDraft("");
1155
+ setNote("已清除。");
1156
+ await load();
1157
+ } catch (cause) {
1158
+ if (!alive.current) return;
1159
+ setFailure(cause instanceof Error ? cause.message : String(cause));
1160
+ } finally {
1161
+ if (alive.current) setBusy(false);
1162
+ }
1163
+ };
1164
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsShell, { children: [
1165
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
1166
+ className: "zhihu-status-grid",
1167
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1168
+ className: "zhihu-status-label",
1169
+ children: "状态"
1170
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", {
1171
+ className: "zhihu-status-value",
1172
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1173
+ className: dotClass,
1174
+ "aria-hidden": true
1175
+ }), statusText]
1176
+ })]
1177
+ }),
1178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1179
+ className: "zhihu-field",
1180
+ children: [
1181
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1182
+ className: "zhihu-field-heading",
1183
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1184
+ className: "zhihu-field-label",
1185
+ id: "zhihu-access-secret-label",
1186
+ children: "Access Secret"
1187
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1188
+ className: "zhihu-link",
1189
+ href: ZHIHU_CONSOLE_URL,
1190
+ target: "_blank",
1191
+ rel: "noreferrer noopener",
1192
+ onClick: (event) => {
1193
+ const bridge = globalThis.dshWindow;
1194
+ if (bridge?.openExternal) {
1195
+ event.preventDefault();
1196
+ bridge.openExternal(ZHIHU_CONSOLE_URL);
1197
+ }
1198
+ },
1199
+ children: "获取密钥"
1200
+ })]
1201
+ }),
1202
+ renderInput(props.Input, {
1203
+ type: "password",
1204
+ className: "zhihu-input",
1205
+ value: keyDraft,
1206
+ placeholder,
1207
+ disabled: busy || keyLocked,
1208
+ "aria-label": "Access Secret",
1209
+ onChange: setKeyDraft
1210
+ }),
1211
+ draftFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1212
+ className: "zhihu-warning",
1213
+ role: "alert",
1214
+ children: draftFailure === "blank" ? "密钥不能只包含空白字符。" : "密钥含有非法字符(应为可打印 ASCII,且不是 ENV 赋值行)。"
1215
+ })
1216
+ ]
1217
+ }),
1218
+ failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1219
+ className: "zhihu-warning",
1220
+ role: "alert",
1221
+ children: failure
1222
+ }) : null,
1223
+ note !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1224
+ className: "zhihu-saved",
1225
+ role: "status",
1226
+ children: note
1227
+ }) : null,
1228
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1229
+ className: "zhihu-row",
1230
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
1231
+ host: props.Button,
1232
+ variant: "danger",
1233
+ className: "zhihu-button zhihu-button-danger",
1234
+ disabled: busy || keyLocked || !configured,
1235
+ onClick: () => void clear(),
1236
+ children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), "处理中…"] }) : "清除"
1237
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
1238
+ host: props.Button,
1239
+ variant: "primary",
1240
+ className: "zhihu-button zhihu-button-primary",
1241
+ disabled: busy || keyLocked || keyValue.length === 0 || draftFailure !== void 0,
1242
+ onClick: () => void save(),
1243
+ children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), "保存中…"] }) : "保存"
1244
+ })]
1245
+ })
1246
+ ] });
1247
+ }
1248
+ const USAGE_DAYS = 30;
1249
+ function isUsageSummary(value) {
1250
+ if (typeof value !== "object" || value === null) return false;
1251
+ return Array.isArray(value.days);
1252
+ }
1253
+ /** Local calendar day, kept in the client bundle so it does not pull the node usage domain. */
1254
+ function localDayKey(now = /* @__PURE__ */ new Date()) {
1255
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
1256
+ }
1257
+ /** `2026-09-15` → `9/15`, so axis labels stay short and do not clip. */
1258
+ function formatUsageDate(date) {
1259
+ const parts = date.split("-");
1260
+ if (parts.length !== 3) return date;
1261
+ const month = Number(parts[1]);
1262
+ const day = Number(parts[2]);
1263
+ if (!Number.isInteger(month) || !Number.isInteger(day) || month < 1 || day < 1) return date;
1264
+ return `${month}/${day}`;
1265
+ }
1266
+ function summarizeUsage(days, today) {
1267
+ let todayCalls = 0;
1268
+ let calls = 0;
1269
+ let failures = 0;
1270
+ let results = 0;
1271
+ const active = [];
1272
+ for (const day of days) {
1273
+ calls += day.calls;
1274
+ failures += day.failures;
1275
+ results += day.results;
1276
+ if (day.date === today) todayCalls = day.calls;
1277
+ if (day.calls > 0) active.push(day);
1278
+ }
1279
+ return {
1280
+ todayCalls,
1281
+ calls,
1282
+ failures,
1283
+ results,
1284
+ active: active.slice().reverse()
1285
+ };
1286
+ }
1287
+ const TICK_MIN_GAP = 3;
1288
+ /** Sparse windows label active days; nearby ticks collapse so labels do not overlap. */
1289
+ function usageChartTicks(days) {
1290
+ const last = days.length - 1;
1291
+ if (last < 0) return [];
1292
+ if (last === 0) return [0];
1293
+ const chosen = [];
1294
+ const accept = (index) => {
1295
+ if (chosen.some((item) => Math.abs(item - index) < TICK_MIN_GAP)) return;
1296
+ chosen.push(index);
1297
+ };
1298
+ const lastHasCalls = (days[last]?.calls ?? 0) > 0;
1299
+ if (lastHasCalls) accept(last);
1300
+ accept(0);
1301
+ const active = days.map((day, index) => ({
1302
+ index,
1303
+ calls: day.calls
1304
+ })).filter((row) => row.calls > 0).sort((left, right) => right.calls - left.calls || left.index - right.index);
1305
+ if (active.length > 0 && active.length <= 6) for (const row of active) accept(row.index);
1306
+ else {
1307
+ accept(Math.round(last / 3));
1308
+ accept(Math.round(2 * last / 3));
1309
+ }
1310
+ if (!lastHasCalls) accept(last);
1311
+ return chosen.sort((left, right) => left - right);
1312
+ }
1313
+ function usageYTicks(maxCalls) {
1314
+ if (maxCalls <= 1) return [0, 1];
1315
+ if (maxCalls === 2) return [
1316
+ 0,
1317
+ 1,
1318
+ 2
1319
+ ];
1320
+ const mid = Math.round(maxCalls / 2);
1321
+ return mid === 0 || mid === maxCalls ? [0, maxCalls] : [
1322
+ 0,
1323
+ mid,
1324
+ maxCalls
1325
+ ];
1326
+ }
1327
+ const CHART_WIDTH = 600;
1328
+ const CHART_HEIGHT = 176;
1329
+ const CHART_PAD = {
1330
+ top: 20,
1331
+ right: 12,
1332
+ bottom: 24,
1333
+ left: 36
1334
+ };
1335
+ function UsageChart(props) {
1336
+ const days = props.days;
1337
+ const maxCalls = Math.max(1, ...days.map((day) => day.calls));
1338
+ const plotLeft = CHART_PAD.left;
1339
+ const plotRight = CHART_WIDTH - CHART_PAD.right;
1340
+ const plotTop = CHART_PAD.top;
1341
+ const plotBottom = CHART_HEIGHT - CHART_PAD.bottom;
1342
+ const plotWidth = plotRight - plotLeft;
1343
+ const plotHeight = plotBottom - plotTop;
1344
+ const slot = plotWidth / days.length;
1345
+ const barWidth = Math.max(2, Math.min(14, slot - 2));
1346
+ const grid = usageYTicks(maxCalls).map((value) => {
1347
+ const y = plotBottom - value / maxCalls * plotHeight;
1348
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
1349
+ className: "zhihu-chart-grid",
1350
+ x1: plotLeft,
1351
+ x2: plotRight,
1352
+ y1: y,
1353
+ y2: y
1354
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1355
+ className: "zhihu-chart-axis",
1356
+ x: plotLeft - 6,
1357
+ y,
1358
+ textAnchor: "end",
1359
+ dominantBaseline: "middle",
1360
+ children: String(value)
1361
+ })] }, `y-${value}`);
1362
+ });
1363
+ const bars = days.map((day, index) => {
1364
+ const x = plotLeft + index * slot + (slot - barWidth) / 2;
1365
+ const okCalls = Math.max(0, day.calls - day.failures);
1366
+ const failHeight = day.failures / maxCalls * plotHeight;
1367
+ const okHeight = okCalls / maxCalls * plotHeight;
1368
+ const label = `${day.date}:调用 ${day.calls} 次,成功 ${okCalls} 次,失败 ${day.failures} 次,结果 ${day.results} 条`;
1369
+ const parts = [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1370
+ className: "zhihu-chart-hit",
1371
+ x: plotLeft + index * slot,
1372
+ y: plotTop,
1373
+ width: slot,
1374
+ height: plotHeight
1375
+ }, "hit")];
1376
+ if (okHeight > 0) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1377
+ className: "zhihu-chart-bar-ok",
1378
+ x,
1379
+ y: plotBottom - okHeight,
1380
+ width: barWidth,
1381
+ height: okHeight
1382
+ }, "ok"));
1383
+ if (failHeight > 0) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1384
+ className: "zhihu-chart-bar-fail",
1385
+ x,
1386
+ y: plotBottom - okHeight - failHeight,
1387
+ width: barWidth,
1388
+ height: failHeight
1389
+ }, "fail"));
1390
+ if (day.calls > 0) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1391
+ className: "zhihu-chart-value",
1392
+ x: x + barWidth / 2,
1393
+ y: plotBottom - okHeight - failHeight - 4,
1394
+ textAnchor: "middle",
1395
+ children: String(day.calls)
1396
+ }, "n"));
1397
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("title", { children: label }), parts] }, day.date);
1398
+ });
1399
+ const last = days.length - 1;
1400
+ const ticks = usageChartTicks(days).map((index) => {
1401
+ const day = days[index];
1402
+ if (!day) return null;
1403
+ const anchor = index === 0 ? "start" : index === last ? "end" : "middle";
1404
+ const tickX = index === 0 ? plotLeft : index === last ? plotRight : plotLeft + index * slot + slot / 2;
1405
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1406
+ className: "zhihu-chart-tick",
1407
+ x: tickX,
1408
+ y: 170,
1409
+ textAnchor: anchor,
1410
+ children: formatUsageDate(day.date)
1411
+ }, `x-${day.date}-${index}`);
1412
+ });
1413
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1414
+ className: "zhihu-chart",
1415
+ viewBox: `0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`,
1416
+ role: "img",
1417
+ "aria-label": `近 ${USAGE_DAYS} 天知乎工具调用 ${props.totals.calls} 次,失败 ${props.totals.failures} 次,结果 ${props.totals.results} 条`,
1418
+ children: [
1419
+ grid,
1420
+ bars,
1421
+ ticks
1422
+ ]
1423
+ });
1424
+ }
1425
+ function UsageSection(props) {
1426
+ const { rpc } = props;
1427
+ const gateRef = (0, react.useRef)(null);
1428
+ if (!gateRef.current) gateRef.current = createZhihuClientState();
1429
+ const gate = gateRef.current;
1430
+ const [state, setState] = (0, react.useState)({ status: "loading" });
1431
+ (0, react.useEffect)(() => () => gate.cancel(), [gate]);
1432
+ const load = (0, react.useCallback)(async () => {
1433
+ const { ticket, signal } = gate.begin();
1434
+ setState({ status: "loading" });
1435
+ try {
1436
+ const raw = await rpc.call(ZHIHU_RPC_CHANNEL, "usage.summary", { days: USAGE_DAYS }, signal);
1437
+ if (!gate.isCurrent(ticket)) return;
1438
+ const result = raw;
1439
+ if (!result.ok) {
1440
+ setState({
1441
+ status: "error",
1442
+ error: result.error.message
1443
+ });
1444
+ return;
1445
+ }
1446
+ if (!isUsageSummary(result.value)) {
1447
+ setState({
1448
+ status: "error",
1449
+ error: "响应格式与契约不符。"
1450
+ });
1451
+ return;
1452
+ }
1453
+ setState({
1454
+ status: "ready",
1455
+ days: result.value.days
1456
+ });
1457
+ } catch (cause) {
1458
+ if (!gate.isCurrent(ticket)) return;
1459
+ setState({
1460
+ status: "error",
1461
+ error: cause instanceof Error ? cause.message : String(cause)
1462
+ });
1463
+ }
1464
+ }, [gate, rpc]);
1465
+ (0, react.useEffect)(() => {
1466
+ load();
1467
+ }, [load]);
1468
+ if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1469
+ "data-testid": "zhihu-usage",
1470
+ "aria-label": "知乎调用用量",
1471
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1472
+ className: "zhihu-status",
1473
+ role: "status",
1474
+ children: [activityDots(), "正在读取用量…"]
1475
+ })
1476
+ });
1477
+ if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1478
+ "data-testid": "zhihu-usage",
1479
+ "aria-label": "知乎调用用量",
1480
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1481
+ className: "zhihu-error",
1482
+ role: "alert",
1483
+ children: [`读取失败:${state.error} `, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
1484
+ host: props.Button,
1485
+ className: "zhihu-button",
1486
+ onClick: () => void load(),
1487
+ children: "重试"
1488
+ })]
1489
+ })
1490
+ });
1491
+ const totals = summarizeUsage(state.days, localDayKey());
1492
+ const hasAny = totals.calls > 0;
1493
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1494
+ className: "zhihu-usage",
1495
+ "data-testid": "zhihu-usage",
1496
+ "aria-label": "知乎调用用量",
1497
+ children: [
1498
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1499
+ className: "zhihu-usage-intro",
1500
+ children: "本机搜索、问答、热榜和知识库的调用次数,不是知乎官方配额或费用。"
1501
+ }),
1502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1503
+ className: "zhihu-usage-cards",
1504
+ children: [
1505
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
1506
+ label: "今日调用",
1507
+ value: totals.todayCalls,
1508
+ unit: "次"
1509
+ }),
1510
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
1511
+ label: `近 ${USAGE_DAYS} 天`,
1512
+ value: totals.calls,
1513
+ unit: "次"
1514
+ }),
1515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
1516
+ label: "失败",
1517
+ value: totals.failures,
1518
+ unit: "次"
1519
+ }),
1520
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
1521
+ label: "结果条数",
1522
+ value: totals.results,
1523
+ unit: "条"
1524
+ })
1525
+ ]
1526
+ }),
1527
+ hasAny ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [
1528
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1529
+ className: "zhihu-usage-heading",
1530
+ children: "每日调用次数"
1531
+ }),
1532
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageChart, {
1533
+ days: state.days,
1534
+ totals
1535
+ }),
1536
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
1537
+ className: "zhihu-usage-legend",
1538
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1539
+ className: "zhihu-chart-chip zhihu-chart-chip-ok",
1540
+ "aria-hidden": "true"
1541
+ }), "成功"] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1542
+ className: "zhihu-chart-chip zhihu-chart-chip-fail",
1543
+ "aria-hidden": "true"
1544
+ }), "失败"] })]
1545
+ }),
1546
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1547
+ className: "zhihu-usage-heading",
1548
+ children: "有记录的日期"
1549
+ }),
1550
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1551
+ className: "zhihu-usage-table-wrap",
1552
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1553
+ className: "zhihu-usage-table",
1554
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1555
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1556
+ scope: "col",
1557
+ children: "日期"
1558
+ }),
1559
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1560
+ scope: "col",
1561
+ children: "调用"
1562
+ }),
1563
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1564
+ scope: "col",
1565
+ children: "成功"
1566
+ }),
1567
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1568
+ scope: "col",
1569
+ children: "失败"
1570
+ }),
1571
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1572
+ scope: "col",
1573
+ children: "结果条数"
1574
+ })
1575
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: totals.active.map((day) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1576
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1577
+ scope: "row",
1578
+ children: day.date
1579
+ }),
1580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: String(day.calls) }),
1581
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: String(Math.max(0, day.calls - day.failures)) }),
1582
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: String(day.failures) }),
1583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: String(day.results) })
1584
+ ] }, day.date)) })]
1585
+ })
1586
+ })
1587
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1588
+ className: "zhihu-status",
1589
+ children: "近 30 天暂无调用记录。"
1590
+ })
1591
+ ]
1592
+ });
1593
+ }
1594
+ function UsageCard(props) {
1595
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1596
+ className: "zhihu-usage-card",
1597
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1598
+ className: "zhihu-usage-card-label",
1599
+ children: props.label
1600
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1601
+ className: "zhihu-usage-card-value",
1602
+ children: [String(props.value), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1603
+ className: "zhihu-usage-card-unit",
1604
+ children: props.unit
1605
+ })]
1606
+ })]
1607
+ });
1608
+ }
1609
+ function isKnowledgeBaseList(value) {
1610
+ if (typeof value !== "object" || value === null) return false;
1611
+ return Array.isArray(value.bases);
1612
+ }
1613
+ /** 分块 base64,避免一次性展开大字符串。 */
1614
+ function toBase64(buffer) {
1615
+ const bytes = new Uint8Array(buffer);
1616
+ let binary = "";
1617
+ const chunk = 32768;
1618
+ for (let offset = 0; offset < bytes.length; offset += chunk) binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk));
1619
+ return btoa(binary);
1620
+ }
1621
+ const KB_ACCEPT = ".pdf,.md,.txt,.ppt,.pptx,.xlsx,.xls,.docx,.doc,.webp,.png,.jpg,.mobi,.epub,.csv,.azw3";
1622
+ const KB_MAX_BYTES = 20971520;
1623
+ function formatSize(size) {
1624
+ if (size >= 1048576) return `${(size / 1048576).toFixed(1)} MB`;
1625
+ if (size >= 1024) return `${(size / 1024).toFixed(1)} KB`;
1626
+ return `${size} B`;
1627
+ }
1628
+ function KnowledgeSection(props) {
1629
+ const { rpc } = props;
1630
+ const gateRef = (0, react.useRef)(null);
1631
+ if (!gateRef.current) gateRef.current = createZhihuClientState();
1632
+ const gate = gateRef.current;
1633
+ const [list, setList] = (0, react.useState)({ status: "loading" });
1634
+ const [baseId, setBaseId] = (0, react.useState)("");
1635
+ const [file, setFile] = (0, react.useState)(void 0);
1636
+ const [fileKey, setFileKey] = (0, react.useState)(0);
1637
+ const fileRef = (0, react.useRef)(null);
1638
+ const [busy, setBusy] = (0, react.useState)(false);
1639
+ const [note, setNote] = (0, react.useState)(void 0);
1640
+ const [failure, setFailure] = (0, react.useState)(void 0);
1641
+ (0, react.useEffect)(() => () => gate.cancel(), [gate]);
1642
+ const load = (0, react.useCallback)(async () => {
1643
+ const { ticket, signal } = gate.begin();
1644
+ setList({ status: "loading" });
1645
+ try {
1646
+ const raw = await rpc.call(ZHIHU_RPC_CHANNEL, "knowledge.bases", {}, signal);
1647
+ if (!gate.isCurrent(ticket)) return;
1648
+ const result = raw;
1649
+ if (!result.ok) {
1650
+ setList({
1651
+ status: "error",
1652
+ failure: failureOf(result.error.code, result.error.message)
1653
+ });
1654
+ return;
1655
+ }
1656
+ if (!isKnowledgeBaseList(result.value)) {
1657
+ setList({
1658
+ status: "error",
1659
+ failure: {
1660
+ kind: "request",
1661
+ text: "响应格式与契约不符。"
1662
+ }
1663
+ });
1664
+ return;
1665
+ }
1666
+ setList({
1667
+ status: "ready",
1668
+ bases: result.value.bases
1669
+ });
1670
+ } catch (cause) {
1671
+ if (!gate.isCurrent(ticket)) return;
1672
+ setList({
1673
+ status: "error",
1674
+ failure: networkFailure(cause)
1675
+ });
1676
+ }
1677
+ }, [gate, rpc]);
1678
+ (0, react.useEffect)(() => {
1679
+ load();
1680
+ }, [load]);
1681
+ const upload = async () => {
1682
+ if (!file || busy) return;
1683
+ setFailure(void 0);
1684
+ setNote(void 0);
1685
+ if (file.size > KB_MAX_BYTES) {
1686
+ setFailure("文件超过 20 MB 上限。");
1687
+ return;
1688
+ }
1689
+ const { ticket, signal } = gate.begin();
1690
+ setBusy(true);
1691
+ try {
1692
+ const contentBase64 = toBase64(await file.arrayBuffer());
1693
+ if (!gate.isCurrent(ticket)) return;
1694
+ const raw = await rpc.call(ZHIHU_RPC_CHANNEL, "knowledge.upload", {
1695
+ fileName: file.name,
1696
+ contentBase64,
1697
+ ...baseId ? { knowledgeBaseId: baseId } : {}
1698
+ }, signal);
1699
+ if (!gate.isCurrent(ticket)) return;
1700
+ const result = raw;
1701
+ if (!result.ok) {
1702
+ setFailure(`上传失败:${result.error.message}`);
1703
+ return;
1704
+ }
1705
+ setNote("已上传到知乎知识库。");
1706
+ setFile(void 0);
1707
+ setFileKey((key) => key + 1);
1708
+ await load();
1709
+ } catch (cause) {
1710
+ if (!gate.isCurrent(ticket)) return;
1711
+ setFailure(`上传失败:${cause instanceof Error ? cause.message : String(cause)}`);
1712
+ } finally {
1713
+ setBusy(false);
1714
+ }
1715
+ };
1716
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1717
+ className: "zhihu-knowledge",
1718
+ "data-testid": "zhihu-knowledge",
1719
+ "aria-label": "知乎知识库",
1720
+ children: [
1721
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1722
+ className: "zhihu-intro",
1723
+ children: "把参考资料上传到知乎知识库,供搭档检索。文件会进入知乎云端,请勿上传未发表手稿。"
1724
+ }),
1725
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1726
+ className: "zhihu-link",
1727
+ href: KB_MANAGE_URL,
1728
+ target: "_blank",
1729
+ rel: "noreferrer",
1730
+ children: "管理知识库"
1731
+ }) }),
1732
+ list.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1733
+ className: "zhihu-status",
1734
+ role: "status",
1735
+ children: [activityDots(), "正在读取知识库列表…"]
1736
+ }) : null,
1737
+ list.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1738
+ className: "zhihu-error",
1739
+ role: "alert",
1740
+ children: [`${list.failure.text} `, list.failure.kind !== "credential" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
1741
+ host: props.Button,
1742
+ className: "zhihu-button",
1743
+ onClick: () => void load(),
1744
+ children: "重试"
1745
+ }) : null]
1746
+ }) : null,
1747
+ list.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1748
+ className: "zhihu-field",
1749
+ children: [
1750
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1751
+ className: "zhihu-field-label",
1752
+ htmlFor: "zhihu-kb-base",
1753
+ children: "目标知识库"
1754
+ }),
1755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1756
+ className: "zhihu-row",
1757
+ children: [renderSelect(props.Select, {
1758
+ value: baseId,
1759
+ disabled: busy,
1760
+ "aria-label": "目标知识库",
1761
+ onChange: setBaseId,
1762
+ options: [{
1763
+ value: "",
1764
+ label: "默认知识库"
1765
+ }, ...list.bases.map((base) => ({
1766
+ value: base.id,
1767
+ label: `${base.name}${base.isDefault ? "(默认)" : ""} · ${base.contentCount} 篇`
1768
+ }))]
1769
+ }, "zhihu-select"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
1770
+ host: props.Button,
1771
+ className: "zhihu-button",
1772
+ disabled: busy,
1773
+ onClick: () => void load(),
1774
+ children: "刷新"
1775
+ })]
1776
+ }),
1777
+ list.bases.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1778
+ className: "zhihu-hint",
1779
+ children: "暂无可用知识库。"
1780
+ }) : null
1781
+ ]
1782
+ }) : null,
1783
+ list.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1784
+ className: "zhihu-field",
1785
+ children: [
1786
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1787
+ className: "zhihu-field-label",
1788
+ children: "选择文件"
1789
+ }),
1790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1791
+ ref: fileRef,
1792
+ id: "zhihu-kb-file",
1793
+ type: "file",
1794
+ accept: KB_ACCEPT,
1795
+ hidden: true,
1796
+ className: "zhihu-file",
1797
+ disabled: busy,
1798
+ onChange: (event) => setFile(event.target.files?.[0])
1799
+ }, fileKey),
1800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
1801
+ host: props.Button,
1802
+ className: "zhihu-button",
1803
+ disabled: busy,
1804
+ onClick: () => fileRef.current?.click(),
1805
+ children: file ? file.name : "选择文件"
1806
+ })
1807
+ ]
1808
+ }) : null,
1809
+ list.status === "ready" && file ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1810
+ className: "zhihu-upload-confirm",
1811
+ children: `确认将「${file.name}」(${formatSize(file.size)})上传到${baseId ? "所选知识库" : "默认知识库"}?文件会进入知乎云端。`
1812
+ }) : null,
1813
+ list.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1814
+ className: "zhihu-row",
1815
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
1816
+ host: props.Button,
1817
+ variant: "primary",
1818
+ className: "zhihu-button zhihu-button-primary",
1819
+ disabled: busy || !file,
1820
+ onClick: () => void upload(),
1821
+ children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), "上传中…"] }) : "确认上传"
1822
+ })
1823
+ }) : null,
1824
+ note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1825
+ className: "zhihu-saved",
1826
+ role: "status",
1827
+ children: note
1828
+ }) : null,
1829
+ failure ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1830
+ className: "zhihu-warning",
1831
+ role: "alert",
1832
+ children: failure
1833
+ }) : null
1834
+ ]
1835
+ });
1836
+ }
1837
+ const TAB_LABEL = {
1838
+ search: "搜索",
1839
+ settings: "设置",
1840
+ usage: "用量",
1841
+ knowledge: "知识库"
1842
+ };
1843
+ const OVERLAY_TABS = [
1844
+ "search",
1845
+ "settings",
1846
+ "usage",
1847
+ "knowledge"
1848
+ ];
1849
+ const SETTINGS_TABS = [
1850
+ "settings",
1851
+ "usage",
1852
+ "knowledge",
1853
+ "search"
1854
+ ];
1855
+ function tabLabel(tab, surface) {
1856
+ if (tab === "search" && surface === "settings") return "连接测试";
1857
+ return TAB_LABEL[tab];
1858
+ }
1859
+ function ZhihuDock(props) {
1860
+ const { rpc, credentials, Select, Dialog, Button, Input } = props;
1861
+ const surface = props.surface === "settings" ? "settings" : "overlay";
1862
+ const tabs = surface === "settings" ? SETTINGS_TABS : OVERLAY_TABS;
1863
+ const gateRef = (0, react.useRef)(null);
1864
+ if (!gateRef.current) gateRef.current = createZhihuClientState();
1865
+ const gate = gateRef.current;
1866
+ const [open, setOpen] = (0, react.useState)(surface === "settings");
1867
+ const [tab, setTab] = (0, react.useState)(surface === "settings" ? "settings" : "search");
1868
+ const [query, setQuery] = (0, react.useState)("");
1869
+ const [mode, setMode] = (0, react.useState)("search");
1870
+ const [askModel, setAskModel] = (0, react.useState)("zhida-thinking-1p5");
1871
+ const [scopes, setScopes] = (0, react.useState)(["public"]);
1872
+ const [phase, setPhase] = (0, react.useState)("idle");
1873
+ const [failure, setFailure] = (0, react.useState)(null);
1874
+ const [outcome, setOutcome] = (0, react.useState)(null);
1875
+ const [revision, setRevision] = (0, react.useState)(0);
1876
+ const [resultRevision, setResultRevision] = (0, react.useState)(0);
1877
+ const toggleRef = (0, react.useRef)(null);
1878
+ const queryRef = (0, react.useRef)(null);
1879
+ const wasOpen = (0, react.useRef)(false);
1880
+ (0, react.useEffect)(() => {
1881
+ if (Dialog || surface === "settings") return;
1882
+ if (open) {
1883
+ wasOpen.current = true;
1884
+ queryRef.current?.focus();
1885
+ } else if (wasOpen.current) {
1886
+ wasOpen.current = false;
1887
+ toggleRef.current?.focus();
1888
+ }
1889
+ }, [
1890
+ open,
1891
+ Dialog,
1892
+ surface
1893
+ ]);
1894
+ (0, react.useEffect)(() => () => gate.cancel(), [gate]);
1895
+ const runSearch = (0, react.useCallback)(async () => {
1896
+ if (mode !== "hot" && !query.trim()) return;
1897
+ const { ticket, signal } = gate.begin();
1898
+ setPhase("loading");
1899
+ setFailure(null);
1900
+ const payload = mode === "hot" ? { limit: 10 } : mode === "ask" ? {
1901
+ query: query.trim(),
1902
+ model: askModel
1903
+ } : mode === "knowledge" ? {
1904
+ query: query.trim(),
1905
+ limit: 5,
1906
+ recallScopes: scopes
1907
+ } : mode === "global" ? {
1908
+ query: query.trim(),
1909
+ count: 10
1910
+ } : {
1911
+ query: query.trim(),
1912
+ count: 5
1913
+ };
1914
+ try {
1915
+ const response = await rpc.call(ZHIHU_RPC_CHANNEL, MODE_ENDPOINT[mode], payload, signal);
1916
+ if (!gate.isCurrent(ticket)) return;
1917
+ if (response.ok) {
1918
+ const parsed = parseOutcome(mode, response.value);
1919
+ if (!parsed) {
1920
+ setFailure({
1921
+ kind: "request",
1922
+ text: "响应格式与契约不符。"
1923
+ });
1924
+ setPhase("error");
1925
+ return;
1926
+ }
1927
+ setOutcome(parsed);
1928
+ setResultRevision(ticket.revision);
1929
+ setPhase("done");
1930
+ } else if (response.error.code === "cancelled") setPhase("idle");
1931
+ else {
1932
+ setFailure(failureOf(response.error.code, response.error.message));
1933
+ setPhase("error");
1934
+ }
1935
+ } catch (cause) {
1936
+ if (!gate.isCurrent(ticket)) return;
1937
+ setFailure(networkFailure(cause));
1938
+ setPhase("error");
1939
+ }
1940
+ }, [
1941
+ gate,
1942
+ rpc,
1943
+ mode,
1944
+ query,
1945
+ askModel,
1946
+ scopes
1947
+ ]);
1948
+ const resetToIdle = () => {
1949
+ setPhase((current) => current === "loading" || current === "error" ? "idle" : current);
1950
+ setFailure(null);
1951
+ };
1952
+ const onQueryChange = (value) => {
1953
+ setQuery(value);
1954
+ setRevision(gate.noteInput());
1955
+ resetToIdle();
1956
+ };
1957
+ const onModeChange = (next) => {
1958
+ if (next === mode) return;
1959
+ setRevision(gate.noteInput());
1960
+ setMode(next);
1961
+ resetToIdle();
1962
+ };
1963
+ const onTabChange = (next) => {
1964
+ if (next === tab) return;
1965
+ gate.cancel();
1966
+ setPhase((current) => current === "loading" ? "idle" : current);
1967
+ setTab(next);
1968
+ };
1969
+ const toggleScope = (scope) => {
1970
+ setRevision(gate.noteInput());
1971
+ resetToIdle();
1972
+ setScopes((current) => {
1973
+ const next = current.includes(scope) ? current.filter((item) => item !== scope) : [...current, scope];
1974
+ return next.length > 0 ? next : current;
1975
+ });
1976
+ };
1977
+ const onAskModelChange = (next) => {
1978
+ if (next === askModel) return;
1979
+ setRevision(gate.noteInput());
1980
+ setAskModel(next);
1981
+ resetToIdle();
1982
+ };
1983
+ const closePanel = (0, react.useCallback)(() => {
1984
+ gate.cancel();
1985
+ setPhase("idle");
1986
+ setOpen(false);
1987
+ }, [gate]);
1988
+ const onPanelKeyDown = (event) => {
1989
+ dockEscapeKeyDown(event, {
1990
+ loading: phase === "loading",
1991
+ close: closePanel
1992
+ });
1993
+ };
1994
+ const onQueryKeyDown = (event) => {
1995
+ zhihuQueryKeyDown(event, {
1996
+ disabled: isSearchDisabled(),
1997
+ search: () => {
1998
+ runSearch();
1999
+ }
2000
+ });
2001
+ };
2002
+ const isSearchDisabled = () => phase === "loading" || mode !== "hot" && !query.trim();
2003
+ const stale = outcome !== null && resultRevision !== revision;
2004
+ const searchDisabled = isSearchDisabled();
2005
+ const tablist = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2006
+ className: "zhihu-tabs",
2007
+ role: "tablist",
2008
+ "aria-label": "知乎资料分区",
2009
+ onKeyDown: (event) => {
2010
+ if (![
2011
+ "ArrowLeft",
2012
+ "ArrowRight",
2013
+ "Home",
2014
+ "End"
2015
+ ].includes(event.key)) return;
2016
+ const buttons = [...event.currentTarget.querySelectorAll("[role=\"tab\"]")];
2017
+ const index = buttons.indexOf(event.target);
2018
+ if (index < 0) return;
2019
+ event.preventDefault();
2020
+ const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (index + (event.key === "ArrowRight" ? 1 : -1) + buttons.length) % buttons.length;
2021
+ buttons[next]?.focus();
2022
+ buttons[next]?.click();
2023
+ },
2024
+ children: tabs.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
2025
+ host: Button,
2026
+ type: "button",
2027
+ role: "tab",
2028
+ "aria-selected": tab === key,
2029
+ tabIndex: tab === key ? 0 : -1,
2030
+ className: "zhihu-tab",
2031
+ onClick: () => onTabChange(key),
2032
+ children: tabLabel(key, surface)
2033
+ }, key))
2034
+ }, "tabs");
2035
+ const body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2036
+ className: "zhihu-panel-body",
2037
+ children: [
2038
+ tab === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2039
+ role: "tabpanel",
2040
+ className: "zhihu-field",
2041
+ children: [
2042
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2043
+ className: "zhihu-row",
2044
+ children: [renderSelect(Select, {
2045
+ "aria-label": "搜索方式",
2046
+ value: mode,
2047
+ onChange: (next) => onModeChange(next),
2048
+ options: Object.keys(MODE_LABEL).map((key) => ({
2049
+ value: key,
2050
+ label: MODE_LABEL[key]
2051
+ }))
2052
+ }, "zhihu-select"), mode === "ask" ? renderSelect(Select, {
2053
+ "aria-label": "直答模型",
2054
+ value: askModel,
2055
+ onChange: (next) => onAskModelChange(next),
2056
+ options: ASK_MODELS.map((model) => ({
2057
+ value: model.value,
2058
+ label: model.label
2059
+ }))
2060
+ }, "zhihu-select") : null]
2061
+ }),
2062
+ mode === "knowledge" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2063
+ className: "zhihu-scopes",
2064
+ role: "group",
2065
+ "aria-label": "检索范围",
2066
+ children: SCOPE_OPTIONS.map((scope) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
2067
+ host: Button,
2068
+ role: "checkbox",
2069
+ "aria-checked": scopes.includes(scope.value),
2070
+ className: `zhihu-scope${scopes.includes(scope.value) ? " is-on" : ""}`,
2071
+ onClick: () => toggleScope(scope.value),
2072
+ children: scope.label
2073
+ }, scope.value))
2074
+ }) : null,
2075
+ renderInput(Input, {
2076
+ ref: queryRef,
2077
+ type: "search",
2078
+ className: "zhihu-input",
2079
+ "data-testid": "zhihu-query",
2080
+ "aria-label": "搜索关键词",
2081
+ placeholder: mode === "hot" ? "热榜无需关键词" : "输入关键词…",
2082
+ value: query,
2083
+ disabled: mode === "hot",
2084
+ onChange: onQueryChange,
2085
+ onKeyDown: onQueryKeyDown
2086
+ }),
2087
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2088
+ className: "zhihu-row",
2089
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
2090
+ host: Button,
2091
+ variant: "primary",
2092
+ className: "zhihu-button zhihu-button-primary",
2093
+ "data-testid": "zhihu-search",
2094
+ disabled: searchDisabled,
2095
+ onClick: () => void runSearch(),
2096
+ children: phase === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), "请求中…"] }) : mode === "hot" ? "获取热榜" : "搜索"
2097
+ }), phase === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
2098
+ host: Button,
2099
+ className: "zhihu-button",
2100
+ onClick: () => {
2101
+ gate.cancel();
2102
+ setPhase("idle");
2103
+ },
2104
+ children: "取消"
2105
+ }) : null]
2106
+ }),
2107
+ surface !== "settings" && phase === "idle" && mode !== "hot" && !query.trim() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2108
+ className: "zhihu-status",
2109
+ role: "status",
2110
+ children: "输入关键词后搜索。"
2111
+ }) : null,
2112
+ phase === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2113
+ className: "zhihu-status",
2114
+ role: "status",
2115
+ children: [activityDots(), "正在请求知乎…"]
2116
+ }) : null,
2117
+ phase === "error" && failure ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2118
+ className: "zhihu-error",
2119
+ role: "alert",
2120
+ children: failure.text
2121
+ }) : null,
2122
+ phase === "done" && outcome ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutcomeView, {
2123
+ outcome,
2124
+ stale
2125
+ }) : null
2126
+ ]
2127
+ }) : null,
2128
+ tab === "settings" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsSection, {
2129
+ credentials,
2130
+ Button,
2131
+ Input
2132
+ }) : null,
2133
+ tab === "usage" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageSection, {
2134
+ rpc,
2135
+ Button
2136
+ }) : null,
2137
+ tab === "knowledge" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnowledgeSection, {
2138
+ rpc,
2139
+ Select,
2140
+ Button
2141
+ }) : null
2142
+ ]
2143
+ }, "body");
2144
+ const inner = [
2145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2146
+ className: "zhihu-panel-header",
2147
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2148
+ className: "zhihu-panel-title",
2149
+ children: "知乎资料"
2150
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
2151
+ host: Button,
2152
+ className: "zhihu-panel-close",
2153
+ disabled: phase === "loading",
2154
+ onClick: closePanel,
2155
+ children: "关闭"
2156
+ })]
2157
+ }, "header"),
2158
+ tablist,
2159
+ body
2160
+ ];
2161
+ if (surface === "settings") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2162
+ className: "zhihu-settings-embed",
2163
+ "data-testid": "zhihu-settings-embed",
2164
+ "aria-label": SLOT_LABEL,
2165
+ children: [tablist, body]
2166
+ });
2167
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2168
+ className: "zhihu-dock",
2169
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeatButton, {
2170
+ host: Button,
2171
+ ref: toggleRef,
2172
+ className: "zhihu-toggle",
2173
+ "data-testid": "zhihu-open",
2174
+ onClick: () => setOpen(true),
2175
+ children: SLOT_LABEL
2176
+ }), Dialog ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
2177
+ open,
2178
+ onOpenChange: (next) => {
2179
+ if (!next) closePanel();
2180
+ else setOpen(true);
2181
+ },
2182
+ title: "知乎资料",
2183
+ className: "file-dialog zhihu-panel",
2184
+ overlayClassName: "file-dialog-overlay",
2185
+ dismissible: phase !== "loading",
2186
+ initialFocusRef: queryRef,
2187
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2188
+ "data-testid": "zhihu-panel",
2189
+ className: "zhihu-panel-inner",
2190
+ children: inner
2191
+ })
2192
+ }) : open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
2193
+ className: "zhihu-panel",
2194
+ "data-testid": "zhihu-panel",
2195
+ "aria-label": "知乎资料",
2196
+ onKeyDown: onPanelKeyDown,
2197
+ children: inner
2198
+ }) : null]
2199
+ });
2200
+ }
2201
+ function apply(ctx) {
2202
+ ctx.effect(() => {
2203
+ const style = injectStyles();
2204
+ return () => style?.remove();
2205
+ }, "zhihu.styles");
2206
+ const client = ctx;
2207
+ const overlayRender = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuDock, {
2208
+ rpc: client.connection.rpc,
2209
+ credentials: wrapCredentials(client.remote.credentials),
2210
+ surface: "overlay",
2211
+ ...hostComponentsFromRenderProps(props)
2212
+ });
2213
+ const settingsRender = (props) => {
2214
+ const host = hostComponentsFromRenderProps(props);
2215
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuDock, {
2216
+ rpc: client.connection.rpc,
2217
+ credentials: wrapCredentials(client.remote.credentials),
2218
+ surface: "settings",
2219
+ ...host
2220
+ });
2221
+ };
2222
+ client.slots.inject("shell.overlay", () => client.slots.register({
2223
+ name: "shell.overlay",
2224
+ id: SLOT_ID,
2225
+ order: SLOT_ORDER,
2226
+ label: SLOT_LABEL
2227
+ }, overlayRender));
2228
+ client.slots.inject("dsh-editor.settings.zhihu", () => client.slots.register({
2229
+ name: "dsh-editor.settings.zhihu",
2230
+ id: SLOT_ID,
2231
+ order: SLOT_ORDER,
2232
+ label: SLOT_LABEL
2233
+ }, settingsRender));
2234
+ }
2235
+ //#endregion
2236
+ exports.apply = apply;
2237
+ exports.formatUsageDate = formatUsageDate;
2238
+ exports.inject = inject;
2239
+ exports.name = name;
2240
+ exports.summarizeUsage = summarizeUsage;
2241
+ exports.usageChartTicks = usageChartTicks;
2242
+
2243
+ //# sourceMappingURL=client.inner.cjs.map
2244
+ return module.exports;
2245
+ }
2246
+ });