@klarkxy/dsh-current-title 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +42 -0
- package/README.md +27 -0
- package/cordis.patch.yml +5 -0
- package/docs/README.zh-CN.md +13 -0
- package/lib/client.inner.cjs +455 -0
- package/lib/client.inner.cjs.map +1 -0
- package/lib/client.js +625 -0
- package/lib/contracts.d.ts +74 -0
- package/lib/contracts.js +43 -0
- package/lib/contracts.js.map +1 -0
- package/lib/index.d.ts +160 -0
- package/lib/index.js +714 -0
- package/lib/index.js.map +1 -0
- package/package.json +103 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
function __createDshNodeShims() {
|
|
2
|
+
class EventEmitter {
|
|
3
|
+
constructor() {
|
|
4
|
+
this._e = Object.create(null)
|
|
5
|
+
}
|
|
6
|
+
on(type, fn) {
|
|
7
|
+
(this._e[type] ||= []).push(fn)
|
|
8
|
+
return this
|
|
9
|
+
}
|
|
10
|
+
addListener(type, fn) {
|
|
11
|
+
return this.on(type, fn)
|
|
12
|
+
}
|
|
13
|
+
once(type, fn) {
|
|
14
|
+
const wrap = (...args) => {
|
|
15
|
+
this.off(type, wrap)
|
|
16
|
+
fn(...args)
|
|
17
|
+
}
|
|
18
|
+
return this.on(type, wrap)
|
|
19
|
+
}
|
|
20
|
+
off(type, fn) {
|
|
21
|
+
const list = this._e[type]
|
|
22
|
+
if (list) this._e[type] = list.filter((item) => item !== fn)
|
|
23
|
+
return this
|
|
24
|
+
}
|
|
25
|
+
removeListener(type, fn) {
|
|
26
|
+
return this.off(type, fn)
|
|
27
|
+
}
|
|
28
|
+
emit(type, ...args) {
|
|
29
|
+
const list = this._e[type]
|
|
30
|
+
if (!list) return false
|
|
31
|
+
for (const fn of list.slice()) fn(...args)
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
removeAllListeners(type) {
|
|
35
|
+
if (type) delete this._e[type]
|
|
36
|
+
else this._e = Object.create(null)
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
listeners(type) {
|
|
40
|
+
return (this._e[type] || []).slice()
|
|
41
|
+
}
|
|
42
|
+
setMaxListeners() {
|
|
43
|
+
return this
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class Buffer extends Uint8Array {
|
|
48
|
+
static from(value, encodingOrOffset, length) {
|
|
49
|
+
if (typeof value === 'string') return new Buffer(new TextEncoder().encode(value))
|
|
50
|
+
if (value instanceof ArrayBuffer) return new Buffer(new Uint8Array(value, encodingOrOffset, length))
|
|
51
|
+
if (ArrayBuffer.isView(value)) return new Buffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
|
|
52
|
+
if (Array.isArray(value) || value && typeof value.length === 'number') return new Buffer(Uint8Array.from(value))
|
|
53
|
+
return new Buffer(0)
|
|
54
|
+
}
|
|
55
|
+
static alloc(size, fill) {
|
|
56
|
+
const buf = new Buffer(size)
|
|
57
|
+
if (fill !== undefined && fill !== 0) {
|
|
58
|
+
if (typeof fill === 'string') {
|
|
59
|
+
const bytes = new TextEncoder().encode(fill)
|
|
60
|
+
for (let i = 0; i < size; i += 1) buf[i] = bytes[i % bytes.length]
|
|
61
|
+
} else buf.fill(fill)
|
|
62
|
+
}
|
|
63
|
+
return buf
|
|
64
|
+
}
|
|
65
|
+
static allocUnsafe(size) {
|
|
66
|
+
return new Buffer(size)
|
|
67
|
+
}
|
|
68
|
+
static allocUnsafeSlow(size) {
|
|
69
|
+
return new Buffer(size)
|
|
70
|
+
}
|
|
71
|
+
static concat(list, totalLength) {
|
|
72
|
+
const length = totalLength ?? list.reduce((sum, item) => sum + item.length, 0)
|
|
73
|
+
const out = new Buffer(length)
|
|
74
|
+
let offset = 0
|
|
75
|
+
for (const item of list) {
|
|
76
|
+
out.set(item, offset)
|
|
77
|
+
offset += item.length
|
|
78
|
+
}
|
|
79
|
+
return out
|
|
80
|
+
}
|
|
81
|
+
static isBuffer(value) {
|
|
82
|
+
return value instanceof Buffer
|
|
83
|
+
}
|
|
84
|
+
static byteLength(value) {
|
|
85
|
+
if (typeof value === 'string') return new TextEncoder().encode(value).length
|
|
86
|
+
return value?.byteLength ?? value?.length ?? 0
|
|
87
|
+
}
|
|
88
|
+
static isEncoding(encoding) {
|
|
89
|
+
return /^(utf8|utf-8|ascii|latin1|binary|hex|base64|base64url)$/i.test(String(encoding || ''))
|
|
90
|
+
}
|
|
91
|
+
toString(encoding) {
|
|
92
|
+
if (encoding === 'hex') return [...this].map((byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
93
|
+
if (encoding === 'base64') {
|
|
94
|
+
let binary = ''
|
|
95
|
+
this.forEach((byte) => {
|
|
96
|
+
binary += String.fromCharCode(byte)
|
|
97
|
+
})
|
|
98
|
+
return btoa(binary)
|
|
99
|
+
}
|
|
100
|
+
return new TextDecoder().decode(this)
|
|
101
|
+
}
|
|
102
|
+
slice(start, end) {
|
|
103
|
+
return Buffer.from(super.subarray(start, end))
|
|
104
|
+
}
|
|
105
|
+
copy(target, targetStart = 0, start = 0, end = this.length) {
|
|
106
|
+
const sliced = this.subarray(start, end)
|
|
107
|
+
target.set(sliced, targetStart)
|
|
108
|
+
return sliced.length
|
|
109
|
+
}
|
|
110
|
+
write(string, offset = 0) {
|
|
111
|
+
const bytes = new TextEncoder().encode(string)
|
|
112
|
+
const written = Math.min(bytes.length, this.length - offset)
|
|
113
|
+
this.set(bytes.subarray(0, written), offset)
|
|
114
|
+
return written
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function inherits(ctor, superCtor) {
|
|
119
|
+
ctor.super_ = superCtor
|
|
120
|
+
ctor.prototype = Object.create(superCtor.prototype, {
|
|
121
|
+
constructor: { value: ctor, writable: true, configurable: true },
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!globalThis.Buffer) globalThis.Buffer = Buffer
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
buffer: { Buffer, SlowBuffer: Buffer, INSPECT_MAX_BYTES: 50, kMaxLength: 0x7fffffff },
|
|
129
|
+
stream: {
|
|
130
|
+
Stream: EventEmitter,
|
|
131
|
+
Readable: EventEmitter,
|
|
132
|
+
Writable: EventEmitter,
|
|
133
|
+
Duplex: EventEmitter,
|
|
134
|
+
Transform: EventEmitter,
|
|
135
|
+
PassThrough: EventEmitter,
|
|
136
|
+
},
|
|
137
|
+
events: { EventEmitter },
|
|
138
|
+
util: {
|
|
139
|
+
deprecate(fn) {
|
|
140
|
+
return fn
|
|
141
|
+
},
|
|
142
|
+
inherits,
|
|
143
|
+
inspect() {
|
|
144
|
+
return ''
|
|
145
|
+
},
|
|
146
|
+
format(value) {
|
|
147
|
+
return String(value)
|
|
148
|
+
},
|
|
149
|
+
types: { isBuffer: Buffer.isBuffer },
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
window.__ModuleLoader__.load({
|
|
155
|
+
id: "@klarkxy/dsh-current-title",
|
|
156
|
+
factory: (dshRequire) => {
|
|
157
|
+
var __nodeShims;
|
|
158
|
+
var process = globalThis.process || { env: {}, nextTick: function (fn) { var args = [].slice.call(arguments, 1); queueMicrotask(function () { fn.apply(null, args); }); } };
|
|
159
|
+
var require = function (id) {
|
|
160
|
+
if (id === 'buffer' || id === 'stream' || id === 'util' || id === 'events') {
|
|
161
|
+
if (!__nodeShims) __nodeShims = __createDshNodeShims();
|
|
162
|
+
return __nodeShims[id];
|
|
163
|
+
}
|
|
164
|
+
return dshRequire(id);
|
|
165
|
+
};
|
|
166
|
+
var module = { exports: {} };
|
|
167
|
+
var exports = module.exports;
|
|
168
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
169
|
+
let react = require("react");
|
|
170
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
171
|
+
//#region ../dsh-ai-services/src/client-utils.ts
|
|
172
|
+
/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
|
|
173
|
+
function selectedSessionId(client) {
|
|
174
|
+
return client.uiWorkspace.current ? client.uiWorkspace.current.getSnapshot()?.sessionId ?? "" : client.uiSession.adapter.current.getSnapshot().key ?? "";
|
|
175
|
+
}
|
|
176
|
+
/** Owner props win; native settings only supplies close, so resolve its selected session. */
|
|
177
|
+
function useNativeSeat(client, props) {
|
|
178
|
+
const current = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => (client.uiWorkspace.current ?? client.uiSession.adapter.current).subscribe(fn), [client]), (0, react.useCallback)(() => selectedSessionId(client), [client]), () => "");
|
|
179
|
+
const language = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => client.locale.subscribe(fn), [client]), (0, react.useCallback)(() => client.locale.getSnapshot().active, [client]), () => "en");
|
|
180
|
+
const row = props && typeof props === "object" ? props : {};
|
|
181
|
+
const owner = row.owner && typeof row.owner === "object" ? row.owner : {};
|
|
182
|
+
const sessionId = typeof row.sessionId === "string" ? row.sessionId : typeof owner.sessionId === "string" ? owner.sessionId : current;
|
|
183
|
+
const requestedLocale = row.locale ?? owner.locale ?? language;
|
|
184
|
+
return {
|
|
185
|
+
sessionId,
|
|
186
|
+
locale: String(requestedLocale).startsWith("zh") ? "zh" : "en",
|
|
187
|
+
hidden: row.hidden === true || owner.hidden === true
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */
|
|
191
|
+
function useFeatureRefresh(client, sessionId, refresh, working = false, enabled = true) {
|
|
192
|
+
const refreshRef = (0, react.useRef)(refresh);
|
|
193
|
+
refreshRef.current = refresh;
|
|
194
|
+
(0, react.useEffect)(() => {
|
|
195
|
+
if (!enabled) return;
|
|
196
|
+
let timer;
|
|
197
|
+
const queue = () => {
|
|
198
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
timer = setTimeout(() => refreshRef.current(), 150);
|
|
201
|
+
};
|
|
202
|
+
const stopEvents = (sessionId ? client.sessions.binding?.(sessionId)?.eventSource : void 0)?.subscribe(queue);
|
|
203
|
+
const stopConnection = client.connection?.generation?.subscribe(queue);
|
|
204
|
+
const visibility = () => {
|
|
205
|
+
if (document.visibilityState === "visible") queue();
|
|
206
|
+
};
|
|
207
|
+
window.addEventListener("focus", queue);
|
|
208
|
+
document.addEventListener("visibilitychange", visibility);
|
|
209
|
+
document.addEventListener("toggle", queue, true);
|
|
210
|
+
return () => {
|
|
211
|
+
clearTimeout(timer);
|
|
212
|
+
stopEvents?.();
|
|
213
|
+
stopConnection?.();
|
|
214
|
+
window.removeEventListener("focus", queue);
|
|
215
|
+
document.removeEventListener("visibilitychange", visibility);
|
|
216
|
+
document.removeEventListener("toggle", queue, true);
|
|
217
|
+
};
|
|
218
|
+
}, [
|
|
219
|
+
client,
|
|
220
|
+
sessionId,
|
|
221
|
+
enabled
|
|
222
|
+
]);
|
|
223
|
+
(0, react.useEffect)(() => {
|
|
224
|
+
if (!enabled || !working) return;
|
|
225
|
+
let delay = 250;
|
|
226
|
+
const until = Date.now() + 3e5;
|
|
227
|
+
let timer;
|
|
228
|
+
const tick = () => {
|
|
229
|
+
if (Date.now() >= until) return;
|
|
230
|
+
if (document.visibilityState !== "hidden") refreshRef.current();
|
|
231
|
+
delay = Math.min(delay * 2, 2e3);
|
|
232
|
+
timer = setTimeout(tick, delay);
|
|
233
|
+
};
|
|
234
|
+
timer = setTimeout(tick, delay);
|
|
235
|
+
return () => clearTimeout(timer);
|
|
236
|
+
}, [
|
|
237
|
+
client,
|
|
238
|
+
sessionId,
|
|
239
|
+
working,
|
|
240
|
+
enabled
|
|
241
|
+
]);
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/contracts.ts
|
|
245
|
+
const RPC_CHANNEL = "/dsh-current-title";
|
|
246
|
+
const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
247
|
+
//#endregion
|
|
248
|
+
//#region src/client-work.ts
|
|
249
|
+
const titleCopy = {
|
|
250
|
+
zh: {
|
|
251
|
+
label: "当前标题",
|
|
252
|
+
pinned: "已手动命名",
|
|
253
|
+
generating: "正在生成标题…",
|
|
254
|
+
regenerate: "重新生成",
|
|
255
|
+
locale: "类型标签语言",
|
|
256
|
+
auto: "自动",
|
|
257
|
+
saved: "已保存。",
|
|
258
|
+
failed: "无法读取标题设置。"
|
|
259
|
+
},
|
|
260
|
+
en: {
|
|
261
|
+
label: "Current title",
|
|
262
|
+
pinned: "Renamed manually",
|
|
263
|
+
generating: "Generating title…",
|
|
264
|
+
regenerate: "Regenerate",
|
|
265
|
+
locale: "Type-label language",
|
|
266
|
+
auto: "Auto",
|
|
267
|
+
saved: "Saved.",
|
|
268
|
+
failed: "Could not read title settings."
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
function shouldSkipTitleRefresh(input) {
|
|
272
|
+
return input.busy;
|
|
273
|
+
}
|
|
274
|
+
function createTitleClientWork() {
|
|
275
|
+
let generation = 0;
|
|
276
|
+
let request = 0;
|
|
277
|
+
let disposed = false;
|
|
278
|
+
return {
|
|
279
|
+
get disposed() {
|
|
280
|
+
return disposed;
|
|
281
|
+
},
|
|
282
|
+
beginLoad() {
|
|
283
|
+
generation += 1;
|
|
284
|
+
request += 1;
|
|
285
|
+
return generation;
|
|
286
|
+
},
|
|
287
|
+
captureLoad() {
|
|
288
|
+
return generation;
|
|
289
|
+
},
|
|
290
|
+
beginRequest() {
|
|
291
|
+
request += 1;
|
|
292
|
+
return {
|
|
293
|
+
generation,
|
|
294
|
+
request
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
isLoad(generationToken) {
|
|
298
|
+
return !disposed && generation === generationToken;
|
|
299
|
+
},
|
|
300
|
+
isRequest(token) {
|
|
301
|
+
return !disposed && generation === token.generation && request === token.request;
|
|
302
|
+
},
|
|
303
|
+
dispose() {
|
|
304
|
+
disposed = true;
|
|
305
|
+
generation += 1;
|
|
306
|
+
request += 1;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function unwrap(result) {
|
|
311
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
312
|
+
return result.value;
|
|
313
|
+
}
|
|
314
|
+
async function invoke(call, endpoint, payload) {
|
|
315
|
+
return unwrap(await call(endpoint, payload));
|
|
316
|
+
}
|
|
317
|
+
async function runTitleStatusLoad(input) {
|
|
318
|
+
try {
|
|
319
|
+
const status = await invoke(input.call, "status", input.sessionId ? { sessionId: input.sessionId } : {});
|
|
320
|
+
if (!input.isCurrent()) return;
|
|
321
|
+
input.onStatus(status);
|
|
322
|
+
input.onMarker(status);
|
|
323
|
+
} catch {
|
|
324
|
+
if (!input.isCurrent()) return;
|
|
325
|
+
input.onMarker(void 0);
|
|
326
|
+
input.onError(input.failedMessage);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function runTitleRegenerateFlow(input) {
|
|
330
|
+
if (!input.sessionId || !input.isCurrent()) return;
|
|
331
|
+
input.onBusy(true);
|
|
332
|
+
try {
|
|
333
|
+
await invoke(input.call, "regenerate", { sessionId: input.sessionId });
|
|
334
|
+
if (!input.isCurrent()) return;
|
|
335
|
+
const status = await invoke(input.call, "status", { sessionId: input.sessionId });
|
|
336
|
+
if (!input.isCurrent()) return;
|
|
337
|
+
input.onStatus(status);
|
|
338
|
+
input.onMarker(status);
|
|
339
|
+
} catch (cause) {
|
|
340
|
+
if (!input.isCurrent()) return;
|
|
341
|
+
input.onError(cause instanceof Error ? cause.message : input.failedMessage);
|
|
342
|
+
} finally {
|
|
343
|
+
if (!input.isCurrent()) return;
|
|
344
|
+
input.onBusy(false);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function runTitleSettingsSave(input) {
|
|
348
|
+
input.onBusy(true);
|
|
349
|
+
try {
|
|
350
|
+
const settings = await invoke(input.call, "settings", {
|
|
351
|
+
locale: input.locale,
|
|
352
|
+
expectedRevision: input.expectedRevision
|
|
353
|
+
});
|
|
354
|
+
if (!input.isCurrent()) return;
|
|
355
|
+
input.onSettings(settings);
|
|
356
|
+
input.onNote(input.savedMessage);
|
|
357
|
+
} catch (cause) {
|
|
358
|
+
if (!input.isCurrent()) return;
|
|
359
|
+
input.onError(cause instanceof Error ? cause.message : input.failedMessage);
|
|
360
|
+
} finally {
|
|
361
|
+
if (!input.isCurrent()) return;
|
|
362
|
+
input.onBusy(false);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
//#endregion
|
|
366
|
+
//#region src/marker.ts
|
|
367
|
+
function createTitleClientMarker() {
|
|
368
|
+
return { active: false };
|
|
369
|
+
}
|
|
370
|
+
function applyMarkerFromStatus(marker, status, live) {
|
|
371
|
+
if (!live) return;
|
|
372
|
+
marker.active = status?.support.weOwn === true;
|
|
373
|
+
}
|
|
374
|
+
function disposeTitleClientMarker(marker) {
|
|
375
|
+
marker.active = false;
|
|
376
|
+
}
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region src/client.tsx
|
|
379
|
+
const name = "dsh-current-title-client";
|
|
380
|
+
const inject = [
|
|
381
|
+
"slots",
|
|
382
|
+
"connection",
|
|
383
|
+
"sessions",
|
|
384
|
+
"locale",
|
|
385
|
+
"uiWorkspace",
|
|
386
|
+
"uiSession"
|
|
387
|
+
];
|
|
388
|
+
function TitleSettings(props) {
|
|
389
|
+
const seat = useNativeSeat(props.client, props.owner);
|
|
390
|
+
const sessionId = seat.sessionId;
|
|
391
|
+
const text = titleCopy[seat.locale];
|
|
392
|
+
const [status, setStatus] = (0, react.useState)();
|
|
393
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
394
|
+
const [note, setNote] = (0, react.useState)("");
|
|
395
|
+
const [error, setError] = (0, react.useState)("");
|
|
396
|
+
const workRef = (0, react.useRef)(void 0);
|
|
397
|
+
if (!workRef.current) workRef.current = createTitleClientWork();
|
|
398
|
+
const work = workRef.current;
|
|
399
|
+
const busyRef = (0, react.useRef)(busy);
|
|
400
|
+
busyRef.current = busy;
|
|
401
|
+
const call = (endpoint, payload = {}) => props.client.connection.rpc.call(RPC_CHANNEL, endpoint, payload);
|
|
402
|
+
function commitMarker(next) {
|
|
403
|
+
applyMarkerFromStatus(props.marker, next, true);
|
|
404
|
+
}
|
|
405
|
+
(0, react.useEffect)(() => () => {
|
|
406
|
+
work.dispose();
|
|
407
|
+
}, [work]);
|
|
408
|
+
(0, react.useEffect)(() => {
|
|
409
|
+
const generation = work.beginLoad();
|
|
410
|
+
const isCurrent = () => work.isLoad(generation);
|
|
411
|
+
setBusy(false);
|
|
412
|
+
setError("");
|
|
413
|
+
setNote("");
|
|
414
|
+
runTitleStatusLoad({
|
|
415
|
+
call,
|
|
416
|
+
sessionId: sessionId || void 0,
|
|
417
|
+
isCurrent,
|
|
418
|
+
failedMessage: text.failed,
|
|
419
|
+
onStatus: setStatus,
|
|
420
|
+
onError: setError,
|
|
421
|
+
onMarker: commitMarker
|
|
422
|
+
});
|
|
423
|
+
return () => {
|
|
424
|
+
if (work.isLoad(generation)) work.beginLoad();
|
|
425
|
+
};
|
|
426
|
+
}, [
|
|
427
|
+
props.client,
|
|
428
|
+
props.marker,
|
|
429
|
+
sessionId,
|
|
430
|
+
text.failed,
|
|
431
|
+
work
|
|
432
|
+
]);
|
|
433
|
+
const refreshStatus = (0, react.useCallback)(() => {
|
|
434
|
+
if (shouldSkipTitleRefresh({ busy: busyRef.current }) || work.disposed) return;
|
|
435
|
+
const generation = work.captureLoad();
|
|
436
|
+
runTitleStatusLoad({
|
|
437
|
+
call,
|
|
438
|
+
sessionId: sessionId || void 0,
|
|
439
|
+
isCurrent: () => work.isLoad(generation) && !shouldSkipTitleRefresh({ busy: busyRef.current }),
|
|
440
|
+
failedMessage: text.failed,
|
|
441
|
+
onStatus: (next) => {
|
|
442
|
+
if (shouldSkipTitleRefresh({ busy: busyRef.current })) return;
|
|
443
|
+
setStatus(next);
|
|
444
|
+
},
|
|
445
|
+
onError: () => {},
|
|
446
|
+
onMarker: (next) => {
|
|
447
|
+
if (shouldSkipTitleRefresh({ busy: busyRef.current })) return;
|
|
448
|
+
commitMarker(next);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}, [
|
|
452
|
+
props.client,
|
|
453
|
+
props.marker,
|
|
454
|
+
sessionId,
|
|
455
|
+
text.failed,
|
|
456
|
+
work
|
|
457
|
+
]);
|
|
458
|
+
useFeatureRefresh(props.client, sessionId, refreshStatus, status?.session?.generating === true && !busy, Boolean(sessionId));
|
|
459
|
+
const locale = status?.settings.locale ?? "auto";
|
|
460
|
+
const modes = [
|
|
461
|
+
{
|
|
462
|
+
id: "auto",
|
|
463
|
+
label: text.auto
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
id: "zh",
|
|
467
|
+
label: "中文"
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: "en",
|
|
471
|
+
label: "English"
|
|
472
|
+
}
|
|
473
|
+
];
|
|
474
|
+
const title = status?.session?.title;
|
|
475
|
+
const pinned = status?.session?.pinned === true;
|
|
476
|
+
const weOwn = status?.support.weOwn === true;
|
|
477
|
+
const canRegenerate = Boolean(sessionId) && weOwn && !busy;
|
|
478
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
479
|
+
className: "current-title-settings",
|
|
480
|
+
"data-testid": "current-title-settings",
|
|
481
|
+
children: [
|
|
482
|
+
!status && !error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
483
|
+
role: "status",
|
|
484
|
+
children: seat.locale === "en" ? "Loading title settings…" : "正在读取标题设置…"
|
|
485
|
+
}) : null,
|
|
486
|
+
title ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
487
|
+
translate: "no",
|
|
488
|
+
children: title
|
|
489
|
+
}) : null,
|
|
490
|
+
pinned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
491
|
+
className: "current-title-meta",
|
|
492
|
+
children: text.pinned
|
|
493
|
+
}) : null,
|
|
494
|
+
status?.session?.generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
495
|
+
role: "status",
|
|
496
|
+
children: text.generating
|
|
497
|
+
}) : null,
|
|
498
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
499
|
+
role: "alert",
|
|
500
|
+
children: error
|
|
501
|
+
}) : null,
|
|
502
|
+
note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
503
|
+
role: "status",
|
|
504
|
+
children: note
|
|
505
|
+
}) : null,
|
|
506
|
+
sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
507
|
+
type: "button",
|
|
508
|
+
disabled: !canRegenerate,
|
|
509
|
+
"aria-label": text.regenerate,
|
|
510
|
+
onClick: () => {
|
|
511
|
+
if (!sessionId) return;
|
|
512
|
+
const token = work.beginRequest();
|
|
513
|
+
setError("");
|
|
514
|
+
setNote("");
|
|
515
|
+
runTitleRegenerateFlow({
|
|
516
|
+
call,
|
|
517
|
+
sessionId,
|
|
518
|
+
isCurrent: () => work.isRequest(token),
|
|
519
|
+
failedMessage: text.failed,
|
|
520
|
+
onBusy: setBusy,
|
|
521
|
+
onStatus: setStatus,
|
|
522
|
+
onError: setError,
|
|
523
|
+
onMarker: commitMarker
|
|
524
|
+
});
|
|
525
|
+
},
|
|
526
|
+
children: text.regenerate
|
|
527
|
+
}) : null,
|
|
528
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
529
|
+
disabled: busy || !status,
|
|
530
|
+
"aria-label": text.locale,
|
|
531
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", { children: text.locale }), modes.map((mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
532
|
+
type: "radio",
|
|
533
|
+
name: "current-title-locale",
|
|
534
|
+
value: mode.id,
|
|
535
|
+
checked: locale === mode.id,
|
|
536
|
+
onChange: () => {
|
|
537
|
+
if (!status) return;
|
|
538
|
+
const token = work.beginRequest();
|
|
539
|
+
setError("");
|
|
540
|
+
setNote("");
|
|
541
|
+
runTitleSettingsSave({
|
|
542
|
+
call,
|
|
543
|
+
locale: mode.id,
|
|
544
|
+
expectedRevision: status.settings.revision,
|
|
545
|
+
isCurrent: () => work.isRequest(token),
|
|
546
|
+
savedMessage: text.saved,
|
|
547
|
+
failedMessage: text.failed,
|
|
548
|
+
onBusy: setBusy,
|
|
549
|
+
onSettings: (settings) => setStatus({
|
|
550
|
+
...status,
|
|
551
|
+
settings
|
|
552
|
+
}),
|
|
553
|
+
onNote: setNote,
|
|
554
|
+
onError: setError
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}), mode.label] }, mode.id))]
|
|
558
|
+
})
|
|
559
|
+
]
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
const styles = `
|
|
563
|
+
.current-title-settings{display:grid;gap:8px;color:inherit;font:400 var(--font-size-2,14px)/1.5 var(--default-font-family,system-ui,sans-serif)}
|
|
564
|
+
.current-title-settings p{margin:0}
|
|
565
|
+
.current-title-settings p[translate="no"]{overflow-wrap:anywhere}
|
|
566
|
+
.current-title-meta{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}
|
|
567
|
+
.current-title-settings button{min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:8px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start;transition:background-color 150ms ease,color 150ms ease,border-color 150ms ease,box-shadow 150ms ease,transform 150ms ease}
|
|
568
|
+
.current-title-settings button:hover:not(:disabled){background:var(--gray-3,color-mix(in srgb,currentColor 6%,transparent));border-color:color-mix(in srgb,currentColor 35%,transparent)}
|
|
569
|
+
.current-title-settings button:active:not(:disabled){transform:scale(.97)}
|
|
570
|
+
.current-title-settings button:disabled{opacity:.45;cursor:not-allowed}
|
|
571
|
+
.current-title-settings fieldset{margin:0;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:10px;padding:8px 12px;display:grid;gap:8px}
|
|
572
|
+
.current-title-settings label{display:flex;gap:8px;align-items:center}
|
|
573
|
+
.current-title-settings :focus-visible{outline:2px solid currentColor;outline-offset:3px}
|
|
574
|
+
@media(prefers-reduced-motion:reduce){.current-title-settings button{transition:none}}
|
|
575
|
+
`;
|
|
576
|
+
function apply(ctx) {
|
|
577
|
+
const client = ctx;
|
|
578
|
+
const marker = createTitleClientMarker();
|
|
579
|
+
ctx.provide(TITLE_CLIENT_SERVICE, marker);
|
|
580
|
+
ctx.effect(() => {
|
|
581
|
+
let live = true;
|
|
582
|
+
client.connection.rpc.call(RPC_CHANNEL, "status", {}).then((result) => {
|
|
583
|
+
const row = result;
|
|
584
|
+
if (!live) return;
|
|
585
|
+
applyMarkerFromStatus(marker, row?.ok ? row.value : void 0, live);
|
|
586
|
+
}).catch(() => {
|
|
587
|
+
if (live) applyMarkerFromStatus(marker, void 0, live);
|
|
588
|
+
});
|
|
589
|
+
return () => {
|
|
590
|
+
live = false;
|
|
591
|
+
disposeTitleClientMarker(marker);
|
|
592
|
+
};
|
|
593
|
+
}, "current-title.marker");
|
|
594
|
+
ctx.effect(() => {
|
|
595
|
+
if (typeof document === "undefined") return () => {};
|
|
596
|
+
const style = document.createElement("style");
|
|
597
|
+
style.setAttribute("data-plugin", "@klarkxy/dsh-current-title");
|
|
598
|
+
style.textContent = styles;
|
|
599
|
+
document.head.appendChild(style);
|
|
600
|
+
return () => style.remove();
|
|
601
|
+
}, "current-title.styles");
|
|
602
|
+
ctx.effect(() => client.slots.inject("settings.section", () => client.slots.register({
|
|
603
|
+
name: "settings.section",
|
|
604
|
+
id: "current-title",
|
|
605
|
+
order: 80,
|
|
606
|
+
label: "当前标题"
|
|
607
|
+
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettings, {
|
|
608
|
+
client,
|
|
609
|
+
marker,
|
|
610
|
+
owner: props
|
|
611
|
+
}))), "current-title.settings");
|
|
612
|
+
}
|
|
613
|
+
//#endregion
|
|
614
|
+
exports.TitleSettings = TitleSettings;
|
|
615
|
+
exports.apply = apply;
|
|
616
|
+
exports.applyMarkerFromStatus = applyMarkerFromStatus;
|
|
617
|
+
exports.createTitleClientMarker = createTitleClientMarker;
|
|
618
|
+
exports.disposeTitleClientMarker = disposeTitleClientMarker;
|
|
619
|
+
exports.inject = inject;
|
|
620
|
+
exports.name = name;
|
|
621
|
+
|
|
622
|
+
//# sourceMappingURL=client.inner.cjs.map
|
|
623
|
+
return module.exports;
|
|
624
|
+
}
|
|
625
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { RpcResult } from "@klarkxy/dsh-ai-services/contracts";
|
|
2
|
+
//#region src/contracts.d.ts
|
|
3
|
+
declare const PLUGIN_NAME = "@klarkxy/dsh-current-title";
|
|
4
|
+
declare const FEATURE_ID = "current-title";
|
|
5
|
+
declare const PROVIDER_ID = "@klarkxy/dsh-current-title";
|
|
6
|
+
declare const PURPOSE_ID = "current-title.generate";
|
|
7
|
+
declare const RPC_CHANNEL = "/dsh-current-title";
|
|
8
|
+
declare const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
9
|
+
declare const PROMPT_VERSION = "current-title.v1";
|
|
10
|
+
declare const SCHEMA_VERSION = "title-json.v1";
|
|
11
|
+
declare const CONTRACT_VERSION = 1;
|
|
12
|
+
declare const SETTINGS_KEY = "current";
|
|
13
|
+
declare const DEFAULT_MAX_RECENT_MESSAGES = 8;
|
|
14
|
+
declare const DEFAULT_TARGET_WORDS = 5;
|
|
15
|
+
declare const DEFAULT_TARGET_CJK = 10;
|
|
16
|
+
declare const DEFAULT_MAX_INPUT_BYTES = 4096;
|
|
17
|
+
declare const DEFAULT_MAX_OUTPUT_TOKENS = 64;
|
|
18
|
+
declare const DEFAULT_TIMEOUT_MS = 60000;
|
|
19
|
+
declare const DEFAULT_MAX_TITLE_BYTES = 80;
|
|
20
|
+
type TitleLocaleMode = 'auto' | 'zh' | 'en';
|
|
21
|
+
type TitleSourceKind = 'fallback' | 'provider' | 'user';
|
|
22
|
+
interface TitleMessage {
|
|
23
|
+
readonly seq: number;
|
|
24
|
+
readonly text: string;
|
|
25
|
+
}
|
|
26
|
+
interface TitleSnapshot {
|
|
27
|
+
readonly title: string;
|
|
28
|
+
readonly messageSeqs: readonly number[];
|
|
29
|
+
readonly source: {
|
|
30
|
+
readonly kind: TitleSourceKind;
|
|
31
|
+
readonly provider?: string;
|
|
32
|
+
};
|
|
33
|
+
readonly eventSeq: number;
|
|
34
|
+
readonly updatedAt: number;
|
|
35
|
+
}
|
|
36
|
+
interface TitleSettings {
|
|
37
|
+
revision: number;
|
|
38
|
+
locale: TitleLocaleMode;
|
|
39
|
+
}
|
|
40
|
+
interface TitleSupport {
|
|
41
|
+
nativeOwner: string | null;
|
|
42
|
+
weOwn: boolean;
|
|
43
|
+
limitation?: string;
|
|
44
|
+
}
|
|
45
|
+
interface TitleStatus {
|
|
46
|
+
settings: TitleSettings;
|
|
47
|
+
support: TitleSupport;
|
|
48
|
+
session?: {
|
|
49
|
+
sessionId: string;
|
|
50
|
+
title?: string;
|
|
51
|
+
sourceKind?: TitleSourceKind;
|
|
52
|
+
generating: boolean;
|
|
53
|
+
pinned: boolean;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
interface TitleClientMarker {
|
|
57
|
+
active: boolean;
|
|
58
|
+
}
|
|
59
|
+
interface GenerateConfig {
|
|
60
|
+
readonly maxRecentMessages: number;
|
|
61
|
+
readonly locale: TitleLocaleMode;
|
|
62
|
+
readonly targetWords: number;
|
|
63
|
+
readonly targetCjkCharacters: number;
|
|
64
|
+
readonly maxInputBytes: number;
|
|
65
|
+
readonly maxOutputTokens: number;
|
|
66
|
+
readonly timeoutMs: number;
|
|
67
|
+
readonly maxTitleBytes: number;
|
|
68
|
+
}
|
|
69
|
+
declare function defaultSettings(locale?: TitleLocaleMode): TitleSettings;
|
|
70
|
+
declare function defaultGenerateConfig(locale?: TitleLocaleMode): GenerateConfig;
|
|
71
|
+
declare function isTitleLocaleMode(value: unknown): value is TitleLocaleMode;
|
|
72
|
+
//#endregion
|
|
73
|
+
export { CONTRACT_VERSION, DEFAULT_MAX_INPUT_BYTES, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MAX_RECENT_MESSAGES, DEFAULT_MAX_TITLE_BYTES, DEFAULT_TARGET_CJK, DEFAULT_TARGET_WORDS, DEFAULT_TIMEOUT_MS, FEATURE_ID, GenerateConfig, PLUGIN_NAME, PROMPT_VERSION, PROVIDER_ID, PURPOSE_ID, RPC_CHANNEL, type RpcResult, SCHEMA_VERSION, SETTINGS_KEY, TITLE_CLIENT_SERVICE, TitleClientMarker, TitleLocaleMode, TitleMessage, TitleSettings, TitleSnapshot, TitleSourceKind, TitleStatus, TitleSupport, defaultGenerateConfig, defaultSettings, isTitleLocaleMode };
|
|
74
|
+
//# sourceMappingURL=contracts.d.ts.map
|