@klarkxy/dsh-zhihu 0.1.9 → 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/README.md +26 -22
- package/docs/README.zh-CN.md +26 -22
- package/lib/client.js +2012 -887
- package/lib/index.d.ts +1 -1
- package/lib/index.js +78 -103
- package/lib/index.js.map +1 -1
- package/lib/open-platform.js +8 -8
- package/lib/open-platform.js.map +1 -1
- package/lib/tools.js +11 -11
- package/lib/tools.js.map +1 -1
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +22 -15
package/lib/client.js
CHANGED
|
@@ -1,176 +1,54 @@
|
|
|
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
1
|
window.__ModuleLoader__.load({
|
|
155
2
|
id: "@klarkxy/dsh-zhihu",
|
|
156
3
|
factory: (dshRequire) => {
|
|
157
|
-
var
|
|
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
|
-
};
|
|
4
|
+
var require = dshRequire;
|
|
166
5
|
var module = { exports: {} };
|
|
167
6
|
var exports = module.exports;
|
|
168
7
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
169
8
|
let react = require("react");
|
|
9
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
170
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
|
+
//#region src/client-locale.ts
|
|
12
|
+
function zhihuLocale(value) {
|
|
13
|
+
return String(value ?? "zh").startsWith("zh") ? "zh" : "en";
|
|
14
|
+
}
|
|
15
|
+
function translator(locale = "zh") {
|
|
16
|
+
return (zh, en) => locale === "en" ? en : zh;
|
|
17
|
+
}
|
|
18
|
+
/** Components below the settings root read the locale here instead of threading a prop. */
|
|
19
|
+
const ZhihuLocaleContext = (0, react.createContext)("zh");
|
|
20
|
+
function useZhihuLocale() {
|
|
21
|
+
return (0, react.useContext)(ZhihuLocaleContext);
|
|
22
|
+
}
|
|
23
|
+
const TRANSLATORS = {
|
|
24
|
+
zh: translator("zh"),
|
|
25
|
+
en: translator("en")
|
|
26
|
+
};
|
|
27
|
+
function useT() {
|
|
28
|
+
return TRANSLATORS[useZhihuLocale()];
|
|
29
|
+
}
|
|
30
|
+
/** Follow the host locale when the service is present; standalone renders stay Chinese. */
|
|
31
|
+
function useHostLocale(service) {
|
|
32
|
+
const subscribe = (0, react.useCallback)((listener) => service ? service.subscribe(listener) : () => {}, [service]);
|
|
33
|
+
const snapshot = (0, react.useCallback)(() => service ? service.getSnapshot().active : "zh", [service]);
|
|
34
|
+
return zhihuLocale((0, react.useSyncExternalStore)(subscribe, snapshot, snapshot));
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
171
37
|
//#region src/contracts.ts
|
|
172
38
|
const ZHIHU_RPC_CHANNEL = "/zhihu";
|
|
173
39
|
const ZHIHU_CREDENTIAL_REF = "ZHIHU_ACCESS_TOKEN";
|
|
40
|
+
/** Official quota IDs, not local execution counters or predicted balances. */
|
|
41
|
+
const ZHIHU_QUOTA_IDS = [
|
|
42
|
+
"global_search",
|
|
43
|
+
"zhihu_search",
|
|
44
|
+
"hot_list",
|
|
45
|
+
"question_answers",
|
|
46
|
+
"zhida_openai",
|
|
47
|
+
"tools",
|
|
48
|
+
"knowledge",
|
|
49
|
+
"user_data",
|
|
50
|
+
"creator"
|
|
51
|
+
];
|
|
174
52
|
//#endregion
|
|
175
53
|
//#region src/client-state.ts
|
|
176
54
|
/**
|
|
@@ -212,212 +90,504 @@ function createZhihuClientState() {
|
|
|
212
90
|
}
|
|
213
91
|
};
|
|
214
92
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
93
|
+
Object.values({
|
|
94
|
+
/** Surfaces, from the page up to the topmost nested card. */
|
|
95
|
+
surface: [
|
|
96
|
+
"--dsw-alias-bg-base",
|
|
97
|
+
"--dsw-alias-bg-layer-1",
|
|
98
|
+
"--dsw-alias-bg-layer-2",
|
|
99
|
+
"--dsw-alias-bg-layer-3",
|
|
100
|
+
"--dsw-alias-bg-layer-4",
|
|
101
|
+
"--dsw-alias-bg-module-platform",
|
|
102
|
+
"--dsw-alias-bg-overlay"
|
|
103
|
+
],
|
|
104
|
+
/** Hairlines. l1 is near-invisible on a menu surface, so menus use l2+. */
|
|
105
|
+
border: [
|
|
106
|
+
"--dsw-alias-border-l1",
|
|
107
|
+
"--dsw-alias-border-l2",
|
|
108
|
+
"--dsw-alias-border-l3",
|
|
109
|
+
"--dsw-alias-border-l4"
|
|
110
|
+
],
|
|
111
|
+
/** Text. `caption` is menu shortcut text, `dimmed` is placeholder text. */
|
|
112
|
+
label: [
|
|
113
|
+
"--dsw-alias-label-primary",
|
|
114
|
+
"--dsw-alias-label-secondary",
|
|
115
|
+
"--dsw-alias-label-tertiary",
|
|
116
|
+
"--dsw-alias-label-caption",
|
|
117
|
+
"--dsw-alias-label-dimmed",
|
|
118
|
+
"--dsw-alias-label-error",
|
|
119
|
+
"--dsw-alias-label-primary-foreground"
|
|
120
|
+
],
|
|
121
|
+
/** Interactive fills. The `-danger` hover belongs to destructive rows. */
|
|
122
|
+
interactive: [
|
|
123
|
+
"--dsw-alias-interactive-bg-hover",
|
|
124
|
+
"--dsw-alias-interactive-bg-active",
|
|
125
|
+
"--dsw-alias-interactive-bg-hover-danger"
|
|
126
|
+
],
|
|
127
|
+
/** Accent and status. `business` is the focus ring's default colour. */
|
|
128
|
+
accent: [
|
|
129
|
+
"--dsw-alias-brand-primary",
|
|
130
|
+
"--dsw-alias-button-primary-fill",
|
|
131
|
+
"--dsw-alias-button-primary-hover",
|
|
132
|
+
"--dsw-alias-button-tool-bar-fill",
|
|
133
|
+
"--dsw-alias-button-tool-bar-hover",
|
|
134
|
+
"--dsw-alias-button-ghost-active-fill",
|
|
135
|
+
"--dsw-alias-button-ghost-active-border"
|
|
136
|
+
],
|
|
137
|
+
state: [
|
|
138
|
+
"--dsw-alias-state-business-primary",
|
|
139
|
+
"--dsw-alias-state-success-primary",
|
|
140
|
+
"--dsw-alias-state-warn-primary",
|
|
141
|
+
"--dsw-alias-state-error-primary",
|
|
142
|
+
"--dsw-alias-state-idle-primary"
|
|
143
|
+
],
|
|
144
|
+
/** Corner scale: sm 8, md 12, lg the large card, panel the modal. */
|
|
145
|
+
radius: [
|
|
146
|
+
"--dsw-radius-sm",
|
|
147
|
+
"--dsw-radius-md",
|
|
148
|
+
"--dsw-radius-lg",
|
|
149
|
+
"--dsw-radius-panel"
|
|
150
|
+
],
|
|
151
|
+
/** An elevated surface draws its edge in the shadow, so it keeps border: 0. */
|
|
152
|
+
elevation: [
|
|
153
|
+
"--dsw-elevation-soft",
|
|
154
|
+
"--dsw-elevation-stroke",
|
|
155
|
+
"--dsw-elevation-stroke-color",
|
|
156
|
+
"--dsw-elevation-panel",
|
|
157
|
+
"--dsw-elevation-prominent"
|
|
158
|
+
],
|
|
159
|
+
/** Floats, masks and scrollbars. */
|
|
160
|
+
overlay: [
|
|
161
|
+
"--dsw-menu-surface-fill",
|
|
162
|
+
"--dsw-menu-backdrop-filter",
|
|
163
|
+
"--dsw-mask-blur",
|
|
164
|
+
"--dsw-alias-bg-mask-1",
|
|
165
|
+
"--dsw-alias-menu-icon",
|
|
166
|
+
"--dsw-alias-scrollbar-bg-l2",
|
|
167
|
+
"--dsw-alias-scrollbar-hover-l2",
|
|
168
|
+
"--dsw-shadow-lv3"
|
|
169
|
+
],
|
|
170
|
+
/** Motion, focus and type axis, published by the host rather than by a component. */
|
|
171
|
+
system: [
|
|
172
|
+
"--dsw-focus-ring-width",
|
|
173
|
+
"--dsw-focus-ring-color",
|
|
174
|
+
"--dsw-font-markdown-code-font-family",
|
|
175
|
+
"--ds-transition-duration",
|
|
176
|
+
"--ds-ease-in-out",
|
|
177
|
+
"--ds-font-family-code",
|
|
178
|
+
"--dsh-scrollbar-thumb",
|
|
179
|
+
"--dsh-scrollbar-thumb-hover",
|
|
180
|
+
"--dsh-content-font-size-secondary",
|
|
181
|
+
"--dsh-content-font-delta",
|
|
182
|
+
"--dsh-frame-top-clearance"
|
|
183
|
+
]
|
|
184
|
+
}).flat();
|
|
185
|
+
/**
|
|
186
|
+
* The host's focus ring shorthand, verbatim. Writing this per control is how
|
|
187
|
+
* rings drift apart; a stylesheet that draws its own focus style should use
|
|
188
|
+
* {@link officialFocus} instead. The colour fallback is the host's own default.
|
|
189
|
+
*/
|
|
190
|
+
const OFFICIAL_FOCUS_RING = "var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary))";
|
|
191
|
+
/**
|
|
192
|
+
* A complete `outline` declaration at the official geometry. `offset` defaults
|
|
193
|
+
* to the 2px the native controls use; the host's inline field rules sit at 1px
|
|
194
|
+
* and a segmented tab pulls the ring inside itself at -2px.
|
|
195
|
+
*/
|
|
196
|
+
function officialFocus(offset = "2px") {
|
|
197
|
+
return `outline: ${OFFICIAL_FOCUS_RING}; outline-offset: ${offset}`;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* An elevated surface. The host draws a raised surface's edge inside its
|
|
201
|
+
* shadow rather than as a border, so a card sets `border: 0` and takes the edge
|
|
202
|
+
* from the elevation — which is also why its border stays off the alias-border
|
|
203
|
+
* set that a flat, non-elevated rule would use.
|
|
204
|
+
*/
|
|
205
|
+
function officialElevation(level) {
|
|
206
|
+
return `border: 0; box-shadow: var(--dsw-elevation-${level})`;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The shared stylesheet for a plugin UI, scoped under that plugin's root class.
|
|
210
|
+
*
|
|
211
|
+
* Scoping is what keeps this safe to always-bundle into every client: the rules
|
|
212
|
+
* can only match inside the plugin's own subtree, so two plugins can both mount
|
|
213
|
+
* `dsh-ui-card` without either styling the other's markup.
|
|
214
|
+
*
|
|
215
|
+
* Each recipe matches the root element *and* its descendants, because the
|
|
216
|
+
* commonest arrangement is one element that is both — a surface that carries
|
|
217
|
+
* the root class and `dsh-ui-card`, or a portaled dialog that is the root and
|
|
218
|
+
* the `dsh-ui-surface`. A descendant-only scope would silently leave those
|
|
219
|
+
* unstyled, with no error anywhere.
|
|
220
|
+
*
|
|
221
|
+
* A caveat worth knowing: `Modal` and `Menu` portal to `document.body`, so their
|
|
222
|
+
* content leaves the plugin's subtree entirely. A portaled surface therefore
|
|
223
|
+
* needs its own root class on an element inside the portal.
|
|
224
|
+
*
|
|
225
|
+
* Only patterns that primitives do not provide live here. If a control appears
|
|
226
|
+
* below that `Button`/`Input`/`Switch`/`Tag`/`Menu` already covers, that is a
|
|
227
|
+
* bug in this file: use the primitive and delete the recipe.
|
|
228
|
+
*/
|
|
229
|
+
function officialUiCss(roots) {
|
|
230
|
+
const scope = (typeof roots === "string" ? [roots] : roots).map((root) => `.${root}`).join(", ");
|
|
231
|
+
return `
|
|
232
|
+
/* Official DSH Web UI contract. Generated by @klarkxy/dsh-plugin-kit/official-ui.
|
|
233
|
+
* Do not hand-edit the copy inside a plugin: change this module so every plugin
|
|
234
|
+
* moves together. Scope: ${scope} */
|
|
235
|
+
/* The scope rule below stays open for the whole sheet: every rule after it
|
|
236
|
+
* nests with the parent selector, so one stylesheet can cover several unrelated
|
|
237
|
+
* roots without leaking into a neighbouring plugin. Its closing brace is last. */
|
|
238
|
+
${scope} {
|
|
239
|
+
color: var(--dsw-alias-label-primary);
|
|
240
|
+
font-family: inherit;
|
|
241
|
+
font-size: 14px;
|
|
242
|
+
line-height: 22px;
|
|
243
|
+
min-width: 0;
|
|
244
|
+
|
|
245
|
+
& *, & *::before, & *::after { box-sizing: border-box; }
|
|
246
|
+
& h1, & h2, & h3, & h4, & p, & figure { margin: 0; }
|
|
247
|
+
/* Keyboard focus only: pointer input keeps the native resting appearance. The
|
|
248
|
+
* ring needs no radius of its own — the outline already follows the control's
|
|
249
|
+
* own border-radius, and overriding it here would flatten every control. */
|
|
250
|
+
& :focus-visible { ${officialFocus()}; }
|
|
251
|
+
& [hidden] { display: none !important; }
|
|
252
|
+
|
|
253
|
+
/* --- Type tiers. The same 14/22 and 13/20 steps the primitives use. --- */
|
|
254
|
+
& .dsh-ui-title { font-size: 16px; line-height: 24px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
255
|
+
& .dsh-ui-heading { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
256
|
+
& .dsh-ui-compact { font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-primary); }
|
|
257
|
+
& .dsh-ui-meta { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
258
|
+
& .dsh-ui-hint { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
259
|
+
& .dsh-ui-error { font-size: 12px; line-height: 18px; color: var(--dsw-alias-state-error-primary); }
|
|
260
|
+
& .dsh-ui-warn { color: var(--dsw-alias-state-warn-primary); }
|
|
261
|
+
& .dsh-ui-success { color: var(--dsw-alias-state-success-primary); }
|
|
262
|
+
& .dsh-ui-muted { color: var(--dsw-alias-label-tertiary); }
|
|
263
|
+
& .dsh-ui-truncate { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
264
|
+
& .dsh-ui-wrap { min-width: 0; overflow-wrap: anywhere; }
|
|
265
|
+
|
|
266
|
+
/* --- Layout scaffolding. --- */
|
|
267
|
+
& .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
|
|
268
|
+
& .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
|
|
269
|
+
& .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
270
|
+
& .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
271
|
+
& .dsh-ui-spacer { flex: 1; min-width: 0; }
|
|
272
|
+
/* A 0.5px l2 rule is the host's section divider; reserve the same rhythm. */
|
|
273
|
+
& .dsh-ui-divider { height: 0.5px; background: var(--dsw-alias-border-l2); }
|
|
274
|
+
& .dsh-ui-section { display: flex; flex-direction: column; gap: 12px; padding-top: 16px; border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
219
275
|
|
|
220
|
-
.
|
|
221
|
-
|
|
222
|
-
|
|
276
|
+
/* --- Card: a raised surface whose edge lives in the shadow. --- */
|
|
277
|
+
& .dsh-ui-card {
|
|
278
|
+
${officialElevation("stroke")}
|
|
223
279
|
display: flex;
|
|
224
280
|
flex-direction: column;
|
|
281
|
+
gap: 12px;
|
|
282
|
+
padding: 16px;
|
|
283
|
+
border-radius: var(--dsw-radius-lg);
|
|
284
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
285
|
+
color: var(--dsw-alias-label-primary);
|
|
225
286
|
min-width: 0;
|
|
226
|
-
width: 100%;
|
|
227
|
-
max-width: 760px;
|
|
228
|
-
min-height: 0;
|
|
229
|
-
gap: 0;
|
|
230
|
-
}
|
|
231
|
-
.zhihu-settings-embed .zhihu-tabs {
|
|
232
|
-
margin: 0 0 var(--space-2);
|
|
233
|
-
}
|
|
234
|
-
.zhihu-settings-embed .zhihu-panel-body {
|
|
235
|
-
padding: var(--space-4) 0 0;
|
|
236
|
-
overflow: visible;
|
|
237
287
|
}
|
|
238
|
-
.
|
|
239
|
-
.
|
|
240
|
-
.
|
|
241
|
-
|
|
242
|
-
|
|
288
|
+
& .dsh-ui-card--flat { border: 0.5px solid var(--dsw-alias-border-l3); box-shadow: none; }
|
|
289
|
+
& .dsh-ui-card--nested { background: var(--dsw-alias-bg-layer-2); }
|
|
290
|
+
& .dsh-ui-card--plain { background: none; }
|
|
291
|
+
|
|
292
|
+
/* --- Field: label, control, help, error. The control itself is an
|
|
293
|
+
* official primitive, so this only owns the vertical rhythm. --- */
|
|
294
|
+
& .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
295
|
+
& .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
|
|
296
|
+
& .dsh-ui-label { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 20px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
297
|
+
& .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
298
|
+
& .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
299
|
+
& .dsh-ui-help p { margin: 0; }
|
|
300
|
+
& .dsh-ui-help p + p { margin-top: 8px; }
|
|
301
|
+
& .dsh-ui-required { color: var(--dsw-alias-state-error-primary); }
|
|
302
|
+
& .dsh-ui-control { width: 100%; min-width: 0; }
|
|
303
|
+
/* An invalid control is marked on the control, not only in the message. */
|
|
304
|
+
& .dsh-ui-control[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
305
|
+
& .dsh-ui-readonly { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
306
|
+
& .dsh-ui-readonly > dt { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
307
|
+
& .dsh-ui-readonly > dd { margin: 0; font-size: 13px; line-height: 20px; overflow-wrap: anywhere; }
|
|
308
|
+
|
|
309
|
+
/* --- Buttons row. A no-op container: it exists so actions line up on the
|
|
310
|
+
* trailing edge the way every native panel does. --- */
|
|
311
|
+
& .dsh-ui-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 4px; }
|
|
312
|
+
& .dsh-ui-actions-end { justify-content: flex-end; }
|
|
313
|
+
|
|
314
|
+
/* --- Banners. A banner is a left-rail notice, the shape used for warnings,
|
|
315
|
+
* deprecations and blocking instructions. --- */
|
|
316
|
+
& .dsh-ui-banner {
|
|
317
|
+
display: flex;
|
|
318
|
+
flex-direction: column;
|
|
319
|
+
gap: 8px;
|
|
320
|
+
padding: 10px 14px;
|
|
321
|
+
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
322
|
+
border-left: 3px solid var(--dsw-alias-state-warn-primary);
|
|
323
|
+
border-radius: var(--dsw-radius-md);
|
|
324
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
325
|
+
font-size: 13px;
|
|
326
|
+
line-height: 20px;
|
|
327
|
+
color: var(--dsw-alias-label-secondary);
|
|
243
328
|
}
|
|
329
|
+
& .dsh-ui-banner-title { font-weight: 500; color: var(--dsw-alias-state-warn-primary); }
|
|
330
|
+
& .dsh-ui-banner--info { border-left-color: var(--dsw-alias-state-business-primary); }
|
|
331
|
+
& .dsh-ui-banner--danger { border-left-color: var(--dsw-alias-state-error-primary); }
|
|
332
|
+
& .dsh-ui-banner--success { border-left-color: var(--dsw-alias-state-success-primary); }
|
|
333
|
+
& .dsh-ui-banner p { margin: 0; }
|
|
244
334
|
|
|
245
|
-
.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
335
|
+
/* --- Inline notice: a transient confirmation with no chrome of its own. --- */
|
|
336
|
+
& .dsh-ui-notice { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 18px; color: var(--dsw-alias-state-success-primary); }
|
|
337
|
+
& .dsh-ui-notice--error { color: var(--dsw-alias-state-error-primary); }
|
|
338
|
+
|
|
339
|
+
/* --- Empty and loading states. Both sit in a dashed well so an empty panel
|
|
340
|
+
* reads as "nothing here yet" rather than as a broken layout. --- */
|
|
341
|
+
& .dsh-ui-empty {
|
|
342
|
+
display: flex;
|
|
343
|
+
flex-direction: column;
|
|
344
|
+
align-items: flex-start;
|
|
345
|
+
gap: 6px;
|
|
346
|
+
padding: 20px 16px;
|
|
347
|
+
border: 0.5px dashed var(--dsw-alias-border-l3);
|
|
348
|
+
border-radius: var(--dsw-radius-md);
|
|
349
|
+
font-size: 13px;
|
|
350
|
+
line-height: 20px;
|
|
351
|
+
color: var(--dsw-alias-label-secondary);
|
|
250
352
|
}
|
|
251
|
-
.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
353
|
+
& .dsh-ui-loading { display: flex; align-items: center; gap: 8px; padding: 20px 0; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-secondary); }
|
|
354
|
+
|
|
355
|
+
/* --- Selectable list. A row is a button-sized target with a hover fill and
|
|
356
|
+
* a selected fill; the name carries the weight, the description does not. --- */
|
|
357
|
+
& .dsh-ui-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
358
|
+
& .dsh-ui-list-scroll { max-height: min(66vh, 760px); overflow-y: auto; overflow-x: hidden; }
|
|
359
|
+
& .dsh-ui-list-row { border-radius: var(--dsw-radius-md); }
|
|
360
|
+
& .dsh-ui-list-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
361
|
+
& .dsh-ui-list-row[aria-selected='true'], & .dsh-ui-list-row[data-selected] { background: var(--dsw-alias-interactive-bg-active); }
|
|
362
|
+
& .dsh-ui-list-item {
|
|
363
|
+
display: flex;
|
|
364
|
+
flex-direction: column;
|
|
365
|
+
align-items: flex-start;
|
|
366
|
+
gap: 4px;
|
|
367
|
+
width: 100%;
|
|
368
|
+
min-width: 0;
|
|
369
|
+
padding: 10px 12px;
|
|
266
370
|
border: 0;
|
|
267
|
-
border-radius: var(--radius-
|
|
268
|
-
background:
|
|
269
|
-
|
|
371
|
+
border-radius: var(--dsw-radius-md);
|
|
372
|
+
background: none;
|
|
373
|
+
font: inherit;
|
|
374
|
+
color: inherit;
|
|
375
|
+
text-align: start;
|
|
270
376
|
cursor: pointer;
|
|
271
377
|
}
|
|
272
|
-
.
|
|
273
|
-
.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
378
|
+
& .dsh-ui-list-item:disabled { cursor: default; }
|
|
379
|
+
& .dsh-ui-list-item:disabled:hover { background: none; }
|
|
380
|
+
& .dsh-ui-list-name { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
|
|
381
|
+
& .dsh-ui-list-desc { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
382
|
+
|
|
383
|
+
/* --- Code and structured output. Wraps rather than scrolls, because a
|
|
384
|
+
* feature panel is read rather than scanned. --- */
|
|
385
|
+
& .dsh-ui-code {
|
|
386
|
+
padding: 10px 12px;
|
|
387
|
+
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
388
|
+
border-radius: var(--dsw-radius-md);
|
|
389
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
390
|
+
font-family: var(--ds-font-family-code, ui-monospace, 'Cascadia Mono', Consolas, monospace);
|
|
391
|
+
font-size: 12px;
|
|
392
|
+
line-height: 20px;
|
|
393
|
+
color: var(--dsw-alias-label-secondary);
|
|
394
|
+
overflow-wrap: anywhere;
|
|
395
|
+
white-space: pre-wrap;
|
|
279
396
|
}
|
|
280
|
-
.
|
|
281
|
-
|
|
397
|
+
& .dsh-ui-scroll { max-height: 60vh; overflow: auto; overscroll-behavior: contain; }
|
|
398
|
+
|
|
399
|
+
/* --- A toggle row: label on the left, switch on the trailing edge. This is
|
|
400
|
+
* layout only; the switch itself is the official primitive. --- */
|
|
401
|
+
& .dsh-ui-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-width: 0; padding: 12px 0; }
|
|
402
|
+
& .dsh-ui-toggle-row + .dsh-ui-toggle-row { border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
403
|
+
& .dsh-ui-toggle-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
404
|
+
& .dsh-ui-toggle-label { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
405
|
+
|
|
406
|
+
/* --- Floating surface: a panel anchored to a trigger. This is the material
|
|
407
|
+
* a menu card is made of — the translucent fill plus its backdrop blur, with
|
|
408
|
+
* the edge riding inside the prominent elevation. A plugin that hand-rolls this
|
|
409
|
+
* is how tokens like --dsw-specific-menu get invented; use this instead. --- */
|
|
410
|
+
& .dsh-ui-surface {
|
|
411
|
+
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
412
|
+
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
|
413
|
+
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
|
414
|
+
position: relative;
|
|
282
415
|
display: flex;
|
|
283
416
|
flex-direction: column;
|
|
284
|
-
|
|
285
|
-
|
|
417
|
+
border: 0;
|
|
418
|
+
border-radius: var(--dsw-radius-lg);
|
|
419
|
+
background: var(--dsw-menu-surface-fill);
|
|
420
|
+
backdrop-filter: var(--dsw-menu-backdrop-filter);
|
|
421
|
+
box-shadow: var(--dsw-elevation-prominent);
|
|
422
|
+
color: var(--dsw-alias-label-primary);
|
|
423
|
+
font-size: 13px;
|
|
424
|
+
line-height: 20px;
|
|
425
|
+
min-width: 0;
|
|
286
426
|
}
|
|
287
|
-
.
|
|
288
|
-
.
|
|
289
|
-
.
|
|
290
|
-
|
|
291
|
-
|
|
427
|
+
& .dsh-ui-surface-body { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px 14px; overflow-y: auto; }
|
|
428
|
+
& .dsh-ui-surface-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
429
|
+
& .dsh-ui-surface-title { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
430
|
+
|
|
431
|
+
/* --- Native select. The primitives ship no select, so a plugin that offers a
|
|
432
|
+
* closed list of choices keeps the platform control and matches the official
|
|
433
|
+
* settings field geometry exactly (34px, 0.5px l4, radius-md, layer-3). A
|
|
434
|
+
* SegmentedControl is the primitive to reach for when the choices are few
|
|
435
|
+
* enough to show at once. --- */
|
|
436
|
+
& .dsh-ui-select {
|
|
292
437
|
box-sizing: border-box;
|
|
438
|
+
width: 100%;
|
|
439
|
+
min-width: 0;
|
|
440
|
+
height: 34px;
|
|
441
|
+
padding: 0 12px;
|
|
442
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
443
|
+
border-radius: var(--dsw-radius-md);
|
|
444
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
293
445
|
font: inherit;
|
|
294
|
-
font-
|
|
295
|
-
|
|
296
|
-
color: var(--
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
.
|
|
335
|
-
|
|
336
|
-
background: var(--gray-a3);
|
|
337
|
-
border-radius: var(--radius-2);
|
|
338
|
-
padding: var(--space-2);
|
|
339
|
-
}
|
|
340
|
-
.zhihu-results { display: flex; flex-direction: column; gap: var(--space-2); }
|
|
341
|
-
.zhihu-results-summary { color: var(--gray-11); font-weight: var(--font-weight-medium); margin: 0; }
|
|
342
|
-
.zhihu-result-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
|
|
343
|
-
.zhihu-result-item {
|
|
344
|
-
border: 1px solid var(--gray-6);
|
|
345
|
-
border-radius: var(--radius-2);
|
|
346
|
-
padding: var(--space-2);
|
|
446
|
+
font-size: 13px;
|
|
447
|
+
line-height: 1.5;
|
|
448
|
+
color: var(--dsw-alias-label-primary);
|
|
449
|
+
cursor: pointer;
|
|
450
|
+
}
|
|
451
|
+
& .dsh-ui-select:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
|
|
452
|
+
& .dsh-ui-select:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
453
|
+
& .dsh-ui-select[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
454
|
+
|
|
455
|
+
/* Long panels scroll inside themselves instead of growing the host page. */
|
|
456
|
+
& .dsh-ui-panel { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
457
|
+
& .dsh-ui-panel-wide { max-width: 100%; }
|
|
458
|
+
|
|
459
|
+
@media (prefers-reduced-motion: reduce) {
|
|
460
|
+
& *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/client-styles.ts
|
|
467
|
+
/**
|
|
468
|
+
* The shared contract paints this page: the type tiers, the card, the field,
|
|
469
|
+
* the banners, the empty state, the focus ring and every control's own recipe.
|
|
470
|
+
* What is left here is only the geometry the contract does not cover — the
|
|
471
|
+
* reading width of the plugin page, two inline SVG charts, the quota matrix and
|
|
472
|
+
* the day table.
|
|
473
|
+
*
|
|
474
|
+
* Nothing below declares a colour of its own beyond naming a host token for a
|
|
475
|
+
* feature the primitives have no equivalent for (a destructive hover, an inset
|
|
476
|
+
* snippet well, a legend chip), and nothing carries a literal fallback: the
|
|
477
|
+
* host publishes both themes and a literal would pin one of them.
|
|
478
|
+
*
|
|
479
|
+
* Every plugin rule nests under the plugin root, as the contract does, so a
|
|
480
|
+
* .zhihu-* class never paints a neighbouring plugin. Content portaled into a
|
|
481
|
+
* Modal carries the root class on its own element for the same reason.
|
|
482
|
+
*/
|
|
483
|
+
const zhihuClientStyles = `${officialUiCss("zhihu-panel")}
|
|
484
|
+
.zhihu-panel {
|
|
485
|
+
/* --- Plugin page. The contract's rules nest under the scope selector, so the
|
|
486
|
+
* root's own column and reading width are the one part it cannot reach. --- */
|
|
487
|
+
&.zhihu-settings-embed {
|
|
347
488
|
display: flex;
|
|
348
489
|
flex-direction: column;
|
|
349
|
-
gap:
|
|
350
|
-
|
|
490
|
+
gap: 12px;
|
|
491
|
+
width: 100%;
|
|
492
|
+
max-width: 760px;
|
|
493
|
+
min-width: 0;
|
|
494
|
+
}
|
|
495
|
+
/* The tab strip is a primitive; this only places it in the page column. */
|
|
496
|
+
.zhihu-tabs { margin: 0; }
|
|
497
|
+
|
|
498
|
+
/* --- Fields. The official Input is an inline-flex wrapper, so a form-width
|
|
499
|
+
* field states that width once instead of per call site. --- */
|
|
500
|
+
.zhihu-input { width: 100%; }
|
|
501
|
+
|
|
502
|
+
/* --- Actions. A destructive button keeps the outlined resting appearance every
|
|
503
|
+
* other action uses and only shows its tone under the pointer or the keyboard. */
|
|
504
|
+
.zhihu-button-danger:hover:not(:disabled),
|
|
505
|
+
.zhihu-button-danger:focus-visible {
|
|
506
|
+
color: var(--dsw-alias-state-error-primary);
|
|
507
|
+
border-color: var(--dsw-alias-state-error-primary);
|
|
508
|
+
background: var(--dsw-alias-interactive-bg-hover-danger);
|
|
351
509
|
}
|
|
352
|
-
|
|
353
|
-
.
|
|
354
|
-
|
|
355
|
-
.zhihu-
|
|
510
|
+
|
|
511
|
+
/* --- Disclosures. The capability and fine-print blocks use the host
|
|
512
|
+
* DisclosureRow; only the reasoning block is still a native <summary>. --- */
|
|
513
|
+
.zhihu-ask-reasoning > summary { cursor: pointer; width: fit-content; }
|
|
514
|
+
.zhihu-guide-list { margin: 4px 0 0; padding-inline-start: 20px; display: grid; gap: 6px; }
|
|
515
|
+
|
|
516
|
+
/* --- Result rows. The card, the row and the name all come from the contract; a
|
|
517
|
+
* matched snippet is the one inset well the panel needs of its own. --- */
|
|
356
518
|
.zhihu-result-snippet {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
border-radius: var(--radius-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
.zhihu-ask-content { white-space: pre-wrap;
|
|
363
|
-
|
|
364
|
-
.
|
|
365
|
-
|
|
366
|
-
.zhihu-
|
|
367
|
-
.zhihu-
|
|
368
|
-
.zhihu-
|
|
369
|
-
|
|
370
|
-
.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
.zhihu-
|
|
374
|
-
.zhihu-
|
|
375
|
-
.zhihu-settings :is(.zhihu-field-heading a,.zhihu-status-grid) { font-size: var(--font-size-1); }
|
|
376
|
-
.zhihu-settings :is(a,summary):focus-visible { outline: 2px solid var(--accent-8); outline-offset: 2px; }
|
|
377
|
-
.zhihu-guide {
|
|
378
|
-
border: 1px solid var(--gray-6);
|
|
379
|
-
border-radius: var(--radius-2);
|
|
380
|
-
padding: var(--space-2);
|
|
381
|
-
}
|
|
382
|
-
.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); }
|
|
383
|
-
.zhihu-guide-steps { margin: 0; padding-left: 18px; color: var(--gray-11); line-height: 1.7; }
|
|
384
|
-
.zhihu-scopes { display: flex; gap: var(--space-3); flex-wrap: wrap; }
|
|
385
|
-
.zhihu-scope {
|
|
386
|
-
display: inline-flex; align-items: center; gap: 4px; color: var(--gray-11);
|
|
387
|
-
font: inherit; background: none; border: 1px solid var(--gray-6); border-radius: var(--radius-2);
|
|
388
|
-
padding: 0 10px; min-height: var(--control-h, 34px); cursor: pointer;
|
|
389
|
-
}
|
|
390
|
-
.zhihu-scope.is-on, .zhihu-scope[aria-pressed="true"] {
|
|
391
|
-
color: var(--accent-11); background: var(--accent-a3); border-color: var(--accent-a6);
|
|
392
|
-
}
|
|
393
|
-
.zhihu-input.ui-input { width: 100%; }
|
|
394
|
-
.zhihu-usage { display: flex; flex-direction: column; gap: var(--space-3); }
|
|
395
|
-
.zhihu-usage-intro { margin: 0; color: var(--gray-11); line-height: 1.6; }
|
|
396
|
-
.zhihu-usage-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: var(--space-2); }
|
|
397
|
-
.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); }
|
|
398
|
-
.zhihu-usage-card:hover { background: var(--gray-a3); }
|
|
399
|
-
.zhihu-usage-card-label { color: var(--gray-11); font-size: var(--font-size-2); }
|
|
400
|
-
.zhihu-usage-card-value { font-weight: var(--font-weight-medium); font-variant-numeric: tabular-nums; color: var(--gray-12); }
|
|
401
|
-
.zhihu-usage-card-unit { margin-left: 4px; color: var(--gray-11); font-size: var(--font-size-2); font-weight: 400; }
|
|
402
|
-
.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); }
|
|
403
|
-
.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); }
|
|
404
|
-
.zhihu-usage-legend li { display: inline-flex; align-items: center; gap: 6px; }
|
|
405
|
-
.zhihu-chart-chip { width: 8px; height: 8px; border-radius: 2px; flex: none; }
|
|
406
|
-
.zhihu-chart-chip-ok { background: var(--accent-9); }
|
|
407
|
-
.zhihu-chart-chip-fail { background: var(--red-9); }
|
|
408
|
-
.zhihu-chart { width: 100%; height: auto; aspect-ratio: 600 / 176; display: block; }
|
|
409
|
-
.zhihu-chart-bar-ok { fill: var(--accent-9); }
|
|
410
|
-
.zhihu-chart-bar-fail { fill: var(--red-9); }
|
|
411
|
-
.zhihu-chart-hit { fill: transparent; }
|
|
412
|
-
.zhihu-chart-grid { stroke: var(--gray-6); stroke-width: 1; }
|
|
413
|
-
.zhihu-chart-axis { fill: var(--gray-11); font-size: var(--font-size-1); font-family: var(--code-font-family); }
|
|
414
|
-
.zhihu-chart-tick { fill: var(--gray-11); font-size: var(--font-size-1); font-family: var(--code-font-family); }
|
|
415
|
-
.zhihu-chart-value { fill: var(--gray-11); font-size: var(--font-size-1); font-family: var(--code-font-family); }
|
|
519
|
+
display: block;
|
|
520
|
+
padding: 6px 10px;
|
|
521
|
+
border-radius: var(--dsw-radius-sm);
|
|
522
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
523
|
+
}
|
|
524
|
+
.zhihu-ask-content { white-space: pre-wrap; }
|
|
525
|
+
|
|
526
|
+
/* --- Open platform. The section is the last block of its tab, so it keeps a
|
|
527
|
+
* trailing hairline; the raw response scrolls instead of growing the page. --- */
|
|
528
|
+
.zhihu-open-platform { padding-bottom: 16px; border-bottom: 0.5px solid var(--dsw-alias-border-l2); }
|
|
529
|
+
.zhihu-open-platform-quota > legend { padding: 0 8px 0 0; }
|
|
530
|
+
.zhihu-open-platform-data { max-height: 480px; overflow: auto; margin: 8px 0 0; }
|
|
531
|
+
|
|
532
|
+
/* --- Usage. The stat tiles are a grid and the day list is a table, neither of
|
|
533
|
+
* which the contract has a recipe for; figures align in the columns they sit
|
|
534
|
+
* in, so a column of counts scans as a column. --- */
|
|
535
|
+
.zhihu-usage-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: 8px; }
|
|
536
|
+
.zhihu-usage-value { font-variant-numeric: tabular-nums; }
|
|
416
537
|
.zhihu-usage-table-wrap { overflow-x: auto; }
|
|
417
538
|
.zhihu-usage-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
|
|
418
|
-
.zhihu-usage-table th,
|
|
419
|
-
.zhihu-usage-table
|
|
420
|
-
.zhihu-usage-table th
|
|
539
|
+
.zhihu-usage-table th,
|
|
540
|
+
.zhihu-usage-table td { padding: 4px 6px; text-align: right; border-bottom: 0.5px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); }
|
|
541
|
+
.zhihu-usage-table th:first-child,
|
|
542
|
+
.zhihu-usage-table td:first-child { text-align: start; }
|
|
543
|
+
.zhihu-usage-table th { color: var(--dsw-alias-label-tertiary); font-weight: 500; }
|
|
544
|
+
|
|
545
|
+
/* --- Usage chart. Inline SVG cannot read a CSS module, so the bars and their
|
|
546
|
+
* axis carry their own geometry and take every colour from a host token. --- */
|
|
547
|
+
.zhihu-chart-chip { width: 8px; height: 8px; border-radius: var(--dsw-radius-sm); flex: none; }
|
|
548
|
+
.zhihu-chart-chip-ok { background: var(--dsw-alias-state-business-primary); }
|
|
549
|
+
.zhihu-chart-chip-fail { background: var(--dsw-alias-state-error-primary); }
|
|
550
|
+
.zhihu-chart { width: 100%; height: auto; aspect-ratio: 600 / 176; display: block; }
|
|
551
|
+
.zhihu-chart-bar-ok { fill: var(--dsw-alias-state-business-primary); }
|
|
552
|
+
.zhihu-chart-bar-fail { fill: var(--dsw-alias-state-error-primary); }
|
|
553
|
+
.zhihu-chart-hit { fill: transparent; }
|
|
554
|
+
.zhihu-chart-grid { stroke: var(--dsw-alias-border-l2); stroke-width: 1; }
|
|
555
|
+
.zhihu-chart-axis,
|
|
556
|
+
.zhihu-chart-tick,
|
|
557
|
+
.zhihu-chart-value { fill: var(--dsw-alias-label-secondary); font-size: var(--dsh-content-font-size-secondary); font-family: var(--ds-font-family-code); }
|
|
558
|
+
|
|
559
|
+
/* --- Quota matrix. One row per API, one column per field: the header carries
|
|
560
|
+
* that field's own maximum, so a bar only ever states "this share of this
|
|
561
|
+
* column's maximum". The rule is a 3px underline under the figure, right-anchored
|
|
562
|
+
* to the digits it belongs to, so scanning one column is easy and the number
|
|
563
|
+
* stays the primary read. --- */
|
|
564
|
+
.zhihu-quota-scroll { overflow-x: auto; }
|
|
565
|
+
.zhihu-quota-table { width: 100%; border-collapse: collapse; }
|
|
566
|
+
.zhihu-quota-table th,
|
|
567
|
+
.zhihu-quota-table td { padding: 5px 8px; vertical-align: middle; }
|
|
568
|
+
.zhihu-quota-name { text-align: start; font-weight: 400; color: var(--dsw-alias-label-primary); }
|
|
569
|
+
.zhihu-quota-corner { text-align: start; font-weight: 500; color: var(--dsw-alias-label-secondary); }
|
|
570
|
+
.zhihu-quota-head { text-align: end; }
|
|
571
|
+
.zhihu-quota-head-label { display: block; font-weight: 500; color: var(--dsw-alias-label-secondary); }
|
|
572
|
+
.zhihu-quota-head-scale { display: block; font-weight: 400; color: var(--dsw-alias-label-tertiary); }
|
|
573
|
+
.zhihu-quota-cell { text-align: end; }
|
|
574
|
+
.zhihu-quota-value { display: block; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-primary); }
|
|
575
|
+
/* The ceiling is already in the fraction, so it steps back: same figures, less
|
|
576
|
+
* weight on the part that only supplies the denominator. */
|
|
577
|
+
.zhihu-quota-of { color: var(--dsw-alias-label-tertiary); }
|
|
578
|
+
.zhihu-quota-track { display: block; height: 3px; margin-top: 3px; border-radius: var(--dsw-radius-sm); background: var(--dsw-alias-bg-layer-3); overflow: hidden; }
|
|
579
|
+
.zhihu-quota-fill { display: block; height: 100%; margin-inline-start: auto; border-radius: inherit; background: var(--dsw-alias-state-business-primary); }
|
|
580
|
+
@media (max-width: 480px) {
|
|
581
|
+
/* The columns keep their room and the wrapper scrolls; crushing three scales
|
|
582
|
+
* into a phone width is what made the bars meaningless in the first place. */
|
|
583
|
+
.zhihu-quota-table { min-width: 24rem; }
|
|
584
|
+
.zhihu-quota-table th,
|
|
585
|
+
.zhihu-quota-table td { padding: 5px 6px; }
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/* --- File field. The input is visually hidden but stays focusable and in the
|
|
589
|
+
* accessibility tree; its <label> is the visible trigger, so a click on the
|
|
590
|
+
* label opens the file dialog natively and keyboard focus rings the label. --- */
|
|
421
591
|
.zhihu-file {
|
|
422
592
|
position: absolute;
|
|
423
593
|
width: 1px;
|
|
@@ -429,151 +599,71 @@ const zhihuClientStyles = `
|
|
|
429
599
|
white-space: nowrap;
|
|
430
600
|
border: 0;
|
|
431
601
|
}
|
|
432
|
-
.zhihu-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
padding:
|
|
436
|
-
|
|
602
|
+
.zhihu-file-trigger {
|
|
603
|
+
display: inline-flex;
|
|
604
|
+
align-items: center;
|
|
605
|
+
padding: 4px 12px;
|
|
606
|
+
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
607
|
+
border-radius: var(--dsw-radius-md);
|
|
608
|
+
color: var(--dsw-alias-label-primary);
|
|
609
|
+
cursor: pointer;
|
|
610
|
+
flex: none;
|
|
437
611
|
}
|
|
612
|
+
.zhihu-file-trigger:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
613
|
+
.zhihu-file-trigger[aria-disabled="true"] { opacity: .5; pointer-events: none; }
|
|
614
|
+
.zhihu-file:focus-visible + .dsh-ui-row .zhihu-file-trigger { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
615
|
+
|
|
438
616
|
/* 活动反馈:三点呼吸(pulse-dots),参数改写自 Amicro(MIT License,
|
|
439
617
|
Copyright (c) 2026 Syed Subhan Uddin);装饰元素 aria-hidden。 */
|
|
440
|
-
|
|
441
|
-
.zhihu-dots { display: inline-flex; align-items: center; gap: 3px; margin-inline-end: .4em; vertical-align: middle; color: var(--accent-9); }
|
|
618
|
+
.zhihu-dots { display: inline-flex; align-items: center; gap: 3px; margin-inline-end: .4em; vertical-align: middle; color: var(--dsw-alias-state-business-primary); }
|
|
442
619
|
.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; }
|
|
443
620
|
.zhihu-dots i:nth-child(2) { animation-delay: .2s; }
|
|
444
621
|
.zhihu-dots i:nth-child(3) { animation-delay: .4s; }
|
|
445
622
|
@media (prefers-reduced-motion: reduce) {
|
|
446
|
-
|
|
447
|
-
animation: none !important; transition: none !important;
|
|
448
|
-
transform: none !important; filter: none !important;
|
|
449
|
-
}
|
|
623
|
+
/* Stop the loop outright so the three dots stay lit instead of flickering. */
|
|
450
624
|
.zhihu-dots i { animation: none; }
|
|
451
625
|
}
|
|
452
|
-
|
|
453
|
-
.zhihu-panel${standaloneOutsideTheme} {
|
|
454
|
-
|
|
455
|
-
--default-font-family: "Noto Sans SC", "PingFang SC", "Microsoft YaHei UI", system-ui, sans-serif;
|
|
456
|
-
--code-font-family: ui-monospace, "SF Mono", "JetBrains Mono", Consolas, monospace;
|
|
457
|
-
--font-weight-medium: 500;
|
|
458
|
-
--font-size-1: 13px;
|
|
459
|
-
--font-size-2: 14px;
|
|
460
|
-
--font-size-3: 16px;
|
|
461
|
-
--font-size-4: 18px;
|
|
462
|
-
--font-size-5: 20px;
|
|
463
|
-
--font-size-6: 24px;
|
|
464
|
-
--font-size-7: 28px;
|
|
465
|
-
--font-size-8: 35px;
|
|
466
|
-
--font-size-9: 60px;
|
|
467
|
-
--space-1: 4px;
|
|
468
|
-
--space-2: 8px;
|
|
469
|
-
--space-3: 12px;
|
|
470
|
-
--space-4: 16px;
|
|
471
|
-
--space-5: 24px;
|
|
472
|
-
--radius-2: 4px;
|
|
473
|
-
--radius-3: 6px;
|
|
474
|
-
--control-h: 34px;
|
|
475
|
-
--gray-1: #fcfcfc;
|
|
476
|
-
--gray-2: #f9f9f9;
|
|
477
|
-
--gray-3: #f0f0f0;
|
|
478
|
-
--gray-4: #e8e8e8;
|
|
479
|
-
--gray-5: #e0e0e0;
|
|
480
|
-
--gray-6: #d9d9d9;
|
|
481
|
-
--gray-7: #cecece;
|
|
482
|
-
--gray-8: #bbbbbb;
|
|
483
|
-
--gray-9: #8d8d8d;
|
|
484
|
-
--gray-10: #838383;
|
|
485
|
-
--gray-11: #646464;
|
|
486
|
-
--gray-12: #202020;
|
|
487
|
-
--gray-a2: #00000006;
|
|
488
|
-
--gray-a3: #0000000f;
|
|
489
|
-
--gray-a4: #00000017;
|
|
490
|
-
--gray-a5: #0000001f;
|
|
491
|
-
--gray-a6: #00000026;
|
|
492
|
-
--accent-8: #8da4ef;
|
|
493
|
-
--accent-9: #3e63dd;
|
|
494
|
-
--accent-10: #3358d4;
|
|
495
|
-
--accent-11: #3a5bc7;
|
|
496
|
-
--accent-a3: #0047f112;
|
|
497
|
-
--accent-a4: #0044ff1e;
|
|
498
|
-
--accent-a8: #0034dc72;
|
|
499
|
-
--accent-contrast: #ffffff;
|
|
500
|
-
--red-9: #e5484d;
|
|
501
|
-
--red-11: #ce2c31;
|
|
502
|
-
--green-9: #30a46c;
|
|
503
|
-
--green-11: #218358;
|
|
504
|
-
--amber-a4: #ffd40063;
|
|
505
|
-
--amber-a6: #eab5008c;
|
|
506
|
-
--color-background: #ffffff;
|
|
507
|
-
--color-panel-solid: #ffffff;
|
|
508
|
-
--color-surface: rgba(255, 255, 255, 0.85);
|
|
509
|
-
--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);
|
|
510
|
-
--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);
|
|
511
|
-
--shadow-4: 0 0 0 1px #0000000f, 0 8px 40px rgba(0, 0, 0, 0.05), 0 12px 32px -16px #0000000f;
|
|
512
|
-
color-scheme: light;
|
|
513
|
-
}
|
|
514
|
-
html:not([data-theme]) body[data-ds-dark-theme] .zhihu-panel${standaloneOutsideTheme},
|
|
515
|
-
:root[data-theme="dark"] .zhihu-panel${standaloneOutsideTheme} {
|
|
626
|
+
}
|
|
516
627
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
--
|
|
524
|
-
|
|
525
|
-
--font-size-6: 24px;
|
|
526
|
-
--font-size-7: 28px;
|
|
527
|
-
--font-size-8: 35px;
|
|
528
|
-
--font-size-9: 60px;
|
|
529
|
-
--space-1: 4px;
|
|
530
|
-
--space-2: 8px;
|
|
531
|
-
--space-3: 12px;
|
|
532
|
-
--space-4: 16px;
|
|
533
|
-
--space-5: 24px;
|
|
534
|
-
--radius-2: 4px;
|
|
535
|
-
--radius-3: 6px;
|
|
536
|
-
--control-h: 34px;
|
|
537
|
-
--gray-1: #111111;
|
|
538
|
-
--gray-2: #191919;
|
|
539
|
-
--gray-3: #222222;
|
|
540
|
-
--gray-4: #2a2a2a;
|
|
541
|
-
--gray-5: #313131;
|
|
542
|
-
--gray-6: #3a3a3a;
|
|
543
|
-
--gray-7: #484848;
|
|
544
|
-
--gray-8: #606060;
|
|
545
|
-
--gray-9: #6e6e6e;
|
|
546
|
-
--gray-10: #7b7b7b;
|
|
547
|
-
--gray-11: #b4b4b4;
|
|
548
|
-
--gray-12: #eeeeee;
|
|
549
|
-
--gray-a2: #ffffff09;
|
|
550
|
-
--gray-a3: #ffffff12;
|
|
551
|
-
--gray-a4: #ffffff1b;
|
|
552
|
-
--gray-a5: #ffffff22;
|
|
553
|
-
--gray-a6: #ffffff2c;
|
|
554
|
-
--accent-8: #435db1;
|
|
555
|
-
--accent-9: #3e63dd;
|
|
556
|
-
--accent-10: #5472e4;
|
|
557
|
-
--accent-11: #9eb1ff;
|
|
558
|
-
--accent-a3: #2f62ff3c;
|
|
559
|
-
--accent-a4: #3566ff57;
|
|
560
|
-
--accent-a8: #5b81feac;
|
|
561
|
-
--accent-contrast: #ffffff;
|
|
562
|
-
--red-9: #e5484d;
|
|
563
|
-
--red-11: #ff9592;
|
|
564
|
-
--green-9: #30a46c;
|
|
565
|
-
--green-11: #3dd68c;
|
|
566
|
-
--amber-a4: #fc820032;
|
|
567
|
-
--amber-a6: #fd9b0051;
|
|
568
|
-
--color-background: #111111;
|
|
569
|
-
--color-panel-solid: #191919;
|
|
570
|
-
--color-surface: rgba(0, 0, 0, 0.25);
|
|
571
|
-
--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);
|
|
572
|
-
--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);
|
|
573
|
-
--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);
|
|
574
|
-
color-scheme: dark;
|
|
628
|
+
/* --- Links. Ordinary DSH paints a light fill on a bare <a>, so plugin links
|
|
629
|
+
* stay transparent and the computed contrast falls through to the card behind
|
|
630
|
+
* them. Anchoring the selector here also outranks the contract's own row-name
|
|
631
|
+
* colour, so a result title reads as a link rather than as body text. It sits
|
|
632
|
+
* outside the nested block because it already names the root. */
|
|
633
|
+
.zhihu-panel a.zhihu-link {
|
|
634
|
+
color: var(--dsw-alias-brand-primary);
|
|
635
|
+
background: none;
|
|
575
636
|
}
|
|
637
|
+
|
|
638
|
+
/* Keyframes cannot nest inside a style rule; the name is plugin-prefixed. */
|
|
639
|
+
@keyframes zhihu-activity-pulse { 0%, 100% { opacity: .2; } 50% { opacity: 1; } }
|
|
576
640
|
`;
|
|
641
|
+
//#endregion
|
|
642
|
+
//#region src/client-host-ui.tsx
|
|
643
|
+
/**
|
|
644
|
+
* Folded fine print: one concise sentence stays in the flow, the detail list
|
|
645
|
+
* sits behind the host disclosure row. Children are `<li>` items.
|
|
646
|
+
*/
|
|
647
|
+
function ZhihuDetails(props) {
|
|
648
|
+
const [open, setOpen] = (0, react.useState)(props.defaultOpen === true);
|
|
649
|
+
const toggle = (0, react.useCallback)(() => setOpen((value) => !value), []);
|
|
650
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
651
|
+
className: "zhihu-guide",
|
|
652
|
+
"data-testid": props.testId,
|
|
653
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
|
|
654
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChecklistOutlineRegular, {}),
|
|
655
|
+
title: props.title,
|
|
656
|
+
open,
|
|
657
|
+
expandable: true,
|
|
658
|
+
expandOnRowClick: true,
|
|
659
|
+
onToggle: toggle,
|
|
660
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
661
|
+
className: "zhihu-guide-list dsh-ui-help",
|
|
662
|
+
children: props.children
|
|
663
|
+
})
|
|
664
|
+
})
|
|
665
|
+
});
|
|
666
|
+
}
|
|
577
667
|
/** React KeyboardEvent: isComposing/keyCode live on nativeEvent, not the synthetic event. */
|
|
578
668
|
function nativeKeyFlags(event) {
|
|
579
669
|
const native = event.nativeEvent;
|
|
@@ -620,8 +710,8 @@ function hostComponentsFromRenderProps(props) {
|
|
|
620
710
|
}
|
|
621
711
|
function renderInput(Input, props) {
|
|
622
712
|
if (Input) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, { ...props });
|
|
623
|
-
const { onChange, ...rest } = props;
|
|
624
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
713
|
+
const { onChange, ref, ...rest } = props;
|
|
714
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
625
715
|
...rest,
|
|
626
716
|
onChange: (event) => onChange(event.target.value)
|
|
627
717
|
});
|
|
@@ -629,6 +719,7 @@ function renderInput(Input, props) {
|
|
|
629
719
|
function renderSelect(Select, props, className) {
|
|
630
720
|
if (Select) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, { ...props });
|
|
631
721
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
722
|
+
id: props.id,
|
|
632
723
|
className,
|
|
633
724
|
value: props.value,
|
|
634
725
|
disabled: props.disabled,
|
|
@@ -640,7 +731,21 @@ function renderSelect(Select, props, className) {
|
|
|
640
731
|
}, option.value === "" ? "__empty" : option.value))
|
|
641
732
|
});
|
|
642
733
|
}
|
|
643
|
-
/**
|
|
734
|
+
/**
|
|
735
|
+
* The host's own Button still wins whenever the slot supplies one, so a page
|
|
736
|
+
* rendered inside DSH keeps the surrounding chrome. Standalone there is no host
|
|
737
|
+
* to defer to, and the plugin renders the official primitive instead of a
|
|
738
|
+
* hand-rolled `<button>`: the two legacy variant names the call sites use are
|
|
739
|
+
* mapped onto it here. `danger` has no primitive variant, so it keeps the
|
|
740
|
+
* outlined appearance and takes its tone from a class in the plugin stylesheet
|
|
741
|
+
* — the way every other destructive action in the host reads.
|
|
742
|
+
*/
|
|
743
|
+
const BUTTON_VARIANT = {
|
|
744
|
+
default: "outline",
|
|
745
|
+
primary: "primary",
|
|
746
|
+
danger: "outline",
|
|
747
|
+
icon: "ghost"
|
|
748
|
+
};
|
|
644
749
|
const ZhihuButton = (0, react.forwardRef)(function ZhihuButton({ host, variant, className, ...rest }, ref) {
|
|
645
750
|
if (host) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(host, {
|
|
646
751
|
ref,
|
|
@@ -648,21 +753,861 @@ const ZhihuButton = (0, react.forwardRef)(function ZhihuButton({ host, variant,
|
|
|
648
753
|
className,
|
|
649
754
|
...rest
|
|
650
755
|
});
|
|
651
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
756
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
652
757
|
ref,
|
|
653
|
-
|
|
654
|
-
className
|
|
758
|
+
variant: BUTTON_VARIANT[variant ?? "default"],
|
|
759
|
+
className,
|
|
655
760
|
...rest
|
|
656
761
|
});
|
|
657
762
|
});
|
|
658
763
|
//#endregion
|
|
764
|
+
//#region src/client-open-platform.tsx
|
|
765
|
+
const OPERATION_COPY = [
|
|
766
|
+
{
|
|
767
|
+
value: "question.recommendations",
|
|
768
|
+
zh: "适合回答的问题",
|
|
769
|
+
en: "Questions to answer"
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
value: "question.answers",
|
|
773
|
+
zh: "问题回答摘要(非全文)",
|
|
774
|
+
en: "Answer summaries (not full text)"
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
value: "content.detail",
|
|
778
|
+
zh: "本人创作全文",
|
|
779
|
+
en: "My content (full text)"
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
value: "content.comments",
|
|
783
|
+
zh: "本人创作评论",
|
|
784
|
+
en: "Comments on my content"
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
value: "creator.account.stats",
|
|
788
|
+
zh: "账号创作数据",
|
|
789
|
+
en: "Account creator stats"
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
value: "creator.content.stats",
|
|
793
|
+
zh: "单篇创作数据",
|
|
794
|
+
en: "Per-post creator stats"
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
value: "quota",
|
|
798
|
+
zh: "官方每日额度",
|
|
799
|
+
en: "Official daily quota"
|
|
800
|
+
}
|
|
801
|
+
];
|
|
802
|
+
function openPlatformOptions(t = translator()) {
|
|
803
|
+
return OPERATION_COPY.map((option) => ({
|
|
804
|
+
value: option.value,
|
|
805
|
+
label: t(option.zh, option.en)
|
|
806
|
+
}));
|
|
807
|
+
}
|
|
808
|
+
openPlatformOptions();
|
|
809
|
+
const createOpenPlatformForm = () => ({
|
|
810
|
+
query: "",
|
|
811
|
+
count: "5",
|
|
812
|
+
questionUrl: "",
|
|
813
|
+
contentUrl: "",
|
|
814
|
+
offset: "0",
|
|
815
|
+
limit: "20",
|
|
816
|
+
order: "score",
|
|
817
|
+
contentType: "all",
|
|
818
|
+
startDate: "",
|
|
819
|
+
endDate: "",
|
|
820
|
+
apiIds: []
|
|
821
|
+
});
|
|
822
|
+
const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
823
|
+
/** Int64 stays decimal text throughout the browser; never round-trip through Number. */
|
|
824
|
+
function openPlatformOffset(value) {
|
|
825
|
+
if (typeof value !== "string" || !/^\d+$/.test(value)) return null;
|
|
826
|
+
const decimal = value.replace(/^0+(?=\d)/, "");
|
|
827
|
+
return decimal.length < 19 || decimal.length === 19 && decimal <= "9223372036854775807" ? decimal : null;
|
|
828
|
+
}
|
|
829
|
+
/** `field` is the user-facing name; `param` stays in the message so a log still maps to the API. */
|
|
830
|
+
function integer(value, maximum, field, param, t) {
|
|
831
|
+
const message = t(`${field}(${param})必须是 1–${maximum} 的整数。`, `${field} (${param}) must be a whole number from 1 to ${maximum}.`);
|
|
832
|
+
if (!/^\d+$/.test(value.trim())) throw new Error(message);
|
|
833
|
+
const n = Number(value);
|
|
834
|
+
if (!Number.isInteger(n) || n < 1 || n > maximum) throw new Error(message);
|
|
835
|
+
return n;
|
|
836
|
+
}
|
|
837
|
+
function contentUrl(value, question, t) {
|
|
838
|
+
const clean = value.trim();
|
|
839
|
+
const invalid = t("须为有效的 HTTPS 知乎链接。", "Must be a valid HTTPS Zhihu link.");
|
|
840
|
+
if (!clean || /[\u0000-\u0020\u007f\\]/.test(clean)) throw new Error(invalid);
|
|
841
|
+
let url;
|
|
842
|
+
try {
|
|
843
|
+
url = new URL(clean);
|
|
844
|
+
} catch {
|
|
845
|
+
throw new Error(invalid);
|
|
846
|
+
}
|
|
847
|
+
const pathOk = question ? url.hostname === "www.zhihu.com" && /^\/question\/\d+\/?$/.test(url.pathname) : url.hostname === "www.zhihu.com" && /^\/(?:answer\/\d+|question\/\d+\/answer\/\d+|pin\/\d+|zvideo\/\d+)\/?$/.test(url.pathname) || url.hostname === "zhuanlan.zhihu.com" && /^\/p\/\d+\/?$/.test(url.pathname);
|
|
848
|
+
if (url.protocol !== "https:" || url.username || url.password || url.port || !pathOk) throw new Error(question ? t("问题链接须为 https://www.zhihu.com/question/{id}。", "The question link must be https://www.zhihu.com/question/{id}.") : t("内容链接须为知乎回答、文章、想法或视频。", "The content link must be a Zhihu answer, article, pin or video."));
|
|
849
|
+
return clean;
|
|
850
|
+
}
|
|
851
|
+
function validDate(value) {
|
|
852
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || value.startsWith("0000")) return false;
|
|
853
|
+
const date = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
|
|
854
|
+
return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
|
|
855
|
+
}
|
|
856
|
+
/** Browser-only validation; authoritative validation remains with the existing RPC. */
|
|
857
|
+
function buildOpenPlatformParams(operation, form, t = translator()) {
|
|
858
|
+
if (!OPERATION_COPY.some((option) => option.value === operation)) throw new Error(t("未知操作。", "Unknown operation."));
|
|
859
|
+
const params = {};
|
|
860
|
+
if (operation === "question.recommendations") {
|
|
861
|
+
if (form.query.trim()) params.query = form.query.trim();
|
|
862
|
+
params.count = integer(form.count, 20, t("数量", "Count"), "count", t);
|
|
863
|
+
}
|
|
864
|
+
if (operation === "question.answers") params.questionUrl = contentUrl(form.questionUrl, true, t);
|
|
865
|
+
if ([
|
|
866
|
+
"content.detail",
|
|
867
|
+
"content.comments",
|
|
868
|
+
"creator.content.stats"
|
|
869
|
+
].includes(operation)) params.contentUrl = contentUrl(form.contentUrl, false, t);
|
|
870
|
+
if (operation === "question.answers" || operation === "content.comments") {
|
|
871
|
+
const offset = openPlatformOffset(form.offset.trim());
|
|
872
|
+
if (offset === null) throw new Error(t("起始位置(offset)必须是非负整数。", "Start position (offset) must be a non-negative whole number."));
|
|
873
|
+
params.offset = offset;
|
|
874
|
+
params.limit = integer(form.limit, 50, t("每页数量", "Page size"), "limit", t);
|
|
875
|
+
}
|
|
876
|
+
if (operation === "content.comments") {
|
|
877
|
+
if (![
|
|
878
|
+
"score",
|
|
879
|
+
"reverse",
|
|
880
|
+
"ascending"
|
|
881
|
+
].includes(form.order)) throw new Error(t("评论排序无效。", "Invalid comment order."));
|
|
882
|
+
params.order = form.order;
|
|
883
|
+
}
|
|
884
|
+
if (operation === "creator.account.stats") {
|
|
885
|
+
if (![
|
|
886
|
+
"all",
|
|
887
|
+
"answer",
|
|
888
|
+
"article",
|
|
889
|
+
"pin",
|
|
890
|
+
"zvideo"
|
|
891
|
+
].includes(form.contentType)) throw new Error(t("内容类型无效。", "Invalid content type."));
|
|
892
|
+
params.contentType = form.contentType;
|
|
893
|
+
}
|
|
894
|
+
if (operation === "creator.account.stats" || operation === "creator.content.stats") {
|
|
895
|
+
const start = form.startDate.trim(), end = form.endDate.trim();
|
|
896
|
+
if (start || end) {
|
|
897
|
+
if (!validDate(start) || !validDate(end)) throw new Error(t("开始与结束日期须同时填写有效的 YYYY-MM-DD,或同时留空。", "Fill both start and end dates as YYYY-MM-DD, or leave both empty."));
|
|
898
|
+
if (end < start) throw new Error(t("结束日期不得早于开始日期。", "The end date cannot be before the start date."));
|
|
899
|
+
params.startDate = start;
|
|
900
|
+
params.endDate = end;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (operation === "quota") {
|
|
904
|
+
if (!Array.isArray(form.apiIds) || form.apiIds.some((id) => !ZHIHU_QUOTA_IDS.includes(id))) throw new Error(t("包含未知的额度项。", "Contains an unknown quota item."));
|
|
905
|
+
if (form.apiIds.length) params.apiIds = [...new Set(form.apiIds)];
|
|
906
|
+
}
|
|
907
|
+
return params;
|
|
908
|
+
}
|
|
909
|
+
function jsonValue(value, depth = 0) {
|
|
910
|
+
if (depth > 64) return false;
|
|
911
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
912
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
913
|
+
if (Array.isArray(value)) return value.every((item) => jsonValue(item, depth + 1));
|
|
914
|
+
return isRecord(value) && Object.values(value).every((item) => jsonValue(item, depth + 1));
|
|
915
|
+
}
|
|
916
|
+
function parseOpenPlatformResponse(operation, raw) {
|
|
917
|
+
if (!isRecord(raw) || raw.ok !== true) {
|
|
918
|
+
if (isRecord(raw) && raw.ok === false && isRecord(raw.error) && typeof raw.error.message === "string") throw new Error(`${typeof raw.error.code === "string" ? raw.error.code + ":" : ""}${raw.error.message}`);
|
|
919
|
+
throw new Error("RPC 返回格式无效。");
|
|
920
|
+
}
|
|
921
|
+
const value = raw.value;
|
|
922
|
+
if (!isRecord(value) || value.version !== 1 || value.operation !== operation || !isRecord(value.data) || !jsonValue(value.data)) throw new Error("开放平台响应格式无效。");
|
|
923
|
+
if (value.quotaId !== void 0 && !ZHIHU_QUOTA_IDS.includes(value.quotaId)) throw new Error("响应额度项无效。");
|
|
924
|
+
if (value.paging !== void 0 && (!isRecord(value.paging) || typeof value.paging.canContinue !== "boolean" || value.paging.isEnd !== void 0 && typeof value.paging.isEnd !== "boolean" || value.paging.warning !== void 0 && typeof value.paging.warning !== "string")) throw new Error("分页响应格式无效。");
|
|
925
|
+
return value;
|
|
926
|
+
}
|
|
927
|
+
function openPlatformNextOffset(result, currentOffset) {
|
|
928
|
+
if (!result || !["question.answers", "content.comments"].includes(result.operation) || result.paging?.canContinue !== true || result.paging.isEnd === true) return null;
|
|
929
|
+
const next = openPlatformOffset(result.paging.nextOffset), current = openPlatformOffset(currentOffset);
|
|
930
|
+
if (next === null || current === null) return null;
|
|
931
|
+
return next.length > current.length || next.length === current.length && next > current ? next : null;
|
|
932
|
+
}
|
|
933
|
+
/** Shared lineage executor, also testable without a DOM. Cancelled/superseded replies never publish. */
|
|
934
|
+
async function requestOpenPlatform(rpc, state, operation, params, publish) {
|
|
935
|
+
const { ticket, signal } = state.begin();
|
|
936
|
+
try {
|
|
937
|
+
const raw = await rpc.call(ZHIHU_RPC_CHANNEL, operation, params, signal);
|
|
938
|
+
if (!state.isCurrent(ticket)) return;
|
|
939
|
+
publish({ value: parseOpenPlatformResponse(operation, raw) });
|
|
940
|
+
} catch (error) {
|
|
941
|
+
if (state.isCurrent(ticket)) publish({ error: error instanceof Error ? error.message : String(error) });
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function ZhihuOpenPlatformSection({ rpc, Button, Input, Select, quotaOnly = false, selectedOperation }) {
|
|
945
|
+
const t = useT();
|
|
946
|
+
const [operation, setOperation] = (0, react.useState)(quotaOnly ? "quota" : "question.recommendations");
|
|
947
|
+
const [form, setForm] = (0, react.useState)(createOpenPlatformForm);
|
|
948
|
+
const [result, setResult] = (0, react.useState)(null);
|
|
949
|
+
const [error, setError] = (0, react.useState)("");
|
|
950
|
+
const [loading, setLoading] = (0, react.useState)(false);
|
|
951
|
+
const [stale, setStale] = (0, react.useState)(false);
|
|
952
|
+
const state = (0, react.useRef)(createZhihuClientState()).current;
|
|
953
|
+
const activeOperation = quotaOnly ? "quota" : selectedOperation ?? operation;
|
|
954
|
+
/** Switching operation or transport: the old result no longer belongs here. */
|
|
955
|
+
const reset = () => {
|
|
956
|
+
state.noteInput();
|
|
957
|
+
setLoading(false);
|
|
958
|
+
setResult(null);
|
|
959
|
+
setStale(false);
|
|
960
|
+
setError("");
|
|
961
|
+
};
|
|
962
|
+
/** Editing a field: abort in flight, keep the last result, flag it stale. */
|
|
963
|
+
const edited = () => {
|
|
964
|
+
state.noteInput();
|
|
965
|
+
setLoading(false);
|
|
966
|
+
setError("");
|
|
967
|
+
setStale(true);
|
|
968
|
+
};
|
|
969
|
+
(0, react.useEffect)(() => {
|
|
970
|
+
reset();
|
|
971
|
+
return () => state.cancel();
|
|
972
|
+
}, [
|
|
973
|
+
rpc,
|
|
974
|
+
quotaOnly,
|
|
975
|
+
selectedOperation,
|
|
976
|
+
state
|
|
977
|
+
]);
|
|
978
|
+
const change = (key, value) => {
|
|
979
|
+
edited();
|
|
980
|
+
setForm((previous) => ({
|
|
981
|
+
...previous,
|
|
982
|
+
[key]: value
|
|
983
|
+
}));
|
|
984
|
+
};
|
|
985
|
+
/** Label is user copy; the API parameter name rides in the hint for people cross-checking the docs. */
|
|
986
|
+
const field = (key, label, hint, placeholder) => {
|
|
987
|
+
const hintId = `zhihu-op-${key}-hint`;
|
|
988
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
989
|
+
className: "dsh-ui-field",
|
|
990
|
+
children: [
|
|
991
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
992
|
+
className: "dsh-ui-label",
|
|
993
|
+
children: label
|
|
994
|
+
}),
|
|
995
|
+
renderInput(Input, {
|
|
996
|
+
value: form[key],
|
|
997
|
+
onChange: (value) => change(key, value),
|
|
998
|
+
"aria-label": label,
|
|
999
|
+
"aria-describedby": hintId,
|
|
1000
|
+
placeholder,
|
|
1001
|
+
className: "zhihu-input"
|
|
1002
|
+
}),
|
|
1003
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1004
|
+
className: "dsh-ui-hint",
|
|
1005
|
+
id: hintId,
|
|
1006
|
+
children: hint
|
|
1007
|
+
})
|
|
1008
|
+
]
|
|
1009
|
+
}, key);
|
|
1010
|
+
};
|
|
1011
|
+
const select = (key, label, hint, options) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1012
|
+
className: "dsh-ui-field",
|
|
1013
|
+
children: [
|
|
1014
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1015
|
+
className: "dsh-ui-label",
|
|
1016
|
+
children: label
|
|
1017
|
+
}),
|
|
1018
|
+
renderSelect(Select, {
|
|
1019
|
+
value: form[key],
|
|
1020
|
+
onChange: (value) => change(key, value),
|
|
1021
|
+
"aria-label": label,
|
|
1022
|
+
options
|
|
1023
|
+
}, "dsh-ui-select"),
|
|
1024
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1025
|
+
className: "dsh-ui-hint",
|
|
1026
|
+
children: hint
|
|
1027
|
+
})
|
|
1028
|
+
]
|
|
1029
|
+
});
|
|
1030
|
+
const run = (offset) => {
|
|
1031
|
+
let params;
|
|
1032
|
+
const nextForm = offset === void 0 ? form : {
|
|
1033
|
+
...form,
|
|
1034
|
+
offset
|
|
1035
|
+
};
|
|
1036
|
+
try {
|
|
1037
|
+
params = buildOpenPlatformParams(activeOperation, nextForm, t);
|
|
1038
|
+
} catch (cause) {
|
|
1039
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (offset !== void 0) setForm(nextForm);
|
|
1043
|
+
setLoading(true);
|
|
1044
|
+
setError("");
|
|
1045
|
+
requestOpenPlatform(rpc, state, activeOperation, params, (outcome) => {
|
|
1046
|
+
setLoading(false);
|
|
1047
|
+
if ("value" in outcome) {
|
|
1048
|
+
setResult(outcome.value);
|
|
1049
|
+
setStale(false);
|
|
1050
|
+
} else setError(outcome.error);
|
|
1051
|
+
});
|
|
1052
|
+
};
|
|
1053
|
+
const nextOffset = stale ? null : openPlatformNextOffset(result, form.offset.trim());
|
|
1054
|
+
const paged = activeOperation === "question.answers" || activeOperation === "content.comments";
|
|
1055
|
+
const dated = activeOperation === "creator.account.stats" || activeOperation === "creator.content.stats";
|
|
1056
|
+
const options = openPlatformOptions(t);
|
|
1057
|
+
const title = quotaOnly ? t("官方每日额度", "Official daily quota") : t("问题与创作", "Questions and creator");
|
|
1058
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1059
|
+
className: "zhihu-open-platform dsh-ui-stack",
|
|
1060
|
+
"aria-label": title,
|
|
1061
|
+
children: [
|
|
1062
|
+
selectedOperation === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1063
|
+
className: "dsh-ui-heading",
|
|
1064
|
+
children: title
|
|
1065
|
+
}),
|
|
1066
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1067
|
+
className: "dsh-ui-help",
|
|
1068
|
+
children: quotaOnly ? t("查询知乎返回的官方额度,不推算余额。", "Shows the quota Zhihu reports; no balance is inferred.") : t("手动触发,不自动翻页或重试。", "Manual trigger only; no automatic paging or retry.")
|
|
1069
|
+
}),
|
|
1070
|
+
!quotaOnly && selectedOperation === void 0 && renderSelect(Select, {
|
|
1071
|
+
value: activeOperation,
|
|
1072
|
+
options,
|
|
1073
|
+
"aria-label": t("开放平台操作", "Open Platform operation"),
|
|
1074
|
+
onChange: (value) => {
|
|
1075
|
+
if (options.some((option) => option.value === value)) {
|
|
1076
|
+
reset();
|
|
1077
|
+
setOperation(value);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}, "dsh-ui-select"),
|
|
1081
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenPlatformDetails, { quotaOnly }),
|
|
1082
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1083
|
+
className: "dsh-ui-stack",
|
|
1084
|
+
children: [
|
|
1085
|
+
activeOperation === "question.recommendations" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [field("query", t("主题关键词", "Topic keywords"), t("query · 留空按账号画像推荐", "query · leave empty to recommend from your profile")), field("count", t("推荐数量", "Number of questions"), t("count · 1–20", "count · 1–20"))] }),
|
|
1086
|
+
activeOperation === "question.answers" && field("questionUrl", t("知乎问题链接", "Zhihu question link"), "questionUrl", "https://www.zhihu.com/question/123"),
|
|
1087
|
+
[
|
|
1088
|
+
"content.detail",
|
|
1089
|
+
"content.comments",
|
|
1090
|
+
"creator.content.stats"
|
|
1091
|
+
].includes(activeOperation) && field("contentUrl", t("本人内容链接", "Link to your content"), t("contentUrl · 回答、文章、想法或视频", "contentUrl · answer, article, pin or video"), "https://www.zhihu.com/answer/123"),
|
|
1092
|
+
paged && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [field("offset", t("起始位置", "Start position"), t("offset · 非负整数,从 0 开始", "offset · non-negative whole number, starting at 0")), field("limit", t("每页数量", "Page size"), t("limit · 1–50", "limit · 1–50"))] }),
|
|
1093
|
+
activeOperation === "content.comments" && select("order", t("评论排序", "Comment order"), "order", [
|
|
1094
|
+
{
|
|
1095
|
+
value: "score",
|
|
1096
|
+
label: t("热度", "Top")
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
value: "reverse",
|
|
1100
|
+
label: t("最新", "Newest")
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
value: "ascending",
|
|
1104
|
+
label: t("最早", "Oldest")
|
|
1105
|
+
}
|
|
1106
|
+
]),
|
|
1107
|
+
activeOperation === "creator.account.stats" && select("contentType", t("内容类型", "Content type"), "contentType", [
|
|
1108
|
+
{
|
|
1109
|
+
value: "all",
|
|
1110
|
+
label: t("全部", "All")
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
value: "answer",
|
|
1114
|
+
label: t("回答", "Answers")
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
value: "article",
|
|
1118
|
+
label: t("文章", "Articles")
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
value: "pin",
|
|
1122
|
+
label: t("想法", "Pins")
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
value: "zvideo",
|
|
1126
|
+
label: t("视频", "Videos")
|
|
1127
|
+
}
|
|
1128
|
+
]),
|
|
1129
|
+
dated && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [field("startDate", t("开始日期", "Start date"), t("startDate · YYYY-MM-DD,与结束日期成对填写", "startDate · YYYY-MM-DD, set together with the end date")), field("endDate", t("结束日期", "End date"), t("endDate · YYYY-MM-DD,不早于开始日期", "endDate · YYYY-MM-DD, not before the start date"))] }),
|
|
1130
|
+
activeOperation === "quota" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
1131
|
+
className: "zhihu-open-platform-quota dsh-ui-card dsh-ui-card--flat",
|
|
1132
|
+
children: [
|
|
1133
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", {
|
|
1134
|
+
className: "dsh-ui-label",
|
|
1135
|
+
children: t("额度项", "Quota items")
|
|
1136
|
+
}),
|
|
1137
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1138
|
+
className: "dsh-ui-hint",
|
|
1139
|
+
children: t("apiIds · 不选表示全部", "apiIds · none selected means all")
|
|
1140
|
+
}),
|
|
1141
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Checkbox, {
|
|
1142
|
+
checked: form.apiIds.length === 0,
|
|
1143
|
+
onChange: () => change("apiIds", []),
|
|
1144
|
+
label: t("全部额度项", "All quota items")
|
|
1145
|
+
}),
|
|
1146
|
+
ZHIHU_QUOTA_IDS.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Checkbox, {
|
|
1147
|
+
checked: form.apiIds.includes(id),
|
|
1148
|
+
onChange: (next) => change("apiIds", next ? [...form.apiIds, id] : form.apiIds.filter((value) => value !== id)),
|
|
1149
|
+
label: id
|
|
1150
|
+
}, id))
|
|
1151
|
+
]
|
|
1152
|
+
})
|
|
1153
|
+
]
|
|
1154
|
+
}),
|
|
1155
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1156
|
+
className: "dsh-ui-actions",
|
|
1157
|
+
children: [
|
|
1158
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1159
|
+
host: Button,
|
|
1160
|
+
variant: "primary",
|
|
1161
|
+
disabled: loading,
|
|
1162
|
+
onClick: () => run(),
|
|
1163
|
+
children: activeOperation === "quota" ? t("查询官方额度", "Check quota") : t("执行请求", "Run request")
|
|
1164
|
+
}),
|
|
1165
|
+
loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1166
|
+
host: Button,
|
|
1167
|
+
onClick: () => {
|
|
1168
|
+
state.cancel();
|
|
1169
|
+
setLoading(false);
|
|
1170
|
+
setError(t("已取消;晚到的结果将被丢弃。", "Cancelled; a late response will be discarded."));
|
|
1171
|
+
},
|
|
1172
|
+
children: t("取消请求", "Cancel")
|
|
1173
|
+
}),
|
|
1174
|
+
nextOffset !== null && !loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1175
|
+
host: Button,
|
|
1176
|
+
onClick: () => run(nextOffset),
|
|
1177
|
+
children: t("下一页", "Next page")
|
|
1178
|
+
})
|
|
1179
|
+
]
|
|
1180
|
+
}),
|
|
1181
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1182
|
+
className: loading ? "dsh-ui-loading" : "dsh-ui-hint",
|
|
1183
|
+
role: "status",
|
|
1184
|
+
children: loading ? t("等待响应…", "Waiting for response…") : ""
|
|
1185
|
+
}),
|
|
1186
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1187
|
+
className: "dsh-ui-error",
|
|
1188
|
+
role: "alert",
|
|
1189
|
+
children: error
|
|
1190
|
+
}),
|
|
1191
|
+
result && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1192
|
+
className: "dsh-ui-stack",
|
|
1193
|
+
children: [
|
|
1194
|
+
stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1195
|
+
className: "dsh-ui-banner",
|
|
1196
|
+
children: t("参数已变化,结果对应旧参数,请重新请求。", "Parameters changed; results are from the previous request. Run it again.")
|
|
1197
|
+
}),
|
|
1198
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1199
|
+
className: "dsh-ui-heading",
|
|
1200
|
+
children: activeOperation === "quota" ? t("官方原始额度字段", "Raw quota fields") : t("返回数据", "Response data")
|
|
1201
|
+
}),
|
|
1202
|
+
result.paging?.warning && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1203
|
+
className: "dsh-ui-banner",
|
|
1204
|
+
children: result.paging.warning
|
|
1205
|
+
}),
|
|
1206
|
+
!stale && paged && result.paging?.canContinue && nextOffset === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1207
|
+
className: "dsh-ui-banner",
|
|
1208
|
+
children: t("下一页位置缺失或未前进,已停止翻页。", "The next page position is missing or did not advance; paging stopped.")
|
|
1209
|
+
}),
|
|
1210
|
+
activeOperation === "content.detail" && (typeof result.data.Body !== "string" || !result.data.Body.trim()) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1211
|
+
className: "dsh-ui-banner dsh-ui-banner--danger",
|
|
1212
|
+
children: t("响应缺少正文(Body),未取得全文。", "The response has no body (Body); full text was not retrieved.")
|
|
1213
|
+
}),
|
|
1214
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
1215
|
+
className: "zhihu-open-platform-data dsh-ui-code",
|
|
1216
|
+
children: JSON.stringify(result.data, null, 2)
|
|
1217
|
+
})
|
|
1218
|
+
]
|
|
1219
|
+
})
|
|
1220
|
+
]
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
/** The fine print, folded: one line stays in the flow, the rest sits here. */
|
|
1224
|
+
function OpenPlatformDetails({ quotaOnly }) {
|
|
1225
|
+
const t = useT();
|
|
1226
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ZhihuDetails, {
|
|
1227
|
+
title: t("说明", "Details"),
|
|
1228
|
+
children: [
|
|
1229
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("只使用当前 Access Secret,不使用 OAuth,也不切换身份。", "Uses the current Access Secret only; no OAuth or identity switching.") }),
|
|
1230
|
+
!quotaOnly && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("问题回答仅提供摘要,非全文;本人创作全文、评论和数据仅限当前凭据对应的本人内容。", "Answer results are summaries, not full text. Your content, comments and stats are limited to the account behind this key.") }),
|
|
1231
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("业务请求可能消耗官方额度或产生费用,以知乎控制台为准;本地调用计数不是账单。", "Requests may consume official quota or incur cost; the Zhihu console is authoritative. Local counts are not billing.") }),
|
|
1232
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("额度查询不计入本地调用次数,不推算余额,不为缺失字段补零。", "Quota checks are not counted locally; no balance is inferred and missing fields are not zero-filled.") })
|
|
1233
|
+
]
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
//#endregion
|
|
1237
|
+
//#region src/client-quota.tsx
|
|
1238
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1239
|
+
const identityFields = /* @__PURE__ */ new Set([
|
|
1240
|
+
"APIID",
|
|
1241
|
+
"ApiId",
|
|
1242
|
+
"apiId",
|
|
1243
|
+
"api_id",
|
|
1244
|
+
"id",
|
|
1245
|
+
"Name",
|
|
1246
|
+
"name",
|
|
1247
|
+
"APIName",
|
|
1248
|
+
"ApiName",
|
|
1249
|
+
"Description",
|
|
1250
|
+
"description",
|
|
1251
|
+
"Date",
|
|
1252
|
+
"date",
|
|
1253
|
+
"Period",
|
|
1254
|
+
"period",
|
|
1255
|
+
"Unit",
|
|
1256
|
+
"unit",
|
|
1257
|
+
"Timestamp",
|
|
1258
|
+
"timestamp",
|
|
1259
|
+
"version"
|
|
1260
|
+
]);
|
|
1261
|
+
/** Friendly name first, official field in parentheses so it still maps to the Zhihu docs. */
|
|
1262
|
+
function fieldLabel(field, t) {
|
|
1263
|
+
switch (field) {
|
|
1264
|
+
case "RemainingQuota": return t("剩余额度(RemainingQuota)", "Remaining (RemainingQuota)");
|
|
1265
|
+
case "TotalQuota": return t("总额度(TotalQuota)", "Total (TotalQuota)");
|
|
1266
|
+
case "UsedQuota": return t("已用额度(UsedQuota)", "Used (UsedQuota)");
|
|
1267
|
+
case "TotalUsed": return t("已用额度(TotalUsed)", "Used (TotalUsed)");
|
|
1268
|
+
default: return field;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
function quotaFieldRole(field) {
|
|
1272
|
+
if (field === "RemainingQuota") return "remaining";
|
|
1273
|
+
if (field === "TotalQuota") return "total";
|
|
1274
|
+
if (field === "UsedQuota" || field === "TotalUsed") return "used";
|
|
1275
|
+
return "other";
|
|
1276
|
+
}
|
|
1277
|
+
/** Only nonnegative finite, safe magnitudes are chartable; exact decimal text is retained. */
|
|
1278
|
+
function quotaNumericValue(value) {
|
|
1279
|
+
if (typeof value !== "number" && typeof value !== "string") return null;
|
|
1280
|
+
if (typeof value === "string") {
|
|
1281
|
+
const decimal = value.trim();
|
|
1282
|
+
if (!/^\+?\d+(?:\.\d+)?$/.test(decimal)) return null;
|
|
1283
|
+
const [whole, fraction = ""] = decimal.replace(/^\+/, "").split(".");
|
|
1284
|
+
const integer = whole.replace(/^0+(?=\d)/, "");
|
|
1285
|
+
const limit = String(Number.MAX_SAFE_INTEGER);
|
|
1286
|
+
if (integer.length > limit.length || integer.length === limit.length && (integer > limit || integer === limit && /[1-9]/.test(fraction))) return null;
|
|
1287
|
+
}
|
|
1288
|
+
const n = typeof value === "number" ? value : Number(value.trim());
|
|
1289
|
+
if (!Number.isFinite(n) || n < 0 || n > Number.MAX_SAFE_INTEGER) return null;
|
|
1290
|
+
if (typeof value === "string" && n === 0 && /[1-9]/.test(value)) return null;
|
|
1291
|
+
return n;
|
|
1292
|
+
}
|
|
1293
|
+
function quotaMetric(field, raw, t = translator()) {
|
|
1294
|
+
const value = quotaNumericValue(raw);
|
|
1295
|
+
let text = t("不可用", "Unavailable");
|
|
1296
|
+
if (value !== null) text = typeof raw === "string" ? raw.trim() : String(raw);
|
|
1297
|
+
else if (typeof raw === "number" && Number.isFinite(raw) || typeof raw === "string" && (/^[+-]?\d+(?:\.\d+)?$/.test(raw.trim()) || /^(?:unlimited|infinite|infinity|不限|无限|无限制)$/i.test(raw.trim()))) text = String(raw).trim() + t("(原始标记,不绘图)", " (raw marker, not charted)");
|
|
1298
|
+
return {
|
|
1299
|
+
field,
|
|
1300
|
+
label: fieldLabel(field, t),
|
|
1301
|
+
text,
|
|
1302
|
+
value
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
const chartableRole = (category, role) => {
|
|
1306
|
+
const metric = category.metrics.find((value) => quotaFieldRole(value.field) === role);
|
|
1307
|
+
return metric !== void 0 && metric.value !== null;
|
|
1308
|
+
};
|
|
1309
|
+
/** The one meter worth drawing: what is left of this API's own ceiling. Both
|
|
1310
|
+
* figures are the server's, for this row; without both the cell stays a number
|
|
1311
|
+
* and never borrows a scale from a neighbouring row or field. */
|
|
1312
|
+
function quotaMeter(metrics) {
|
|
1313
|
+
const remaining = metrics.find((value) => quotaFieldRole(value.field) === "remaining" && value.value !== null);
|
|
1314
|
+
const total = metrics.find((value) => quotaFieldRole(value.field) === "total" && value.value !== null);
|
|
1315
|
+
if (!remaining || !total || !total.value) return null;
|
|
1316
|
+
return {
|
|
1317
|
+
remaining,
|
|
1318
|
+
total,
|
|
1319
|
+
share: remaining.value / total.value
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
function quotaColumns(categories, fields, t = translator()) {
|
|
1323
|
+
const holders = categories.filter((category) => category.metrics.some((value) => quotaFieldRole(value.field) === "used"));
|
|
1324
|
+
const derivable = holders.length > 0 && holders.every((category) => chartableRole(category, "total") && chartableRole(category, "remaining"));
|
|
1325
|
+
const rest = fields.filter((field) => {
|
|
1326
|
+
const role = quotaFieldRole(field);
|
|
1327
|
+
if (role === "total" || role === "remaining") return false;
|
|
1328
|
+
if (role === "used") return !derivable;
|
|
1329
|
+
return true;
|
|
1330
|
+
}).map((field) => ({
|
|
1331
|
+
kind: "field",
|
|
1332
|
+
field,
|
|
1333
|
+
label: fieldLabel(field, t),
|
|
1334
|
+
role: quotaFieldRole(field)
|
|
1335
|
+
}));
|
|
1336
|
+
return fields.some((field) => {
|
|
1337
|
+
const role = quotaFieldRole(field);
|
|
1338
|
+
return role === "total" || role === "remaining";
|
|
1339
|
+
}) ? [{
|
|
1340
|
+
kind: "meter",
|
|
1341
|
+
field: "",
|
|
1342
|
+
label: t("剩余 / 上限", "Remaining / ceiling"),
|
|
1343
|
+
role: "meter"
|
|
1344
|
+
}, ...rest] : rest;
|
|
1345
|
+
}
|
|
1346
|
+
/** The one cell every row is read through: the fraction when the row has both
|
|
1347
|
+
* halves, otherwise whichever single figure the server sent, named so a lone
|
|
1348
|
+
* number is never read as the other half. */
|
|
1349
|
+
function quotaFractionCell(category, t) {
|
|
1350
|
+
const total = category.metrics.find((value) => quotaFieldRole(value.field) === "total");
|
|
1351
|
+
const remaining = category.metrics.find((value) => quotaFieldRole(value.field) === "remaining");
|
|
1352
|
+
const lone = (metric, role) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1353
|
+
className: "zhihu-quota-value",
|
|
1354
|
+
children: [metric.text, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1355
|
+
className: "zhihu-quota-of",
|
|
1356
|
+
children: role === "total" ? t(" 上限", " ceiling") : t(" 剩余", " remaining")
|
|
1357
|
+
})]
|
|
1358
|
+
});
|
|
1359
|
+
const meter = quotaMeter(category.metrics);
|
|
1360
|
+
if (meter) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1361
|
+
className: "zhihu-quota-value",
|
|
1362
|
+
children: [
|
|
1363
|
+
meter.remaining.text,
|
|
1364
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1365
|
+
className: "zhihu-quota-of",
|
|
1366
|
+
children: [" / ", meter.total.text]
|
|
1367
|
+
}),
|
|
1368
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1369
|
+
className: "zhihu-quota-track",
|
|
1370
|
+
"aria-hidden": "true",
|
|
1371
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1372
|
+
className: "zhihu-quota-fill",
|
|
1373
|
+
style: { width: `${Number((meter.share * 100).toFixed(2))}%` }
|
|
1374
|
+
})
|
|
1375
|
+
})
|
|
1376
|
+
]
|
|
1377
|
+
});
|
|
1378
|
+
if (remaining) return lone(remaining, "remaining");
|
|
1379
|
+
if (total) return lone(total, "total");
|
|
1380
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1381
|
+
className: "zhihu-quota-value",
|
|
1382
|
+
children: t("不可用", "Unavailable")
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
/** Supports direct per-API rows only. No recursive guessing, aggregation, or balance derivation. */
|
|
1386
|
+
function projectQuota(data, t = translator()) {
|
|
1387
|
+
if (!record(data)) return {
|
|
1388
|
+
categories: [],
|
|
1389
|
+
fields: []
|
|
1390
|
+
};
|
|
1391
|
+
const source = Object.hasOwn(data, "Items") ? data.Items : data;
|
|
1392
|
+
const rows = [];
|
|
1393
|
+
if (Array.isArray(source)) {
|
|
1394
|
+
for (const raw of source) if (record(raw)) rows.push({ raw });
|
|
1395
|
+
} else if (record(source)) {
|
|
1396
|
+
if ([
|
|
1397
|
+
"APIID",
|
|
1398
|
+
"ApiId",
|
|
1399
|
+
"apiId",
|
|
1400
|
+
"api_id",
|
|
1401
|
+
"id"
|
|
1402
|
+
].some((key) => typeof source[key] === "string" && !!source[key].trim() || typeof source[key] === "number" && Number.isSafeInteger(source[key]))) rows.push({ raw: source });
|
|
1403
|
+
else for (const [key, raw] of Object.entries(source)) if (record(raw)) rows.push({
|
|
1404
|
+
raw,
|
|
1405
|
+
key
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
const fields = /* @__PURE__ */ new Set();
|
|
1409
|
+
return {
|
|
1410
|
+
categories: rows.map(({ raw, key }, index) => {
|
|
1411
|
+
const identifier = [
|
|
1412
|
+
raw.APIID,
|
|
1413
|
+
raw.ApiId,
|
|
1414
|
+
raw.apiId,
|
|
1415
|
+
raw.api_id,
|
|
1416
|
+
raw.id,
|
|
1417
|
+
key
|
|
1418
|
+
].find((value) => typeof value === "string" && value.trim() || typeof value === "number" && Number.isSafeInteger(value));
|
|
1419
|
+
const id = identifier === void 0 ? t(`类别 ${index + 1}`, `Category ${index + 1}`) : String(identifier);
|
|
1420
|
+
const name = [
|
|
1421
|
+
raw.APIName,
|
|
1422
|
+
raw.ApiName,
|
|
1423
|
+
raw.Name,
|
|
1424
|
+
raw.name
|
|
1425
|
+
].find((value) => typeof value === "string" && value.trim());
|
|
1426
|
+
const metrics = Object.entries(raw).filter(([field, value]) => !identityFields.has(field) && !record(value) && !Array.isArray(value)).map(([field, value]) => {
|
|
1427
|
+
fields.add(field);
|
|
1428
|
+
return quotaMetric(field, value, t);
|
|
1429
|
+
});
|
|
1430
|
+
return {
|
|
1431
|
+
id,
|
|
1432
|
+
label: name ? `${String(name)}(${id})` : id,
|
|
1433
|
+
metrics
|
|
1434
|
+
};
|
|
1435
|
+
}),
|
|
1436
|
+
fields: [...fields]
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* One row per API, read as `remaining / ceiling` with the bar under it. The
|
|
1441
|
+
* total earns no column of its own — it is the denominator the fraction already
|
|
1442
|
+
* prints — and the consumed figure is the gap between the two numbers, so it
|
|
1443
|
+
* earns none either. An earlier version gave all three their own panel, which
|
|
1444
|
+
* repeated the API list three times and let a full bar mean "exhausted" in one
|
|
1445
|
+
* place and "untouched" in another, in the same colour and the same length.
|
|
1446
|
+
*/
|
|
1447
|
+
function QuotaCharts({ data }) {
|
|
1448
|
+
const t = useT();
|
|
1449
|
+
const { categories, fields } = projectQuota(data, t);
|
|
1450
|
+
if (!categories.length || !fields.length) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1451
|
+
className: "dsh-ui-empty",
|
|
1452
|
+
children: t("官方额度数据不可用:响应中没有可识别的额度字段。", "Official quota data is unavailable: the response has no recognisable quota fields.")
|
|
1453
|
+
});
|
|
1454
|
+
const columns = quotaColumns(categories, fields, t);
|
|
1455
|
+
const metered = columns[0]?.kind === "meter";
|
|
1456
|
+
const api = t("接口", "API");
|
|
1457
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1458
|
+
className: "dsh-ui-stack",
|
|
1459
|
+
children: [
|
|
1460
|
+
metered && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1461
|
+
className: "dsh-ui-help",
|
|
1462
|
+
children: t("蓝条=该接口还剩自己上限的多少。各接口上限不同,条长不跨行比较。", "A bar is what an API has left of its own ceiling. Limits differ per API, so lengths are not comparable across rows.")
|
|
1463
|
+
}),
|
|
1464
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ZhihuDetails, {
|
|
1465
|
+
title: t("如何阅读", "How to read this"),
|
|
1466
|
+
children: [
|
|
1467
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("「剩余 / 上限」中的上限是知乎给该接口的总额度,蓝条是其中未消耗的部分。", "The ceiling in “remaining / ceiling” is the total Zhihu reports for that API, and the bar is the unconsumed part of it.") }),
|
|
1468
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("已消耗是上限减剩余,因此不单列;官方未返回上限时只显示剩余数字,不画条。", "Consumed is the ceiling minus the remaining, so it gets no column. With no total returned, only the remaining shows and no bar is drawn.") }),
|
|
1469
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("不合计不同 API,不推算已用、总额或余额。", "Different APIs are not summed; used, total and balance are never inferred.") }),
|
|
1470
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("统计周期以官方定义为准,不把累计数据当作今日用量。", "Periods follow the official definition; cumulative figures are not treated as today’s usage.") })
|
|
1471
|
+
]
|
|
1472
|
+
}),
|
|
1473
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1474
|
+
className: "zhihu-quota-scroll",
|
|
1475
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
1476
|
+
className: "zhihu-quota-table",
|
|
1477
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1478
|
+
scope: "col",
|
|
1479
|
+
className: "zhihu-quota-corner",
|
|
1480
|
+
children: api
|
|
1481
|
+
}), columns.map((column) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("th", {
|
|
1482
|
+
scope: "col",
|
|
1483
|
+
className: "zhihu-quota-head",
|
|
1484
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1485
|
+
className: "zhihu-quota-head-label",
|
|
1486
|
+
children: column.label
|
|
1487
|
+
}), column.kind === "meter" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1488
|
+
className: "zhihu-quota-head-scale",
|
|
1489
|
+
children: "RemainingQuota / TotalQuota"
|
|
1490
|
+
})]
|
|
1491
|
+
}, column.field || "meter"))] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: categories.map((category, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1492
|
+
scope: "row",
|
|
1493
|
+
className: "zhihu-quota-name",
|
|
1494
|
+
children: category.label
|
|
1495
|
+
}), columns.map((column) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1496
|
+
className: "zhihu-quota-cell",
|
|
1497
|
+
children: column.kind === "meter" ? quotaFractionCell(category, t) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1498
|
+
className: "zhihu-quota-value",
|
|
1499
|
+
children: (category.metrics.find((value) => value.field === column.field) ?? quotaMetric(column.field, void 0, t)).text
|
|
1500
|
+
})
|
|
1501
|
+
}, column.field || "meter"))] }, `${category.id}-${index}`)) })]
|
|
1502
|
+
})
|
|
1503
|
+
}),
|
|
1504
|
+
categories.filter((category) => !category.metrics.length).map((category, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1505
|
+
className: "dsh-ui-hint",
|
|
1506
|
+
children: t(`${category.label}:额度字段不可用。`, `${category.label}: quota fields unavailable.`)
|
|
1507
|
+
}, `${category.id}-${index}`))
|
|
1508
|
+
]
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
function ZhihuQuotaSection({ rpc, Button }) {
|
|
1512
|
+
const state = (0, react.useRef)(createZhihuClientState()).current;
|
|
1513
|
+
const [snapshot, setSnapshot] = (0, react.useState)(null);
|
|
1514
|
+
const [loading, setLoading] = (0, react.useState)(false);
|
|
1515
|
+
const [error, setError] = (0, react.useState)("");
|
|
1516
|
+
const [stale, setStale] = (0, react.useState)(false);
|
|
1517
|
+
const t = useT();
|
|
1518
|
+
const locale = useZhihuLocale();
|
|
1519
|
+
const refresh = (0, react.useCallback)(() => {
|
|
1520
|
+
setLoading(true);
|
|
1521
|
+
setError("");
|
|
1522
|
+
requestOpenPlatform(rpc, state, "quota", {}, (outcome) => {
|
|
1523
|
+
setLoading(false);
|
|
1524
|
+
if ("value" in outcome) {
|
|
1525
|
+
setSnapshot({
|
|
1526
|
+
data: outcome.value.data,
|
|
1527
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1528
|
+
});
|
|
1529
|
+
setStale(false);
|
|
1530
|
+
} else {
|
|
1531
|
+
setError(outcome.error);
|
|
1532
|
+
setStale(true);
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
}, [rpc, state]);
|
|
1536
|
+
(0, react.useEffect)(() => {
|
|
1537
|
+
refresh();
|
|
1538
|
+
return () => state.cancel();
|
|
1539
|
+
}, [refresh, state]);
|
|
1540
|
+
const title = t("官方额度用量", "Official quota");
|
|
1541
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1542
|
+
className: "dsh-ui-stack",
|
|
1543
|
+
"aria-label": title,
|
|
1544
|
+
children: [
|
|
1545
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1546
|
+
className: "dsh-ui-heading",
|
|
1547
|
+
children: title
|
|
1548
|
+
}),
|
|
1549
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1550
|
+
className: "dsh-ui-help",
|
|
1551
|
+
children: t("打开此页时自动查询,不自动重试。", "Loads when this tab opens; never retried automatically.")
|
|
1552
|
+
}),
|
|
1553
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ZhihuDetails, {
|
|
1554
|
+
title: t("说明", "Details"),
|
|
1555
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("仅显示官方返回字段,不补零,不推算费用。", "Shows only the fields Zhihu returns; nothing is zero-filled or priced.") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("官方额度与本地每日调用计数分开,额度查询不计入本地调用次数。", "Official quota is separate from local daily counts; quota checks are not counted locally.") })]
|
|
1556
|
+
}),
|
|
1557
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1558
|
+
className: "dsh-ui-actions",
|
|
1559
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1560
|
+
host: Button,
|
|
1561
|
+
variant: "primary",
|
|
1562
|
+
disabled: loading,
|
|
1563
|
+
onClick: refresh,
|
|
1564
|
+
children: t("刷新官方额度", "Refresh quota")
|
|
1565
|
+
}), loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1566
|
+
host: Button,
|
|
1567
|
+
onClick: () => {
|
|
1568
|
+
state.cancel();
|
|
1569
|
+
setLoading(false);
|
|
1570
|
+
setError(t("已取消;晚到的结果将被丢弃。", "Cancelled; a late response will be discarded."));
|
|
1571
|
+
},
|
|
1572
|
+
children: t("取消请求", "Cancel")
|
|
1573
|
+
})]
|
|
1574
|
+
}),
|
|
1575
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1576
|
+
className: loading ? "dsh-ui-loading" : "dsh-ui-hint",
|
|
1577
|
+
role: "status",
|
|
1578
|
+
children: loading ? t("正在查询官方额度…", "Checking official quota…") : !snapshot && !error ? t("等待查询官方额度。", "Waiting to check official quota.") : ""
|
|
1579
|
+
}),
|
|
1580
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1581
|
+
className: "dsh-ui-error",
|
|
1582
|
+
role: "alert",
|
|
1583
|
+
children: error
|
|
1584
|
+
}),
|
|
1585
|
+
snapshot && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1586
|
+
className: "dsh-ui-stack",
|
|
1587
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1588
|
+
className: stale ? "dsh-ui-warn" : "dsh-ui-hint",
|
|
1589
|
+
children: [stale ? t("旧数据(刷新未成功) · ", "Old data (refresh failed) · ") : t("最近成功查询 · ", "Last checked · "), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
1590
|
+
dateTime: snapshot.timestamp,
|
|
1591
|
+
children: new Date(snapshot.timestamp).toLocaleString(locale === "en" ? "en" : "zh-CN")
|
|
1592
|
+
})]
|
|
1593
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaCharts, { data: snapshot.data })]
|
|
1594
|
+
}),
|
|
1595
|
+
!snapshot && !loading && error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1596
|
+
className: "dsh-ui-empty",
|
|
1597
|
+
children: t("官方额度暂不可用,请稍后手动刷新。", "Official quota is unavailable. Refresh again later.")
|
|
1598
|
+
})
|
|
1599
|
+
]
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
//#endregion
|
|
659
1603
|
//#region src/client.tsx
|
|
660
1604
|
const name = "dsh-zhihu-client";
|
|
661
1605
|
const inject = [
|
|
662
1606
|
"slots",
|
|
663
1607
|
"connection",
|
|
664
1608
|
"remote",
|
|
665
|
-
"remote.credentials"
|
|
1609
|
+
"remote.credentials",
|
|
1610
|
+
"locale"
|
|
666
1611
|
];
|
|
667
1612
|
const PACKAGE_NAME = "@klarkxy/dsh-zhihu";
|
|
668
1613
|
const SLOT_ORDER = 120;
|
|
@@ -706,29 +1651,85 @@ function safeUrl(url) {
|
|
|
706
1651
|
return /^https?:\/\//i.test(url) ? url : null;
|
|
707
1652
|
}
|
|
708
1653
|
/** Credential absence and transport failure read differently from a plain bad request. */
|
|
709
|
-
function failureOf(code, message) {
|
|
1654
|
+
function failureOf(code, message, t = translator()) {
|
|
710
1655
|
if (code === "token-missing") return {
|
|
711
1656
|
kind: "credential",
|
|
712
|
-
text: "未配置知乎 Access Secret
|
|
1657
|
+
text: t("未配置知乎 Access Secret 或凭证不可用,请到「设置」页配置。", "Zhihu Access Secret is missing or unavailable. Set it on the Settings tab.")
|
|
713
1658
|
};
|
|
714
1659
|
return {
|
|
715
1660
|
kind: "request",
|
|
716
|
-
text: `请求失败:${message}`
|
|
1661
|
+
text: t(`请求失败:${message}`, `Request failed: ${message}`)
|
|
717
1662
|
};
|
|
718
1663
|
}
|
|
719
|
-
function networkFailure(cause) {
|
|
1664
|
+
function networkFailure(cause, t = translator()) {
|
|
1665
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
720
1666
|
return {
|
|
721
1667
|
kind: "network",
|
|
722
|
-
text: `网络或连接失败:${
|
|
1668
|
+
text: t(`网络或连接失败:${detail}`, `Network or connection failed: ${detail}`)
|
|
723
1669
|
};
|
|
724
1670
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1671
|
+
/**
|
|
1672
|
+
* One external-link behaviour for the whole page: an http(s) anchor that the
|
|
1673
|
+
* desktop bridge opens in the system browser when present, and a plain new tab
|
|
1674
|
+
* otherwise. Anything that is not http(s) degrades to text.
|
|
1675
|
+
*/
|
|
1676
|
+
function ExternalLink(props) {
|
|
1677
|
+
const url = safeUrl(props.url);
|
|
1678
|
+
if (!url) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1679
|
+
className: props.className,
|
|
1680
|
+
children: props.children
|
|
1681
|
+
});
|
|
1682
|
+
const open = (event) => {
|
|
1683
|
+
const bridge = globalThis.dshWindow;
|
|
1684
|
+
if (bridge?.openExternal) {
|
|
1685
|
+
event.preventDefault();
|
|
1686
|
+
bridge.openExternal(url);
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1690
|
+
className: props.className ?? "zhihu-link",
|
|
1691
|
+
href: url,
|
|
1692
|
+
target: "_blank",
|
|
1693
|
+
rel: "noreferrer noopener",
|
|
1694
|
+
onClick: open,
|
|
1695
|
+
children: props.children
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
const MODES = [
|
|
1699
|
+
"search",
|
|
1700
|
+
"global",
|
|
1701
|
+
"hot",
|
|
1702
|
+
"knowledge",
|
|
1703
|
+
"ask"
|
|
1704
|
+
];
|
|
1705
|
+
function modeLabel(mode, t) {
|
|
1706
|
+
switch (mode) {
|
|
1707
|
+
case "search": return t("站内搜索", "Zhihu search");
|
|
1708
|
+
case "global": return t("全网搜索", "Web search");
|
|
1709
|
+
case "hot": return t("知乎热榜", "Trending");
|
|
1710
|
+
case "knowledge": return t("知识库检索", "Knowledge base search");
|
|
1711
|
+
case "ask": return t("直答", "Direct answer");
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
function testOptions(t) {
|
|
1715
|
+
return [...MODES.map((value) => ({
|
|
1716
|
+
value,
|
|
1717
|
+
label: modeLabel(value, t)
|
|
1718
|
+
})), ...openPlatformOptions(t).filter((option) => option.value !== "quota")];
|
|
1719
|
+
}
|
|
1720
|
+
const TEST_OPTIONS = testOptions(translator("zh"));
|
|
1721
|
+
function ZhihuTestSelector({ value, onChange, Select }) {
|
|
1722
|
+
const t = useT();
|
|
1723
|
+
const options = testOptions(t);
|
|
1724
|
+
return renderSelect(Select, {
|
|
1725
|
+
"aria-label": t("测试功能", "Feature to test"),
|
|
1726
|
+
value,
|
|
1727
|
+
options,
|
|
1728
|
+
onChange: (next) => {
|
|
1729
|
+
if (options.some((option) => option.value === next)) onChange(next);
|
|
1730
|
+
}
|
|
1731
|
+
}, "dsh-ui-select");
|
|
1732
|
+
}
|
|
732
1733
|
const MODE_ENDPOINT = {
|
|
733
1734
|
search: "search",
|
|
734
1735
|
global: "global.search",
|
|
@@ -739,29 +1740,35 @@ const MODE_ENDPOINT = {
|
|
|
739
1740
|
const ASK_MODELS = [
|
|
740
1741
|
{
|
|
741
1742
|
value: "zhida-thinking-1p5",
|
|
742
|
-
|
|
1743
|
+
zh: "思考",
|
|
1744
|
+
en: "Thinking"
|
|
743
1745
|
},
|
|
744
1746
|
{
|
|
745
1747
|
value: "zhida-fast-1p5",
|
|
746
|
-
|
|
1748
|
+
zh: "快速",
|
|
1749
|
+
en: "Fast"
|
|
747
1750
|
},
|
|
748
1751
|
{
|
|
749
1752
|
value: "zhida-agent",
|
|
750
|
-
|
|
1753
|
+
zh: "智能体",
|
|
1754
|
+
en: "Agent"
|
|
751
1755
|
}
|
|
752
1756
|
];
|
|
753
1757
|
const SCOPE_OPTIONS = [
|
|
754
1758
|
{
|
|
755
1759
|
value: "public",
|
|
756
|
-
|
|
1760
|
+
zh: "公开库",
|
|
1761
|
+
en: "Public"
|
|
757
1762
|
},
|
|
758
1763
|
{
|
|
759
1764
|
value: "personal",
|
|
760
|
-
|
|
1765
|
+
zh: "个人库",
|
|
1766
|
+
en: "Personal"
|
|
761
1767
|
},
|
|
762
1768
|
{
|
|
763
1769
|
value: "subscription",
|
|
764
|
-
|
|
1770
|
+
zh: "订阅库",
|
|
1771
|
+
en: "Subscribed"
|
|
765
1772
|
}
|
|
766
1773
|
];
|
|
767
1774
|
function objectItems(value) {
|
|
@@ -793,7 +1800,7 @@ function parseOutcome(mode, value) {
|
|
|
793
1800
|
if (mode === "hot") return {
|
|
794
1801
|
mode: "hot",
|
|
795
1802
|
items: items.map((item) => ({
|
|
796
|
-
title: text(item.title, "
|
|
1803
|
+
title: text(item.title, "—"),
|
|
797
1804
|
url: text(item.url),
|
|
798
1805
|
summary: text(item.summary)
|
|
799
1806
|
}))
|
|
@@ -801,7 +1808,7 @@ function parseOutcome(mode, value) {
|
|
|
801
1808
|
if (mode === "knowledge") return {
|
|
802
1809
|
mode: "knowledge",
|
|
803
1810
|
items: items.map((item) => ({
|
|
804
|
-
docName: text(item.docName, "
|
|
1811
|
+
docName: text(item.docName, "—"),
|
|
805
1812
|
originUrl: text(item.originUrl),
|
|
806
1813
|
snippets: Array.isArray(item.snippets) ? item.snippets.filter((s) => typeof s === "string") : []
|
|
807
1814
|
}))
|
|
@@ -810,67 +1817,68 @@ function parseOutcome(mode, value) {
|
|
|
810
1817
|
mode,
|
|
811
1818
|
emptyReason: typeof value.emptyReason === "string" ? value.emptyReason : void 0,
|
|
812
1819
|
items: items.map((item) => ({
|
|
813
|
-
title: text(item.title, "
|
|
814
|
-
type: text(item.type
|
|
1820
|
+
title: text(item.title, "—"),
|
|
1821
|
+
type: text(item.type),
|
|
815
1822
|
url: text(item.url),
|
|
816
1823
|
summary: text(item.summary),
|
|
817
1824
|
votes: typeof item.votes === "number" ? item.votes : 0,
|
|
818
1825
|
comments: typeof item.comments === "number" ? item.comments : 0,
|
|
819
|
-
author: text(item.author
|
|
1826
|
+
author: text(item.author),
|
|
820
1827
|
editTime: text(item.editTime)
|
|
821
1828
|
}))
|
|
822
1829
|
};
|
|
823
1830
|
}
|
|
824
1831
|
function LinkOrText(props) {
|
|
825
|
-
|
|
826
|
-
|
|
1832
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExternalLink, {
|
|
1833
|
+
url: props.url,
|
|
827
1834
|
className: props.className,
|
|
828
1835
|
children: props.label
|
|
829
1836
|
});
|
|
830
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
831
|
-
className: props.className ?? "zhihu-link",
|
|
832
|
-
href: url,
|
|
833
|
-
target: "_blank",
|
|
834
|
-
rel: "noreferrer",
|
|
835
|
-
children: props.label
|
|
836
|
-
});
|
|
837
1837
|
}
|
|
838
1838
|
function OutcomeView(props) {
|
|
839
1839
|
const { outcome, stale } = props;
|
|
1840
|
+
const t = useT();
|
|
1841
|
+
const staleBanner = stale ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1842
|
+
className: "dsh-ui-banner",
|
|
1843
|
+
role: "status",
|
|
1844
|
+
children: t("查询已变化,结果对应旧查询,请重新搜索。", "The query changed; results are from the previous query. Search again.")
|
|
1845
|
+
}) : null;
|
|
840
1846
|
let body = null;
|
|
841
1847
|
let summary = "";
|
|
842
1848
|
if (outcome.mode === "ask") {
|
|
843
|
-
summary = `直答(${outcome.answer.model || "默认模型"}
|
|
1849
|
+
summary = t(`直答(${outcome.answer.model || "默认模型"})`, `Direct answer (${outcome.answer.model || "default model"})`);
|
|
844
1850
|
body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [outcome.answer.reasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
845
|
-
className: "zhihu-ask-reasoning",
|
|
846
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
|
|
1851
|
+
className: "zhihu-ask-reasoning dsh-ui-card dsh-ui-card--flat",
|
|
1852
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
|
|
1853
|
+
className: "dsh-ui-heading",
|
|
1854
|
+
children: t("思考过程", "Reasoning")
|
|
1855
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1856
|
+
className: "dsh-ui-help",
|
|
1857
|
+
children: outcome.answer.reasoning
|
|
1858
|
+
})]
|
|
847
1859
|
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
848
|
-
className: "zhihu-ask-content",
|
|
1860
|
+
className: "zhihu-ask-content dsh-ui-wrap",
|
|
849
1861
|
children: outcome.answer.content
|
|
850
1862
|
})] });
|
|
851
1863
|
} else if (outcome.items.length === 0) {
|
|
852
1864
|
const reason = outcome.mode === "search" || outcome.mode === "global" ? outcome.emptyReason : void 0;
|
|
853
1865
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
854
1866
|
"data-testid": "zhihu-results",
|
|
855
|
-
className: "
|
|
856
|
-
children: [
|
|
857
|
-
className: "
|
|
858
|
-
|
|
859
|
-
children: "查询已变化,以下内容对应旧查询,请重新搜索。"
|
|
860
|
-
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
861
|
-
className: "zhihu-results-summary",
|
|
862
|
-
children: `未找到相关结果${reason ? `(${reason})` : ""}。`
|
|
1867
|
+
className: "dsh-ui-stack",
|
|
1868
|
+
children: [staleBanner, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1869
|
+
className: "dsh-ui-empty",
|
|
1870
|
+
children: t(`未找到相关结果${reason ? `(${reason})` : ""}。`, `No results${reason ? ` (${reason})` : ""}.`)
|
|
863
1871
|
})]
|
|
864
1872
|
});
|
|
865
1873
|
} else if (outcome.mode === "knowledge") {
|
|
866
|
-
summary = `知识库检索共 ${outcome.items.length}
|
|
1874
|
+
summary = t(`知识库检索共 ${outcome.items.length} 条`, `${outcome.items.length} knowledge base results`);
|
|
867
1875
|
body = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
868
|
-
className: "
|
|
1876
|
+
className: "dsh-ui-list",
|
|
869
1877
|
children: outcome.items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
870
|
-
className: "
|
|
1878
|
+
className: "dsh-ui-card dsh-ui-card--flat dsh-ui-list-row",
|
|
871
1879
|
children: [
|
|
872
1880
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
873
|
-
className: "
|
|
1881
|
+
className: "dsh-ui-list-name",
|
|
874
1882
|
children: item.docName
|
|
875
1883
|
}),
|
|
876
1884
|
item.originUrl && safeUrl(item.originUrl) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkOrText, {
|
|
@@ -878,46 +1886,46 @@ function OutcomeView(props) {
|
|
|
878
1886
|
label: item.originUrl
|
|
879
1887
|
}) : null,
|
|
880
1888
|
item.snippets.map((snippet, snippetIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
881
|
-
className: "zhihu-result-snippet",
|
|
1889
|
+
className: "zhihu-result-snippet dsh-ui-help",
|
|
882
1890
|
children: snippet
|
|
883
1891
|
}, snippetIndex))
|
|
884
1892
|
]
|
|
885
1893
|
}, `${item.docName}:${index}`))
|
|
886
1894
|
});
|
|
887
1895
|
} else if (outcome.mode === "hot") {
|
|
888
|
-
summary = `知乎热榜共 ${outcome.items.length}
|
|
1896
|
+
summary = t(`知乎热榜共 ${outcome.items.length} 条`, `${outcome.items.length} trending items`);
|
|
889
1897
|
body = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
890
|
-
className: "
|
|
1898
|
+
className: "dsh-ui-list",
|
|
891
1899
|
children: outcome.items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
892
|
-
className: "
|
|
1900
|
+
className: "dsh-ui-card dsh-ui-card--flat dsh-ui-list-row",
|
|
893
1901
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkOrText, {
|
|
894
1902
|
url: item.url,
|
|
895
1903
|
label: item.title,
|
|
896
|
-
className: "
|
|
1904
|
+
className: "dsh-ui-list-name zhihu-link"
|
|
897
1905
|
}), item.summary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
898
|
-
className: "
|
|
1906
|
+
className: "dsh-ui-meta",
|
|
899
1907
|
children: item.summary
|
|
900
1908
|
}) : null]
|
|
901
1909
|
}, `${index}`))
|
|
902
1910
|
});
|
|
903
1911
|
} else {
|
|
904
|
-
summary = `${
|
|
1912
|
+
summary = t(`${modeLabel(outcome.mode, t)}共 ${outcome.items.length} 条`, `${modeLabel(outcome.mode, t)}: ${outcome.items.length} results`);
|
|
905
1913
|
body = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
906
|
-
className: "
|
|
1914
|
+
className: "dsh-ui-list",
|
|
907
1915
|
children: outcome.items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
908
|
-
className: "
|
|
1916
|
+
className: "dsh-ui-card dsh-ui-card--flat dsh-ui-list-row",
|
|
909
1917
|
children: [
|
|
910
1918
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkOrText, {
|
|
911
1919
|
url: item.url,
|
|
912
1920
|
label: item.title,
|
|
913
|
-
className: "
|
|
1921
|
+
className: "dsh-ui-list-name zhihu-link"
|
|
914
1922
|
}),
|
|
915
1923
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
916
|
-
className: "
|
|
917
|
-
children:
|
|
1924
|
+
className: "dsh-ui-list-desc",
|
|
1925
|
+
children: t(`${item.type || "内容"} · ${item.author || "匿名"} · 赞同 ${item.votes} · 评论 ${item.comments}${item.editTime ? ` · ${item.editTime}` : ""}`, `${item.type || "Content"} · ${item.author || "Anonymous"} · ${item.votes} upvotes · ${item.comments} comments${item.editTime ? ` · ${item.editTime}` : ""}`)
|
|
918
1926
|
}),
|
|
919
1927
|
item.summary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
920
|
-
className: "
|
|
1928
|
+
className: "dsh-ui-meta",
|
|
921
1929
|
children: item.summary
|
|
922
1930
|
}) : null
|
|
923
1931
|
]
|
|
@@ -926,16 +1934,11 @@ function OutcomeView(props) {
|
|
|
926
1934
|
}
|
|
927
1935
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
928
1936
|
"data-testid": "zhihu-results",
|
|
929
|
-
className: "
|
|
1937
|
+
className: "dsh-ui-stack",
|
|
930
1938
|
children: [
|
|
931
|
-
|
|
932
|
-
className: "zhihu-stale",
|
|
933
|
-
role: "status",
|
|
934
|
-
children: "查询已变化,以下内容对应旧查询,请重新搜索。"
|
|
935
|
-
}) : null,
|
|
1939
|
+
staleBanner,
|
|
936
1940
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
937
|
-
className: "
|
|
938
|
-
"aria-live": "polite",
|
|
1941
|
+
className: "dsh-ui-heading",
|
|
939
1942
|
children: summary
|
|
940
1943
|
}),
|
|
941
1944
|
body
|
|
@@ -967,32 +1970,56 @@ function useAlive() {
|
|
|
967
1970
|
}, []);
|
|
968
1971
|
return alive;
|
|
969
1972
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1973
|
+
/**
|
|
1974
|
+
* Collapsible detail block. The primitive owns the row chrome; the body keeps
|
|
1975
|
+
* the contract's help tier so a long explanation never sits in the page flow.
|
|
1976
|
+
*/
|
|
1977
|
+
function CapabilitiesNote(props) {
|
|
1978
|
+
const t = useT();
|
|
1979
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ZhihuDetails, {
|
|
1980
|
+
title: t("可用能力", "Capabilities"),
|
|
1981
|
+
defaultOpen: props.defaultOpen,
|
|
1982
|
+
testId: "zhihu-capabilities",
|
|
1983
|
+
children: [
|
|
1984
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("测试:站内搜索、全网搜索、热榜、直答、知识库检索与问题创作查询。", "Test: Zhihu search, web search, trending, direct answers, knowledge base search, question and creator queries.") }),
|
|
1985
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("问题与创作:问题推荐、回答摘要、本人已发布内容及评论、账号与单篇创作数据。", "Questions and creator: question recommendations, answer summaries, your published content and comments, account and per-post stats.") }),
|
|
1986
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("统计:本地记录的每日调用、失败与结果条数。", "Stats: daily calls, failures and result counts recorded locally.") }),
|
|
1987
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("官方用量:知乎返回的官方剩余额度。", "Official quota: the remaining quota Zhihu reports.") }),
|
|
1988
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("知识库:读取列表与上传参考资料,文件进入知乎云端。", "Knowledge base: list bases and upload references; files go to Zhihu cloud.") }),
|
|
1989
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("全部能力共用此 Access Secret,不支持 OAuth 身份切换。", "Every feature shares this Access Secret; OAuth identity switching is not supported.") }),
|
|
1990
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
1991
|
+
t("模型工具需在 Agent 中加载 ", "For model tools, load "),
|
|
1992
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: "@klarkxy/dsh-zhihu/tools" }),
|
|
1993
|
+
t(",仅配置密钥不会启用工具。", " in the Agent; a key alone does not enable them.")
|
|
1994
|
+
] }),
|
|
1995
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("请求手动触发(官方用量在打开页签时加载),不会自动重试。", "Requests run only when you trigger them (official quota loads when its tab opens) and are never retried automatically.") })
|
|
1996
|
+
]
|
|
978
1997
|
});
|
|
979
1998
|
}
|
|
980
1999
|
function SettingsShell(props) {
|
|
2000
|
+
const t = useT();
|
|
981
2001
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
982
|
-
className: "
|
|
2002
|
+
className: "dsh-ui-stack",
|
|
983
2003
|
"data-testid": "zhihu-settings",
|
|
984
|
-
"aria-label": "知乎凭证设置",
|
|
985
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CapabilitiesNote, {})
|
|
2004
|
+
"aria-label": t("知乎凭证设置", "Zhihu credential settings"),
|
|
2005
|
+
children: [props.children, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CapabilitiesNote, {})]
|
|
986
2006
|
});
|
|
987
2007
|
}
|
|
988
2008
|
function SettingsSection(props) {
|
|
989
2009
|
const { credentials } = props;
|
|
2010
|
+
const t = useT();
|
|
990
2011
|
const alive = useAlive();
|
|
991
2012
|
const [state, setState] = (0, react.useState)({ status: "loading" });
|
|
992
2013
|
const [keyDraft, setKeyDraft] = (0, react.useState)("");
|
|
993
|
-
const [
|
|
2014
|
+
const [pending, setPending] = (0, react.useState)(null);
|
|
2015
|
+
const [confirmClear, setConfirmClear] = (0, react.useState)(false);
|
|
994
2016
|
const [failure, setFailure] = (0, react.useState)(void 0);
|
|
995
2017
|
const [note, setNote] = (0, react.useState)(void 0);
|
|
2018
|
+
const busy = pending !== null;
|
|
2019
|
+
const editDraft = (value) => {
|
|
2020
|
+
setKeyDraft(value);
|
|
2021
|
+
setNote(void 0);
|
|
2022
|
+
};
|
|
996
2023
|
const load = (0, react.useCallback)(async () => {
|
|
997
2024
|
setState((current) => current.status === "ready" ? current : { status: "loading" });
|
|
998
2025
|
try {
|
|
@@ -1021,32 +2048,35 @@ function SettingsSection(props) {
|
|
|
1021
2048
|
load();
|
|
1022
2049
|
}, [load]);
|
|
1023
2050
|
if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1024
|
-
className: "
|
|
2051
|
+
className: "dsh-ui-loading",
|
|
1025
2052
|
role: "status",
|
|
1026
|
-
children: [activityDots(), "正在读取凭证状态…"]
|
|
2053
|
+
children: [activityDots(), t("正在读取凭证状态…", "Loading credential status…")]
|
|
1027
2054
|
}) });
|
|
1028
|
-
if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1029
|
-
className: "
|
|
2055
|
+
if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsShell, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2056
|
+
className: "dsh-ui-error",
|
|
1030
2057
|
role: "alert",
|
|
1031
|
-
children:
|
|
2058
|
+
children: t(`读取失败:${state.error}`, `Could not load: ${state.error}`)
|
|
2059
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2060
|
+
className: "dsh-ui-actions",
|
|
2061
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1032
2062
|
host: props.Button,
|
|
1033
|
-
className: "zhihu-button",
|
|
1034
2063
|
onClick: () => void load(),
|
|
1035
|
-
children: "重试"
|
|
1036
|
-
})
|
|
1037
|
-
}) });
|
|
2064
|
+
children: t("重试", "Retry")
|
|
2065
|
+
})
|
|
2066
|
+
})] });
|
|
1038
2067
|
const credential = state.credential;
|
|
1039
2068
|
const keyLocked = credential?.writable === false;
|
|
1040
2069
|
const configured = credential?.configured === true;
|
|
1041
2070
|
const draftFailure = keyFailure(keyDraft);
|
|
1042
2071
|
const keyValue = keyDraft.trim();
|
|
1043
|
-
const statusText = keyLocked ? "由环境变量提供(只读)" : configured ? `已保存${credential?.source ? `(来源:${credential.source})` : ""}` : "未配置";
|
|
1044
|
-
const
|
|
1045
|
-
const placeholder = keyLocked ? "由环境变量提供,无法在界面修改" : configured ? "已保存,输入新密钥可覆盖" : "粘贴知乎开放平台 Access Secret";
|
|
2072
|
+
const statusText = keyLocked ? t("由环境变量提供(只读)", "Provided by an environment variable (read-only)") : configured ? t(`已保存${credential?.source ? `(来源:${credential.source})` : ""}`, `Saved${credential?.source ? ` (source: ${credential.source})` : ""}`) : t("未配置", "Not set");
|
|
2073
|
+
const dotState = keyLocked ? "warning" : configured ? "done" : "idle";
|
|
2074
|
+
const placeholder = keyLocked ? t("由环境变量提供,无法在界面修改", "Set by an environment variable; cannot be edited here") : configured ? t("已保存,输入新密钥可覆盖", "Saved. Enter a new key to replace it") : t("粘贴知乎开放平台 Access Secret", "Paste your Zhihu Open Platform Access Secret");
|
|
1046
2075
|
const save = async () => {
|
|
1047
2076
|
if (keyLocked || draftFailure !== void 0 || keyValue.length === 0 || busy) return;
|
|
1048
|
-
|
|
2077
|
+
setPending("save");
|
|
1049
2078
|
setFailure(void 0);
|
|
2079
|
+
setNote(void 0);
|
|
1050
2080
|
try {
|
|
1051
2081
|
const result = await credentials.set({
|
|
1052
2082
|
ref: ZHIHU_CREDENTIAL_REF,
|
|
@@ -1058,19 +2088,21 @@ function SettingsSection(props) {
|
|
|
1058
2088
|
return;
|
|
1059
2089
|
}
|
|
1060
2090
|
setKeyDraft("");
|
|
1061
|
-
setNote("已保存。");
|
|
2091
|
+
setNote(t("已保存。", "Saved."));
|
|
1062
2092
|
await load();
|
|
1063
2093
|
} catch (cause) {
|
|
1064
2094
|
if (!alive.current) return;
|
|
1065
2095
|
setFailure(cause instanceof Error ? cause.message : String(cause));
|
|
1066
2096
|
} finally {
|
|
1067
|
-
if (alive.current)
|
|
2097
|
+
if (alive.current) setPending(null);
|
|
1068
2098
|
}
|
|
1069
2099
|
};
|
|
1070
2100
|
const clear = async () => {
|
|
2101
|
+
setConfirmClear(false);
|
|
1071
2102
|
if (keyLocked || !configured || busy) return;
|
|
1072
|
-
|
|
2103
|
+
setPending("clear");
|
|
1073
2104
|
setFailure(void 0);
|
|
2105
|
+
setNote(void 0);
|
|
1074
2106
|
try {
|
|
1075
2107
|
const result = await credentials.unset({ ref: ZHIHU_CREDENTIAL_REF });
|
|
1076
2108
|
if (!alive.current) return;
|
|
@@ -1079,51 +2111,35 @@ function SettingsSection(props) {
|
|
|
1079
2111
|
return;
|
|
1080
2112
|
}
|
|
1081
2113
|
setKeyDraft("");
|
|
1082
|
-
setNote("已清除。");
|
|
2114
|
+
setNote(t("已清除。", "Cleared."));
|
|
1083
2115
|
await load();
|
|
1084
2116
|
} catch (cause) {
|
|
1085
2117
|
if (!alive.current) return;
|
|
1086
2118
|
setFailure(cause instanceof Error ? cause.message : String(cause));
|
|
1087
2119
|
} finally {
|
|
1088
|
-
if (alive.current)
|
|
2120
|
+
if (alive.current) setPending(null);
|
|
1089
2121
|
}
|
|
1090
2122
|
};
|
|
1091
2123
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsShell, { children: [
|
|
1092
2124
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
1093
|
-
className: "
|
|
1094
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
1095
|
-
className: "
|
|
1096
|
-
children:
|
|
1097
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", {
|
|
1098
|
-
className: "zhihu-status-value",
|
|
1099
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1100
|
-
className: dotClass,
|
|
1101
|
-
"aria-hidden": true
|
|
1102
|
-
}), statusText]
|
|
2125
|
+
className: "dsh-ui-readonly",
|
|
2126
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("状态", "Status") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", {
|
|
2127
|
+
className: "dsh-ui-row",
|
|
2128
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: dotState }), statusText]
|
|
1103
2129
|
})]
|
|
1104
2130
|
}),
|
|
1105
2131
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1106
|
-
className: "
|
|
2132
|
+
className: "dsh-ui-field",
|
|
1107
2133
|
children: [
|
|
1108
2134
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1109
|
-
className: "
|
|
2135
|
+
className: "dsh-ui-label-row",
|
|
1110
2136
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1111
|
-
className: "
|
|
2137
|
+
className: "dsh-ui-label",
|
|
1112
2138
|
id: "zhihu-access-secret-label",
|
|
1113
2139
|
children: "Access Secret"
|
|
1114
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
target: "_blank",
|
|
1118
|
-
rel: "noreferrer noopener",
|
|
1119
|
-
onClick: (event) => {
|
|
1120
|
-
const bridge = globalThis.dshWindow;
|
|
1121
|
-
if (bridge?.openExternal) {
|
|
1122
|
-
event.preventDefault();
|
|
1123
|
-
bridge.openExternal(ZHIHU_CONSOLE_URL);
|
|
1124
|
-
}
|
|
1125
|
-
},
|
|
1126
|
-
children: "获取密钥"
|
|
2140
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExternalLink, {
|
|
2141
|
+
url: ZHIHU_CONSOLE_URL,
|
|
2142
|
+
children: t("获取密钥", "Get a key")
|
|
1127
2143
|
})]
|
|
1128
2144
|
}),
|
|
1129
2145
|
renderInput(props.Input, {
|
|
@@ -1133,42 +2149,66 @@ function SettingsSection(props) {
|
|
|
1133
2149
|
placeholder,
|
|
1134
2150
|
disabled: busy || keyLocked,
|
|
1135
2151
|
"aria-label": "Access Secret",
|
|
1136
|
-
onChange:
|
|
2152
|
+
onChange: editDraft
|
|
1137
2153
|
}),
|
|
1138
2154
|
draftFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1139
|
-
className: "
|
|
2155
|
+
className: "dsh-ui-error",
|
|
1140
2156
|
role: "alert",
|
|
1141
|
-
children: draftFailure === "blank" ? "密钥不能只包含空白字符。" : "
|
|
2157
|
+
children: draftFailure === "blank" ? t("密钥不能只包含空白字符。", "The key cannot be only whitespace.") : t("密钥含非法字符(应为可打印 ASCII,且不是 ENV 赋值行)。", "The key has invalid characters (use printable ASCII, not an ENV assignment line).")
|
|
1142
2158
|
})
|
|
1143
2159
|
]
|
|
1144
2160
|
}),
|
|
1145
2161
|
failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1146
|
-
className: "
|
|
2162
|
+
className: "dsh-ui-error",
|
|
1147
2163
|
role: "alert",
|
|
1148
2164
|
children: failure
|
|
1149
2165
|
}) : null,
|
|
1150
2166
|
note !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1151
|
-
className: "
|
|
2167
|
+
className: "dsh-ui-notice",
|
|
1152
2168
|
role: "status",
|
|
1153
2169
|
children: note
|
|
1154
2170
|
}) : null,
|
|
1155
2171
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1156
|
-
className: "
|
|
2172
|
+
className: "dsh-ui-actions",
|
|
1157
2173
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1158
2174
|
host: props.Button,
|
|
1159
2175
|
variant: "danger",
|
|
1160
|
-
className: "zhihu-button
|
|
2176
|
+
className: "zhihu-button-danger",
|
|
1161
2177
|
disabled: busy || keyLocked || !configured,
|
|
1162
|
-
onClick: () =>
|
|
1163
|
-
children:
|
|
2178
|
+
onClick: () => setConfirmClear(true),
|
|
2179
|
+
children: pending === "clear" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), t("清除中…", "Clearing…")] }) : t("清除", "Clear")
|
|
1164
2180
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1165
2181
|
host: props.Button,
|
|
1166
2182
|
variant: "primary",
|
|
1167
|
-
className: "zhihu-button zhihu-button-primary",
|
|
1168
2183
|
disabled: busy || keyLocked || keyValue.length === 0 || draftFailure !== void 0,
|
|
1169
2184
|
onClick: () => void save(),
|
|
1170
|
-
children:
|
|
2185
|
+
children: pending === "save" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), t("保存中…", "Saving…")] }) : t("保存", "Save")
|
|
1171
2186
|
})]
|
|
2187
|
+
}),
|
|
2188
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2189
|
+
open: confirmClear,
|
|
2190
|
+
onClose: () => setConfirmClear(false),
|
|
2191
|
+
title: t("清除 Access Secret?", "Clear Access Secret?"),
|
|
2192
|
+
closeLabel: t("关闭", "Close"),
|
|
2193
|
+
className: "zhihu-panel",
|
|
2194
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2195
|
+
className: "dsh-ui-actions",
|
|
2196
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
2197
|
+
host: props.Button,
|
|
2198
|
+
onClick: () => setConfirmClear(false),
|
|
2199
|
+
children: t("取消", "Cancel")
|
|
2200
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
2201
|
+
host: props.Button,
|
|
2202
|
+
variant: "danger",
|
|
2203
|
+
className: "zhihu-button-danger",
|
|
2204
|
+
onClick: () => void clear(),
|
|
2205
|
+
children: t("清除", "Clear")
|
|
2206
|
+
})]
|
|
2207
|
+
}),
|
|
2208
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2209
|
+
className: "dsh-ui-help",
|
|
2210
|
+
children: t("清除后,搜索、工具和知识库请求会失败,直到重新保存密钥。", "Search, tools and knowledge base requests will fail until you save a key again.")
|
|
2211
|
+
})
|
|
1172
2212
|
})
|
|
1173
2213
|
] });
|
|
1174
2214
|
}
|
|
@@ -1259,7 +2299,26 @@ const CHART_PAD = {
|
|
|
1259
2299
|
bottom: 24,
|
|
1260
2300
|
left: 36
|
|
1261
2301
|
};
|
|
2302
|
+
/** Indexes whose bar prints its count: the first maximum and the latest active day. */
|
|
2303
|
+
function usageValueLabels(days) {
|
|
2304
|
+
const chosen = /* @__PURE__ */ new Set();
|
|
2305
|
+
let max = -1;
|
|
2306
|
+
let maxIndex = -1;
|
|
2307
|
+
let latest = -1;
|
|
2308
|
+
days.forEach((day, index) => {
|
|
2309
|
+
if (day.calls <= 0) return;
|
|
2310
|
+
if (day.calls > max) {
|
|
2311
|
+
max = day.calls;
|
|
2312
|
+
maxIndex = index;
|
|
2313
|
+
}
|
|
2314
|
+
latest = index;
|
|
2315
|
+
});
|
|
2316
|
+
if (maxIndex >= 0) chosen.add(maxIndex);
|
|
2317
|
+
if (latest >= 0) chosen.add(latest);
|
|
2318
|
+
return chosen;
|
|
2319
|
+
}
|
|
1262
2320
|
function UsageChart(props) {
|
|
2321
|
+
const t = useT();
|
|
1263
2322
|
const days = props.days;
|
|
1264
2323
|
const maxCalls = Math.max(1, ...days.map((day) => day.calls));
|
|
1265
2324
|
const plotLeft = CHART_PAD.left;
|
|
@@ -1287,12 +2346,13 @@ function UsageChart(props) {
|
|
|
1287
2346
|
children: String(value)
|
|
1288
2347
|
})] }, `y-${value}`);
|
|
1289
2348
|
});
|
|
2349
|
+
const labelled = usageValueLabels(days);
|
|
1290
2350
|
const bars = days.map((day, index) => {
|
|
1291
2351
|
const x = plotLeft + index * slot + (slot - barWidth) / 2;
|
|
1292
2352
|
const okCalls = Math.max(0, day.calls - day.failures);
|
|
1293
2353
|
const failHeight = day.failures / maxCalls * plotHeight;
|
|
1294
2354
|
const okHeight = okCalls / maxCalls * plotHeight;
|
|
1295
|
-
const label = `${day.date}:调用 ${day.calls} 次,成功 ${okCalls} 次,失败 ${day.failures} 次,结果 ${day.results}
|
|
2355
|
+
const label = t(`${day.date}:调用 ${day.calls} 次,成功 ${okCalls} 次,失败 ${day.failures} 次,结果 ${day.results} 条`, `${day.date}: ${day.calls} calls, ${okCalls} succeeded, ${day.failures} failed, ${day.results} results`);
|
|
1296
2356
|
const parts = [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1297
2357
|
className: "zhihu-chart-hit",
|
|
1298
2358
|
x: plotLeft + index * slot,
|
|
@@ -1314,7 +2374,7 @@ function UsageChart(props) {
|
|
|
1314
2374
|
width: barWidth,
|
|
1315
2375
|
height: failHeight
|
|
1316
2376
|
}, "fail"));
|
|
1317
|
-
if (day.calls > 0) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
2377
|
+
if (day.calls > 0 && labelled.has(index)) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
1318
2378
|
className: "zhihu-chart-value",
|
|
1319
2379
|
x: x + barWidth / 2,
|
|
1320
2380
|
y: plotBottom - okHeight - failHeight - 4,
|
|
@@ -1341,7 +2401,7 @@ function UsageChart(props) {
|
|
|
1341
2401
|
className: "zhihu-chart",
|
|
1342
2402
|
viewBox: `0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`,
|
|
1343
2403
|
role: "img",
|
|
1344
|
-
"aria-label": `近 ${USAGE_DAYS} 天知乎工具调用 ${props.totals.calls} 次,失败 ${props.totals.failures} 次,结果 ${props.totals.results} 条`,
|
|
2404
|
+
"aria-label": t(`近 ${USAGE_DAYS} 天知乎工具调用 ${props.totals.calls} 次,失败 ${props.totals.failures} 次,结果 ${props.totals.results} 条`, `Last ${USAGE_DAYS} days: ${props.totals.calls} Zhihu calls, ${props.totals.failures} failed, ${props.totals.results} results`),
|
|
1345
2405
|
children: [
|
|
1346
2406
|
grid,
|
|
1347
2407
|
bars,
|
|
@@ -1351,6 +2411,7 @@ function UsageChart(props) {
|
|
|
1351
2411
|
}
|
|
1352
2412
|
function UsageSection(props) {
|
|
1353
2413
|
const { rpc } = props;
|
|
2414
|
+
const t = useT();
|
|
1354
2415
|
const gateRef = (0, react.useRef)(null);
|
|
1355
2416
|
if (!gateRef.current) gateRef.current = createZhihuClientState();
|
|
1356
2417
|
const gate = gateRef.current;
|
|
@@ -1373,7 +2434,7 @@ function UsageSection(props) {
|
|
|
1373
2434
|
if (!isUsageSummary(result.value)) {
|
|
1374
2435
|
setState({
|
|
1375
2436
|
status: "error",
|
|
1376
|
-
error: "
|
|
2437
|
+
error: "invalid response shape"
|
|
1377
2438
|
});
|
|
1378
2439
|
return;
|
|
1379
2440
|
}
|
|
@@ -1392,87 +2453,106 @@ function UsageSection(props) {
|
|
|
1392
2453
|
(0, react.useEffect)(() => {
|
|
1393
2454
|
load();
|
|
1394
2455
|
}, [load]);
|
|
2456
|
+
const sectionLabel = t("知乎调用用量", "Zhihu call usage");
|
|
1395
2457
|
if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
1396
2458
|
"data-testid": "zhihu-usage",
|
|
1397
|
-
"aria-label":
|
|
2459
|
+
"aria-label": sectionLabel,
|
|
1398
2460
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1399
|
-
className: "
|
|
2461
|
+
className: "dsh-ui-loading",
|
|
1400
2462
|
role: "status",
|
|
1401
|
-
children: [activityDots(), "正在读取用量…"]
|
|
2463
|
+
children: [activityDots(), t("正在读取用量…", "Loading usage…")]
|
|
1402
2464
|
})
|
|
1403
2465
|
});
|
|
1404
|
-
if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
2466
|
+
if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2467
|
+
className: "dsh-ui-stack",
|
|
1405
2468
|
"data-testid": "zhihu-usage",
|
|
1406
|
-
"aria-label":
|
|
1407
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1408
|
-
className: "
|
|
2469
|
+
"aria-label": sectionLabel,
|
|
2470
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2471
|
+
className: "dsh-ui-error",
|
|
1409
2472
|
role: "alert",
|
|
1410
|
-
children:
|
|
2473
|
+
children: t(`读取失败:${state.error}`, `Could not load: ${state.error}`)
|
|
2474
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2475
|
+
className: "dsh-ui-actions",
|
|
2476
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1411
2477
|
host: props.Button,
|
|
1412
|
-
className: "zhihu-button",
|
|
1413
2478
|
onClick: () => void load(),
|
|
1414
|
-
children: "重试"
|
|
1415
|
-
})
|
|
1416
|
-
})
|
|
2479
|
+
children: t("重试", "Retry")
|
|
2480
|
+
})
|
|
2481
|
+
})]
|
|
1417
2482
|
});
|
|
1418
2483
|
const totals = summarizeUsage(state.days, localDayKey());
|
|
1419
2484
|
const hasAny = totals.calls > 0;
|
|
2485
|
+
const times = t("次", "");
|
|
1420
2486
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1421
|
-
className: "
|
|
2487
|
+
className: "dsh-ui-stack",
|
|
1422
2488
|
"data-testid": "zhihu-usage",
|
|
1423
|
-
"aria-label":
|
|
2489
|
+
"aria-label": sectionLabel,
|
|
1424
2490
|
children: [
|
|
1425
2491
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1426
|
-
className: "
|
|
1427
|
-
children: "
|
|
2492
|
+
className: "dsh-ui-help",
|
|
2493
|
+
children: t("本地调用计数,非知乎官方额度或费用。", "Calls recorded locally; not Zhihu official quota or billing.")
|
|
2494
|
+
}),
|
|
2495
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2496
|
+
className: "dsh-ui-actions",
|
|
2497
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
2498
|
+
host: props.Button,
|
|
2499
|
+
onClick: () => void load(),
|
|
2500
|
+
children: t("刷新统计", "Refresh")
|
|
2501
|
+
})
|
|
1428
2502
|
}),
|
|
1429
2503
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1430
2504
|
className: "zhihu-usage-cards",
|
|
1431
2505
|
children: [
|
|
1432
2506
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
|
|
1433
|
-
label: "今日调用",
|
|
2507
|
+
label: t("今日调用", "Today"),
|
|
1434
2508
|
value: totals.todayCalls,
|
|
1435
|
-
unit:
|
|
2509
|
+
unit: times
|
|
1436
2510
|
}),
|
|
1437
2511
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
|
|
1438
|
-
label: `近 ${USAGE_DAYS} 天`,
|
|
2512
|
+
label: t(`近 ${USAGE_DAYS} 天`, `Last ${USAGE_DAYS} days`),
|
|
1439
2513
|
value: totals.calls,
|
|
1440
|
-
unit:
|
|
2514
|
+
unit: times
|
|
1441
2515
|
}),
|
|
1442
2516
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
|
|
1443
|
-
label: "失败",
|
|
2517
|
+
label: t("失败", "Failed"),
|
|
1444
2518
|
value: totals.failures,
|
|
1445
|
-
unit:
|
|
2519
|
+
unit: times
|
|
1446
2520
|
}),
|
|
1447
2521
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
|
|
1448
|
-
label: "结果条数",
|
|
2522
|
+
label: t("结果条数", "Results"),
|
|
1449
2523
|
value: totals.results,
|
|
1450
|
-
unit: "条"
|
|
2524
|
+
unit: t("条", "")
|
|
1451
2525
|
})
|
|
1452
2526
|
]
|
|
1453
2527
|
}),
|
|
1454
2528
|
hasAny ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [
|
|
1455
2529
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1456
|
-
className: "
|
|
1457
|
-
children: "每日调用次数"
|
|
2530
|
+
className: "dsh-ui-heading",
|
|
2531
|
+
children: t("每日调用次数", "Daily calls")
|
|
1458
2532
|
}),
|
|
1459
2533
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageChart, {
|
|
1460
2534
|
days: state.days,
|
|
1461
2535
|
totals
|
|
1462
2536
|
}),
|
|
1463
2537
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
1464
|
-
className: "
|
|
1465
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1466
|
-
className: "
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
"
|
|
1471
|
-
}), "
|
|
2538
|
+
className: "dsh-ui-row-wrap dsh-ui-meta",
|
|
2539
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2540
|
+
className: "dsh-ui-row",
|
|
2541
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2542
|
+
className: "zhihu-chart-chip zhihu-chart-chip-ok",
|
|
2543
|
+
"aria-hidden": "true"
|
|
2544
|
+
}), t("成功", "Succeeded")]
|
|
2545
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2546
|
+
className: "dsh-ui-row",
|
|
2547
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2548
|
+
className: "zhihu-chart-chip zhihu-chart-chip-fail",
|
|
2549
|
+
"aria-hidden": "true"
|
|
2550
|
+
}), t("失败", "Failed")]
|
|
2551
|
+
})]
|
|
1472
2552
|
}),
|
|
1473
2553
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1474
|
-
className: "
|
|
1475
|
-
children: "有记录的日期"
|
|
2554
|
+
className: "dsh-ui-heading",
|
|
2555
|
+
children: t("有记录的日期", "Days with calls")
|
|
1476
2556
|
}),
|
|
1477
2557
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1478
2558
|
className: "zhihu-usage-table-wrap",
|
|
@@ -1481,23 +2561,23 @@ function UsageSection(props) {
|
|
|
1481
2561
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1482
2562
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1483
2563
|
scope: "col",
|
|
1484
|
-
children: "日期"
|
|
2564
|
+
children: t("日期", "Date")
|
|
1485
2565
|
}),
|
|
1486
2566
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1487
2567
|
scope: "col",
|
|
1488
|
-
children: "调用"
|
|
2568
|
+
children: t("调用", "Calls")
|
|
1489
2569
|
}),
|
|
1490
2570
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1491
2571
|
scope: "col",
|
|
1492
|
-
children: "成功"
|
|
2572
|
+
children: t("成功", "Succeeded")
|
|
1493
2573
|
}),
|
|
1494
2574
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1495
2575
|
scope: "col",
|
|
1496
|
-
children: "失败"
|
|
2576
|
+
children: t("失败", "Failed")
|
|
1497
2577
|
}),
|
|
1498
2578
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1499
2579
|
scope: "col",
|
|
1500
|
-
children: "结果条数"
|
|
2580
|
+
children: t("结果条数", "Results")
|
|
1501
2581
|
})
|
|
1502
2582
|
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: totals.active.map((day) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1503
2583
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
@@ -1512,22 +2592,22 @@ function UsageSection(props) {
|
|
|
1512
2592
|
})
|
|
1513
2593
|
})
|
|
1514
2594
|
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1515
|
-
className: "
|
|
1516
|
-
children:
|
|
2595
|
+
className: "dsh-ui-empty",
|
|
2596
|
+
children: t(`近 ${USAGE_DAYS} 天暂无调用记录。`, `No calls in the last ${USAGE_DAYS} days.`)
|
|
1517
2597
|
})
|
|
1518
2598
|
]
|
|
1519
2599
|
});
|
|
1520
2600
|
}
|
|
1521
2601
|
function UsageCard(props) {
|
|
1522
2602
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1523
|
-
className: "
|
|
2603
|
+
className: "dsh-ui-card dsh-ui-card--flat dsh-ui-list-row",
|
|
1524
2604
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1525
|
-
className: "
|
|
2605
|
+
className: "dsh-ui-meta",
|
|
1526
2606
|
children: props.label
|
|
1527
2607
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1528
|
-
className: "zhihu-usage-
|
|
2608
|
+
className: "dsh-ui-heading zhihu-usage-value",
|
|
1529
2609
|
children: [String(props.value), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1530
|
-
className: "
|
|
2610
|
+
className: "dsh-ui-meta",
|
|
1531
2611
|
children: props.unit
|
|
1532
2612
|
})]
|
|
1533
2613
|
})]
|
|
@@ -1554,6 +2634,7 @@ function formatSize(size) {
|
|
|
1554
2634
|
}
|
|
1555
2635
|
function KnowledgeSection(props) {
|
|
1556
2636
|
const { rpc } = props;
|
|
2637
|
+
const t = useT();
|
|
1557
2638
|
const gateRef = (0, react.useRef)(null);
|
|
1558
2639
|
if (!gateRef.current) gateRef.current = createZhihuClientState();
|
|
1559
2640
|
const gate = gateRef.current;
|
|
@@ -1561,7 +2642,6 @@ function KnowledgeSection(props) {
|
|
|
1561
2642
|
const [baseId, setBaseId] = (0, react.useState)("");
|
|
1562
2643
|
const [file, setFile] = (0, react.useState)(void 0);
|
|
1563
2644
|
const [fileKey, setFileKey] = (0, react.useState)(0);
|
|
1564
|
-
const fileRef = (0, react.useRef)(null);
|
|
1565
2645
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1566
2646
|
const [note, setNote] = (0, react.useState)(void 0);
|
|
1567
2647
|
const [failure, setFailure] = (0, react.useState)(void 0);
|
|
@@ -1576,7 +2656,7 @@ function KnowledgeSection(props) {
|
|
|
1576
2656
|
if (!result.ok) {
|
|
1577
2657
|
setList({
|
|
1578
2658
|
status: "error",
|
|
1579
|
-
failure: failureOf(result.error.code, result.error.message)
|
|
2659
|
+
failure: failureOf(result.error.code, result.error.message, t)
|
|
1580
2660
|
});
|
|
1581
2661
|
return;
|
|
1582
2662
|
}
|
|
@@ -1585,7 +2665,7 @@ function KnowledgeSection(props) {
|
|
|
1585
2665
|
status: "error",
|
|
1586
2666
|
failure: {
|
|
1587
2667
|
kind: "request",
|
|
1588
|
-
text: "响应格式与契约不符。"
|
|
2668
|
+
text: t("响应格式与契约不符。", "The response does not match the expected shape.")
|
|
1589
2669
|
}
|
|
1590
2670
|
});
|
|
1591
2671
|
return;
|
|
@@ -1598,10 +2678,14 @@ function KnowledgeSection(props) {
|
|
|
1598
2678
|
if (!gate.isCurrent(ticket)) return;
|
|
1599
2679
|
setList({
|
|
1600
2680
|
status: "error",
|
|
1601
|
-
failure: networkFailure(cause)
|
|
2681
|
+
failure: networkFailure(cause, t)
|
|
1602
2682
|
});
|
|
1603
2683
|
}
|
|
1604
|
-
}, [
|
|
2684
|
+
}, [
|
|
2685
|
+
gate,
|
|
2686
|
+
rpc,
|
|
2687
|
+
t
|
|
2688
|
+
]);
|
|
1605
2689
|
(0, react.useEffect)(() => {
|
|
1606
2690
|
load();
|
|
1607
2691
|
}, [load]);
|
|
@@ -1610,7 +2694,7 @@ function KnowledgeSection(props) {
|
|
|
1610
2694
|
setFailure(void 0);
|
|
1611
2695
|
setNote(void 0);
|
|
1612
2696
|
if (file.size > KB_MAX_BYTES) {
|
|
1613
|
-
setFailure("文件超过 20 MB 上限。");
|
|
2697
|
+
setFailure(t("文件超过 20 MB 上限。", "The file exceeds the 20 MB limit."));
|
|
1614
2698
|
return;
|
|
1615
2699
|
}
|
|
1616
2700
|
const { ticket, signal } = gate.begin();
|
|
@@ -1626,161 +2710,183 @@ function KnowledgeSection(props) {
|
|
|
1626
2710
|
if (!gate.isCurrent(ticket)) return;
|
|
1627
2711
|
const result = raw;
|
|
1628
2712
|
if (!result.ok) {
|
|
1629
|
-
setFailure(`上传失败:${result.error.message}`);
|
|
2713
|
+
setFailure(t(`上传失败:${result.error.message}`, `Upload failed: ${result.error.message}`));
|
|
1630
2714
|
return;
|
|
1631
2715
|
}
|
|
1632
|
-
setNote("已上传到知乎知识库。");
|
|
2716
|
+
setNote(t("已上传到知乎知识库。", "Uploaded to the Zhihu knowledge base."));
|
|
1633
2717
|
setFile(void 0);
|
|
1634
2718
|
setFileKey((key) => key + 1);
|
|
1635
2719
|
await load();
|
|
1636
2720
|
} catch (cause) {
|
|
1637
2721
|
if (!gate.isCurrent(ticket)) return;
|
|
1638
|
-
|
|
2722
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
2723
|
+
setFailure(t(`上传失败:${detail}`, `Upload failed: ${detail}`));
|
|
1639
2724
|
} finally {
|
|
1640
2725
|
setBusy(false);
|
|
1641
2726
|
}
|
|
1642
2727
|
};
|
|
1643
2728
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1644
|
-
className: "
|
|
2729
|
+
className: "dsh-ui-stack",
|
|
1645
2730
|
"data-testid": "zhihu-knowledge",
|
|
1646
|
-
"aria-label": "知乎知识库",
|
|
2731
|
+
"aria-label": t("知乎知识库", "Zhihu knowledge base"),
|
|
1647
2732
|
children: [
|
|
1648
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1649
|
-
className: "
|
|
1650
|
-
children:
|
|
2733
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2734
|
+
className: "dsh-ui-help",
|
|
2735
|
+
children: [
|
|
2736
|
+
t("上传参考资料供检索;文件进入知乎云端,勿上传未发表手稿。", "Upload references for retrieval. Files go to Zhihu cloud, so avoid unpublished drafts."),
|
|
2737
|
+
" ",
|
|
2738
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExternalLink, {
|
|
2739
|
+
url: KB_MANAGE_URL,
|
|
2740
|
+
children: t("管理知识库", "Manage knowledge bases")
|
|
2741
|
+
})
|
|
2742
|
+
]
|
|
1651
2743
|
}),
|
|
1652
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1653
|
-
className: "zhihu-link",
|
|
1654
|
-
href: KB_MANAGE_URL,
|
|
1655
|
-
target: "_blank",
|
|
1656
|
-
rel: "noreferrer",
|
|
1657
|
-
children: "管理知识库"
|
|
1658
|
-
}) }),
|
|
1659
2744
|
list.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1660
|
-
className: "
|
|
2745
|
+
className: "dsh-ui-loading",
|
|
1661
2746
|
role: "status",
|
|
1662
|
-
children: [activityDots(), "正在读取知识库列表…"]
|
|
2747
|
+
children: [activityDots(), t("正在读取知识库列表…", "Loading knowledge bases…")]
|
|
1663
2748
|
}) : null,
|
|
1664
|
-
list.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1665
|
-
className: "
|
|
2749
|
+
list.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2750
|
+
className: "dsh-ui-error",
|
|
1666
2751
|
role: "alert",
|
|
1667
|
-
children:
|
|
2752
|
+
children: list.failure.text
|
|
2753
|
+
}), list.failure.kind !== "credential" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2754
|
+
className: "dsh-ui-actions",
|
|
2755
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1668
2756
|
host: props.Button,
|
|
1669
|
-
className: "zhihu-button",
|
|
1670
2757
|
onClick: () => void load(),
|
|
1671
|
-
children: "重试"
|
|
1672
|
-
})
|
|
1673
|
-
}) : null,
|
|
2758
|
+
children: t("重试", "Retry")
|
|
2759
|
+
})
|
|
2760
|
+
}) : null] }) : null,
|
|
1674
2761
|
list.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1675
|
-
className: "
|
|
2762
|
+
className: "dsh-ui-field",
|
|
1676
2763
|
children: [
|
|
1677
2764
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1678
|
-
className: "
|
|
2765
|
+
className: "dsh-ui-label",
|
|
1679
2766
|
htmlFor: "zhihu-kb-base",
|
|
1680
|
-
children: "目标知识库"
|
|
2767
|
+
children: t("目标知识库", "Target knowledge base")
|
|
1681
2768
|
}),
|
|
1682
2769
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1683
|
-
className: "
|
|
2770
|
+
className: "dsh-ui-row",
|
|
1684
2771
|
children: [renderSelect(props.Select, {
|
|
2772
|
+
id: "zhihu-kb-base",
|
|
1685
2773
|
value: baseId,
|
|
1686
2774
|
disabled: busy,
|
|
1687
|
-
"aria-label": "目标知识库",
|
|
2775
|
+
"aria-label": t("目标知识库", "Target knowledge base"),
|
|
1688
2776
|
onChange: setBaseId,
|
|
1689
2777
|
options: [{
|
|
1690
2778
|
value: "",
|
|
1691
|
-
label: "默认知识库"
|
|
2779
|
+
label: t("默认知识库", "Default knowledge base")
|
|
1692
2780
|
}, ...list.bases.map((base) => ({
|
|
1693
2781
|
value: base.id,
|
|
1694
|
-
label: `${base.name}${base.isDefault ? "(默认)" : ""} · ${base.contentCount}
|
|
2782
|
+
label: t(`${base.name}${base.isDefault ? "(默认)" : ""} · ${base.contentCount} 篇`, `${base.name}${base.isDefault ? " (default)" : ""} · ${base.contentCount} docs`)
|
|
1695
2783
|
}))]
|
|
1696
|
-
}, "
|
|
2784
|
+
}, "dsh-ui-select"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1697
2785
|
host: props.Button,
|
|
1698
|
-
className: "zhihu-button",
|
|
1699
2786
|
disabled: busy,
|
|
1700
2787
|
onClick: () => void load(),
|
|
1701
|
-
children: "刷新"
|
|
2788
|
+
children: t("刷新", "Refresh")
|
|
1702
2789
|
})]
|
|
1703
2790
|
}),
|
|
1704
2791
|
list.bases.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1705
|
-
className: "
|
|
1706
|
-
children: "
|
|
2792
|
+
className: "dsh-ui-hint",
|
|
2793
|
+
children: t("暂无可用知识库,将上传到默认知识库。", "No knowledge bases yet; uploads go to the default one.")
|
|
1707
2794
|
}) : null
|
|
1708
2795
|
]
|
|
1709
2796
|
}) : null,
|
|
1710
2797
|
list.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1711
|
-
className: "
|
|
2798
|
+
className: "dsh-ui-field",
|
|
1712
2799
|
children: [
|
|
1713
2800
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1714
|
-
className: "
|
|
1715
|
-
|
|
2801
|
+
className: "dsh-ui-label",
|
|
2802
|
+
id: "zhihu-kb-file-label",
|
|
2803
|
+
children: t("参考资料", "Reference file")
|
|
1716
2804
|
}),
|
|
1717
2805
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1718
|
-
ref: fileRef,
|
|
1719
2806
|
id: "zhihu-kb-file",
|
|
1720
2807
|
type: "file",
|
|
1721
2808
|
accept: KB_ACCEPT,
|
|
1722
|
-
hidden: true,
|
|
1723
2809
|
className: "zhihu-file",
|
|
2810
|
+
"aria-labelledby": "zhihu-kb-file-label",
|
|
1724
2811
|
disabled: busy,
|
|
1725
2812
|
onChange: (event) => setFile(event.target.files?.[0])
|
|
1726
2813
|
}, fileKey),
|
|
1727
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
2814
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2815
|
+
className: "dsh-ui-row",
|
|
2816
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
2817
|
+
htmlFor: "zhihu-kb-file",
|
|
2818
|
+
className: "zhihu-file-trigger",
|
|
2819
|
+
"aria-disabled": busy || void 0,
|
|
2820
|
+
children: t("选择文件", "Choose file")
|
|
2821
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2822
|
+
className: "dsh-ui-meta dsh-ui-wrap",
|
|
2823
|
+
children: file ? `${file.name} · ${formatSize(file.size)}` : t("未选择文件(最大 20 MB)", "No file chosen (max 20 MB)")
|
|
2824
|
+
})]
|
|
1733
2825
|
})
|
|
1734
2826
|
]
|
|
1735
2827
|
}) : null,
|
|
1736
2828
|
list.status === "ready" && file ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1737
|
-
className: "
|
|
1738
|
-
children:
|
|
2829
|
+
className: "dsh-ui-banner",
|
|
2830
|
+
children: t(`将「${file.name}」上传到${baseId ? "所选知识库" : "默认知识库"}?文件进入知乎云端。`, `Upload "${file.name}" to the ${baseId ? "selected" : "default"} knowledge base? It goes to Zhihu cloud.`)
|
|
1739
2831
|
}) : null,
|
|
1740
2832
|
list.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1741
|
-
className: "
|
|
2833
|
+
className: "dsh-ui-actions",
|
|
1742
2834
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1743
2835
|
host: props.Button,
|
|
1744
2836
|
variant: "primary",
|
|
1745
|
-
className: "zhihu-button zhihu-button-primary",
|
|
1746
2837
|
disabled: busy || !file,
|
|
1747
2838
|
onClick: () => void upload(),
|
|
1748
|
-
children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), "上传中…"] }) : "确认上传"
|
|
2839
|
+
children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), t("上传中…", "Uploading…")] }) : t("确认上传", "Upload")
|
|
1749
2840
|
})
|
|
1750
2841
|
}) : null,
|
|
1751
2842
|
note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1752
|
-
className: "
|
|
2843
|
+
className: "dsh-ui-notice",
|
|
1753
2844
|
role: "status",
|
|
1754
2845
|
children: note
|
|
1755
2846
|
}) : null,
|
|
1756
2847
|
failure ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1757
|
-
className: "
|
|
2848
|
+
className: "dsh-ui-error",
|
|
1758
2849
|
role: "alert",
|
|
1759
2850
|
children: failure
|
|
1760
2851
|
}) : null
|
|
1761
2852
|
]
|
|
1762
2853
|
});
|
|
1763
2854
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
2855
|
+
function tabLabel(tab, t) {
|
|
2856
|
+
switch (tab) {
|
|
2857
|
+
case "settings": return t("设置", "Settings");
|
|
2858
|
+
case "usage": return t("统计", "Stats");
|
|
2859
|
+
case "quota": return t("官方用量", "Official quota");
|
|
2860
|
+
case "knowledge": return t("知识库", "Knowledge base");
|
|
2861
|
+
case "test": return t("测试", "Test");
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
1770
2864
|
const SETTINGS_TABS = [
|
|
1771
2865
|
"settings",
|
|
1772
2866
|
"usage",
|
|
2867
|
+
"quota",
|
|
1773
2868
|
"knowledge",
|
|
1774
|
-
"
|
|
2869
|
+
"test"
|
|
1775
2870
|
];
|
|
2871
|
+
/**
|
|
2872
|
+
* Tabs that have been opened stay mounted (hidden when inactive), so their
|
|
2873
|
+
* loaded data is cached here instead of being refetched on every switch.
|
|
2874
|
+
*/
|
|
2875
|
+
function nextVisitedTabs(visited, next) {
|
|
2876
|
+
return visited.includes(next) ? [...visited] : [...visited, next];
|
|
2877
|
+
}
|
|
1776
2878
|
function ZhihuSettings(props) {
|
|
1777
2879
|
const { rpc, credentials, Select, Button, Input } = props;
|
|
2880
|
+
const t = useT();
|
|
1778
2881
|
const gateRef = (0, react.useRef)(null);
|
|
1779
2882
|
if (!gateRef.current) gateRef.current = createZhihuClientState();
|
|
1780
2883
|
const gate = gateRef.current;
|
|
1781
2884
|
const [tab, setTab] = (0, react.useState)("settings");
|
|
2885
|
+
const [visited, setVisited] = (0, react.useState)(["settings"]);
|
|
1782
2886
|
const [query, setQuery] = (0, react.useState)("");
|
|
1783
|
-
const [
|
|
2887
|
+
const [testOperation, setTestOperation] = (0, react.useState)("search");
|
|
2888
|
+
const isSearchOperation = MODES.includes(testOperation);
|
|
2889
|
+
const mode = isSearchOperation ? testOperation : "search";
|
|
1784
2890
|
const [askModel, setAskModel] = (0, react.useState)("zhida-thinking-1p5");
|
|
1785
2891
|
const [scopes, setScopes] = (0, react.useState)(["public"]);
|
|
1786
2892
|
const [phase, setPhase] = (0, react.useState)("idle");
|
|
@@ -1788,7 +2894,6 @@ function ZhihuSettings(props) {
|
|
|
1788
2894
|
const [outcome, setOutcome] = (0, react.useState)(null);
|
|
1789
2895
|
const [revision, setRevision] = (0, react.useState)(0);
|
|
1790
2896
|
const [resultRevision, setResultRevision] = (0, react.useState)(0);
|
|
1791
|
-
const queryRef = (0, react.useRef)(null);
|
|
1792
2897
|
(0, react.useEffect)(() => () => gate.cancel(), [gate]);
|
|
1793
2898
|
const runSearch = (0, react.useCallback)(async () => {
|
|
1794
2899
|
if (mode !== "hot" && !query.trim()) return;
|
|
@@ -1817,7 +2922,7 @@ function ZhihuSettings(props) {
|
|
|
1817
2922
|
if (!parsed) {
|
|
1818
2923
|
setFailure({
|
|
1819
2924
|
kind: "request",
|
|
1820
|
-
text: "响应格式与契约不符。"
|
|
2925
|
+
text: t("响应格式与契约不符。", "The response does not match the expected shape.")
|
|
1821
2926
|
});
|
|
1822
2927
|
setPhase("error");
|
|
1823
2928
|
return;
|
|
@@ -1827,12 +2932,12 @@ function ZhihuSettings(props) {
|
|
|
1827
2932
|
setPhase("done");
|
|
1828
2933
|
} else if (response.error.code === "cancelled") setPhase("idle");
|
|
1829
2934
|
else {
|
|
1830
|
-
setFailure(failureOf(response.error.code, response.error.message));
|
|
2935
|
+
setFailure(failureOf(response.error.code, response.error.message, t));
|
|
1831
2936
|
setPhase("error");
|
|
1832
2937
|
}
|
|
1833
2938
|
} catch (cause) {
|
|
1834
2939
|
if (!gate.isCurrent(ticket)) return;
|
|
1835
|
-
setFailure(networkFailure(cause));
|
|
2940
|
+
setFailure(networkFailure(cause, t));
|
|
1836
2941
|
setPhase("error");
|
|
1837
2942
|
}
|
|
1838
2943
|
}, [
|
|
@@ -1841,7 +2946,8 @@ function ZhihuSettings(props) {
|
|
|
1841
2946
|
mode,
|
|
1842
2947
|
query,
|
|
1843
2948
|
askModel,
|
|
1844
|
-
scopes
|
|
2949
|
+
scopes,
|
|
2950
|
+
t
|
|
1845
2951
|
]);
|
|
1846
2952
|
const resetToIdle = () => {
|
|
1847
2953
|
setPhase((current) => current === "loading" || current === "error" ? "idle" : current);
|
|
@@ -1852,25 +2958,26 @@ function ZhihuSettings(props) {
|
|
|
1852
2958
|
setRevision(gate.noteInput());
|
|
1853
2959
|
resetToIdle();
|
|
1854
2960
|
};
|
|
1855
|
-
const
|
|
1856
|
-
if (next ===
|
|
2961
|
+
const onTestOperationChange = (next) => {
|
|
2962
|
+
if (next === testOperation) return;
|
|
1857
2963
|
setRevision(gate.noteInput());
|
|
1858
|
-
|
|
1859
|
-
|
|
2964
|
+
setTestOperation(next);
|
|
2965
|
+
setOutcome(null);
|
|
2966
|
+
setPhase("idle");
|
|
2967
|
+
setFailure(null);
|
|
1860
2968
|
};
|
|
1861
2969
|
const onTabChange = (next) => {
|
|
1862
2970
|
if (next === tab) return;
|
|
1863
2971
|
gate.cancel();
|
|
1864
2972
|
setPhase((current) => current === "loading" ? "idle" : current);
|
|
1865
2973
|
setTab(next);
|
|
2974
|
+
setVisited((current) => nextVisitedTabs(current, next));
|
|
1866
2975
|
};
|
|
1867
2976
|
const toggleScope = (scope) => {
|
|
2977
|
+
if (scopes.length === 1 && scopes[0] === scope) return;
|
|
1868
2978
|
setRevision(gate.noteInput());
|
|
1869
2979
|
resetToIdle();
|
|
1870
|
-
setScopes((current) =>
|
|
1871
|
-
const next = current.includes(scope) ? current.filter((item) => item !== scope) : [...current, scope];
|
|
1872
|
-
return next.length > 0 ? next : current;
|
|
1873
|
-
});
|
|
2980
|
+
setScopes((current) => current.includes(scope) ? current.filter((item) => item !== scope) : [...current, scope]);
|
|
1874
2981
|
};
|
|
1875
2982
|
const onAskModelChange = (next) => {
|
|
1876
2983
|
if (next === askModel) return;
|
|
@@ -1889,150 +2996,154 @@ function ZhihuSettings(props) {
|
|
|
1889
2996
|
const isSearchDisabled = () => phase === "loading" || mode !== "hot" && !query.trim();
|
|
1890
2997
|
const stale = outcome !== null && resultRevision !== revision;
|
|
1891
2998
|
const searchDisabled = isSearchDisabled();
|
|
1892
|
-
const
|
|
2999
|
+
const [firstTab, ...restTabs] = SETTINGS_TABS.map((key) => ({
|
|
3000
|
+
value: key,
|
|
3001
|
+
label: tabLabel(key, t),
|
|
3002
|
+
id: `zhihu-tab-${key}`,
|
|
3003
|
+
panelId: `zhihu-tabpanel-${key}`
|
|
3004
|
+
}));
|
|
3005
|
+
const tablist = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
|
|
3006
|
+
value: tab,
|
|
3007
|
+
onChange: onTabChange,
|
|
3008
|
+
label: t("知乎分区", "Zhihu sections"),
|
|
1893
3009
|
className: "zhihu-tabs",
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
},
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
children:
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
children: phase === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), "请求中…"] }) : mode === "hot" ? "获取热榜" : "搜索"
|
|
1984
|
-
}), phase === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
1985
|
-
host: Button,
|
|
1986
|
-
className: "zhihu-button",
|
|
1987
|
-
onClick: () => {
|
|
1988
|
-
gate.cancel();
|
|
1989
|
-
setPhase("idle");
|
|
1990
|
-
},
|
|
1991
|
-
children: "取消"
|
|
1992
|
-
}) : null]
|
|
1993
|
-
}),
|
|
1994
|
-
phase === "idle" && mode !== "hot" && !query.trim() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1995
|
-
className: "zhihu-status",
|
|
1996
|
-
role: "status",
|
|
1997
|
-
children: "输入关键词后搜索。"
|
|
1998
|
-
}) : null,
|
|
1999
|
-
phase === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2000
|
-
className: "zhihu-status",
|
|
2001
|
-
role: "status",
|
|
2002
|
-
children: [activityDots(), "正在请求知乎…"]
|
|
2003
|
-
}) : null,
|
|
2004
|
-
phase === "error" && failure ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2005
|
-
className: "zhihu-error",
|
|
2006
|
-
role: "alert",
|
|
2007
|
-
children: failure.text
|
|
2008
|
-
}) : null,
|
|
2009
|
-
phase === "done" && outcome ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutcomeView, {
|
|
2010
|
-
outcome,
|
|
2011
|
-
stale
|
|
2012
|
-
}) : null
|
|
2013
|
-
]
|
|
3010
|
+
items: [firstTab, ...restTabs]
|
|
3011
|
+
});
|
|
3012
|
+
const panel = (key, content) => visited.includes(key) || key === tab ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3013
|
+
id: `zhihu-tabpanel-${key}`,
|
|
3014
|
+
role: "tabpanel",
|
|
3015
|
+
"aria-labelledby": `zhihu-tab-${key}`,
|
|
3016
|
+
hidden: key !== tab,
|
|
3017
|
+
className: "dsh-ui-panel",
|
|
3018
|
+
children: content
|
|
3019
|
+
}, key) : null;
|
|
3020
|
+
const testPanel = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3021
|
+
className: "dsh-ui-field",
|
|
3022
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuTestSelector, {
|
|
3023
|
+
value: testOperation,
|
|
3024
|
+
onChange: onTestOperationChange,
|
|
3025
|
+
Select
|
|
3026
|
+
}), isSearchOperation ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [
|
|
3027
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3028
|
+
className: "dsh-ui-row",
|
|
3029
|
+
children: mode === "ask" ? renderSelect(Select, {
|
|
3030
|
+
"aria-label": t("直答模型", "Answer model"),
|
|
3031
|
+
value: askModel,
|
|
3032
|
+
onChange: (next) => onAskModelChange(next),
|
|
3033
|
+
options: ASK_MODELS.map((model) => ({
|
|
3034
|
+
value: model.value,
|
|
3035
|
+
label: t(model.zh, model.en)
|
|
3036
|
+
}))
|
|
3037
|
+
}, "dsh-ui-select") : null
|
|
3038
|
+
}),
|
|
3039
|
+
mode === "knowledge" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3040
|
+
className: "dsh-ui-row-wrap",
|
|
3041
|
+
role: "group",
|
|
3042
|
+
"aria-label": t("检索范围", "Search scope"),
|
|
3043
|
+
"aria-describedby": "zhihu-scope-hint",
|
|
3044
|
+
children: SCOPE_OPTIONS.map((scope) => {
|
|
3045
|
+
const checked = scopes.includes(scope.value);
|
|
3046
|
+
const locked = checked && scopes.length === 1;
|
|
3047
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
|
|
3048
|
+
active: checked,
|
|
3049
|
+
role: "checkbox",
|
|
3050
|
+
"aria-checked": checked,
|
|
3051
|
+
"aria-disabled": locked || void 0,
|
|
3052
|
+
onClick: () => toggleScope(scope.value),
|
|
3053
|
+
children: t(scope.zh, scope.en)
|
|
3054
|
+
}, scope.value);
|
|
3055
|
+
})
|
|
3056
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3057
|
+
className: "dsh-ui-hint",
|
|
3058
|
+
id: "zhihu-scope-hint",
|
|
3059
|
+
children: t("至少保留一个检索范围。", "Keep at least one scope selected.")
|
|
3060
|
+
})] }) : null,
|
|
3061
|
+
renderInput(Input, {
|
|
3062
|
+
type: "search",
|
|
3063
|
+
className: "zhihu-input",
|
|
3064
|
+
"data-testid": "zhihu-query",
|
|
3065
|
+
"aria-label": t("搜索关键词", "Search keywords"),
|
|
3066
|
+
placeholder: mode === "hot" ? t("热榜无需关键词", "Trending needs no keywords") : t("输入关键词…", "Enter keywords…"),
|
|
3067
|
+
value: query,
|
|
3068
|
+
disabled: mode === "hot",
|
|
3069
|
+
onChange: onQueryChange,
|
|
3070
|
+
onKeyDown: onQueryKeyDown
|
|
3071
|
+
}),
|
|
3072
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3073
|
+
className: "dsh-ui-actions",
|
|
3074
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
3075
|
+
host: Button,
|
|
3076
|
+
variant: "primary",
|
|
3077
|
+
"data-testid": "zhihu-search",
|
|
3078
|
+
disabled: searchDisabled,
|
|
3079
|
+
onClick: () => void runSearch(),
|
|
3080
|
+
children: mode === "hot" ? t("获取热榜", "Get trending") : t("搜索", "Search")
|
|
3081
|
+
}), phase === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
|
|
3082
|
+
host: Button,
|
|
3083
|
+
onClick: () => {
|
|
3084
|
+
gate.cancel();
|
|
3085
|
+
setPhase("idle");
|
|
3086
|
+
},
|
|
3087
|
+
children: t("取消", "Cancel")
|
|
3088
|
+
}) : null]
|
|
3089
|
+
}),
|
|
3090
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3091
|
+
className: phase === "loading" ? "dsh-ui-loading" : "dsh-ui-hint",
|
|
3092
|
+
role: "status",
|
|
3093
|
+
children: phase === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), t("正在请求知乎…", "Requesting Zhihu…")] }) : phase === "idle" && mode !== "hot" && !query.trim() ? t("输入关键词后搜索。", "Enter keywords to search.") : null
|
|
3094
|
+
}),
|
|
3095
|
+
phase === "error" && failure ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3096
|
+
className: "dsh-ui-error",
|
|
3097
|
+
role: "alert",
|
|
3098
|
+
children: failure.text
|
|
2014
3099
|
}) : null,
|
|
2015
|
-
|
|
3100
|
+
phase === "done" && outcome ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutcomeView, {
|
|
3101
|
+
outcome,
|
|
3102
|
+
stale
|
|
3103
|
+
}) : null
|
|
3104
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuOpenPlatformSection, {
|
|
3105
|
+
selectedOperation: testOperation,
|
|
3106
|
+
rpc,
|
|
3107
|
+
Button,
|
|
3108
|
+
Input,
|
|
3109
|
+
Select
|
|
3110
|
+
}, testOperation)]
|
|
3111
|
+
});
|
|
3112
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3113
|
+
className: "zhihu-panel zhihu-settings-embed",
|
|
3114
|
+
"data-testid": "zhihu-settings-embed",
|
|
3115
|
+
"aria-label": t("知乎", "Zhihu"),
|
|
3116
|
+
children: [
|
|
3117
|
+
tablist,
|
|
3118
|
+
panel("settings", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsSection, {
|
|
2016
3119
|
credentials,
|
|
2017
3120
|
Button,
|
|
2018
3121
|
Input
|
|
2019
|
-
})
|
|
2020
|
-
|
|
3122
|
+
})),
|
|
3123
|
+
panel("usage", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageSection, {
|
|
2021
3124
|
rpc,
|
|
2022
3125
|
Button
|
|
2023
|
-
})
|
|
2024
|
-
|
|
3126
|
+
})),
|
|
3127
|
+
panel("quota", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuQuotaSection, {
|
|
3128
|
+
rpc,
|
|
3129
|
+
Button
|
|
3130
|
+
})),
|
|
3131
|
+
panel("knowledge", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnowledgeSection, {
|
|
2025
3132
|
rpc,
|
|
2026
3133
|
Select,
|
|
2027
3134
|
Button
|
|
2028
|
-
})
|
|
3135
|
+
})),
|
|
3136
|
+
panel("test", testPanel)
|
|
2029
3137
|
]
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3140
|
+
/** Settings root: resolves the host locale once and hands it to every section. */
|
|
3141
|
+
function ZhihuSettingsRoot(props) {
|
|
3142
|
+
const { locale, ...rest } = props;
|
|
3143
|
+
const active = useHostLocale(locale);
|
|
3144
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuLocaleContext.Provider, {
|
|
3145
|
+
value: active,
|
|
3146
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuSettings, { ...rest })
|
|
2036
3147
|
});
|
|
2037
3148
|
}
|
|
2038
3149
|
function apply(ctx) {
|
|
@@ -2041,9 +3152,16 @@ function apply(ctx) {
|
|
|
2041
3152
|
return () => style?.remove();
|
|
2042
3153
|
}, "zhihu.styles");
|
|
2043
3154
|
const client = ctx;
|
|
2044
|
-
|
|
3155
|
+
let locale;
|
|
3156
|
+
try {
|
|
3157
|
+
locale = client.locale;
|
|
3158
|
+
} catch {
|
|
3159
|
+
locale = void 0;
|
|
3160
|
+
}
|
|
3161
|
+
const settingsRender = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuSettingsRoot, {
|
|
2045
3162
|
rpc: client.connection.rpc,
|
|
2046
3163
|
credentials: wrapCredentials(client.remote.credentials),
|
|
3164
|
+
locale,
|
|
2047
3165
|
...hostComponentsFromRenderProps(props)
|
|
2048
3166
|
});
|
|
2049
3167
|
ctx.effect(() => client.slots.inject("plugins.bundle.config", () => client.slots.register({
|
|
@@ -2054,12 +3172,19 @@ function apply(ctx) {
|
|
|
2054
3172
|
}, settingsRender)), "zhihu.settings");
|
|
2055
3173
|
}
|
|
2056
3174
|
//#endregion
|
|
3175
|
+
exports.CapabilitiesNote = CapabilitiesNote;
|
|
3176
|
+
exports.ExternalLink = ExternalLink;
|
|
3177
|
+
exports.SETTINGS_TABS = SETTINGS_TABS;
|
|
3178
|
+
exports.TEST_OPTIONS = TEST_OPTIONS;
|
|
3179
|
+
exports.ZhihuTestSelector = ZhihuTestSelector;
|
|
2057
3180
|
exports.apply = apply;
|
|
2058
3181
|
exports.formatUsageDate = formatUsageDate;
|
|
2059
3182
|
exports.inject = inject;
|
|
2060
3183
|
exports.name = name;
|
|
3184
|
+
exports.nextVisitedTabs = nextVisitedTabs;
|
|
2061
3185
|
exports.summarizeUsage = summarizeUsage;
|
|
2062
3186
|
exports.usageChartTicks = usageChartTicks;
|
|
3187
|
+
exports.usageValueLabels = usageValueLabels;
|
|
2063
3188
|
|
|
2064
3189
|
//# sourceMappingURL=client.inner.cjs.map
|
|
2065
3190
|
return module.exports;
|