@klarkxy/dsh-mood 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,804 @@
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-mood",
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
+ /** Frozen shared chat-events seat. Must stay equal to @klarkxy/dsh-ai-services CHAT_EVENTS_SLOT. */
246
+ const CHAT_EVENTS_SLOT = "dsh-editor.chat.events";
247
+ const MOOD_RPC_CHANNEL = "/dsh-mood";
248
+ const defaultSettings = () => ({
249
+ revision: 0,
250
+ mode: "auto"
251
+ });
252
+ function readinessLabel(readiness) {
253
+ if (readiness === "clear-request") return "表述清楚";
254
+ if (readiness === "user-confirmed") return "作者已确认";
255
+ if (readiness === "disclosed-assumptions") return "按已披露假定继续";
256
+ if (readiness === "cancelled") return "已取消,未确认";
257
+ if (readiness === "stale") return "已过期";
258
+ return "待确认";
259
+ }
260
+ //#endregion
261
+ //#region src/client.tsx
262
+ const name = "dsh-mood-client";
263
+ const inject = [
264
+ "slots",
265
+ "connection",
266
+ "sessions",
267
+ "locale",
268
+ "uiWorkspace",
269
+ "uiSession"
270
+ ];
271
+ function unwrap(result) {
272
+ const row = result;
273
+ if (!row || typeof row !== "object" || !("ok" in row)) throw new Error("请求失败。");
274
+ if (!row.ok) throw new Error(row.error.message);
275
+ return row.value;
276
+ }
277
+ function copy(locale) {
278
+ if (locale === "en") return {
279
+ settings: "Requirements",
280
+ auto: "Auto",
281
+ manual: "Manual",
282
+ strict: "Strict",
283
+ hint: "Clear requests skip analysis. Confirmation is not file or publish approval.",
284
+ card: "Task contract",
285
+ amend: "Amend",
286
+ reanalyze: "Reanalyze",
287
+ retry: "Retry original",
288
+ save: "Save",
289
+ loading: "Loading…",
290
+ empty: "No contract yet.",
291
+ goal: "Goal",
292
+ evidence: "Evidence",
293
+ questions: "Questions",
294
+ recovery: "The original request is waiting. Retry or reanalyze it; it has not been executed.",
295
+ sessionHint: "Select a session to view its task contract."
296
+ };
297
+ return {
298
+ settings: "需求澄清",
299
+ auto: "自动",
300
+ manual: "手动",
301
+ strict: "严格",
302
+ hint: "表述清楚的请求不调用分析。确认需求不能代替文件或发布审批。",
303
+ card: "任务约定",
304
+ amend: "修订",
305
+ reanalyze: "重新分析",
306
+ retry: "按原请求重试",
307
+ save: "保存",
308
+ loading: "正在读取…",
309
+ empty: "还没有任务约定。",
310
+ goal: "目标",
311
+ evidence: "证据",
312
+ questions: "澄清",
313
+ recovery: "原请求尚未执行。可重试或手动分析,不会另造一条提问。",
314
+ sessionHint: "选择一个会话后可以查看该会话的任务约定。"
315
+ };
316
+ }
317
+ function modeFromKey(current, key) {
318
+ const order = [
319
+ "auto",
320
+ "manual",
321
+ "strict"
322
+ ];
323
+ const index = order.indexOf(current);
324
+ if (key === "Home") return "auto";
325
+ if (key === "End") return "strict";
326
+ if (key === "ArrowRight" || key === "ArrowDown") return order[(index + 1) % order.length];
327
+ if (key === "ArrowLeft" || key === "ArrowUp") return order[(index + order.length - 1) % order.length];
328
+ }
329
+ function shouldShowCard(hidden, contract, pendingManual, recovery = false) {
330
+ if (hidden) return false;
331
+ return Boolean(contract) || pendingManual || recovery;
332
+ }
333
+ function isCurrentMoodRequest(input) {
334
+ return input.mounted && input.sessionId === input.viewSessionId && input.requestId === input.latestRequestId;
335
+ }
336
+ function shouldSkipMoodRefresh(input) {
337
+ return input.busy || input.editing === true;
338
+ }
339
+ /** Read-only status peek; does not bump request generation or abort an in-flight edit. */
340
+ async function peekMoodStatus(input) {
341
+ if (shouldSkipMoodRefresh({
342
+ busy: input.busy(),
343
+ editing: input.editing?.() === true
344
+ })) return void 0;
345
+ if (!input.sessionId || input.requestId === 0) return void 0;
346
+ try {
347
+ const next = unwrap(await input.call("status", { sessionId: input.sessionId }));
348
+ if (shouldSkipMoodRefresh({
349
+ busy: input.busy(),
350
+ editing: input.editing?.() === true
351
+ })) return void 0;
352
+ if (!isCurrentMoodRequest({
353
+ mounted: input.mounted(),
354
+ sessionId: input.sessionId,
355
+ viewSessionId: input.viewSessionId(),
356
+ requestId: input.requestId,
357
+ latestRequestId: input.latestRequestId()
358
+ })) return void 0;
359
+ return next;
360
+ } catch {
361
+ return;
362
+ }
363
+ }
364
+ function shouldOfferRecovery(status) {
365
+ const session = status?.session;
366
+ if (!session) return false;
367
+ if (session.held || session.pendingManual) return true;
368
+ const readiness = session.contract?.readiness;
369
+ if (readiness === "pending" || readiness === "cancelled") return true;
370
+ return session.clarification.some((item) => item.status === "pending" || item.status === "cancelled");
371
+ }
372
+ function MoodModePanel({ client, locale }) {
373
+ const text = copy(locale);
374
+ const groupId = (0, react.useId)();
375
+ const [status, setStatus] = (0, react.useState)();
376
+ const [busy, setBusy] = (0, react.useState)(false);
377
+ const [error, setError] = (0, react.useState)("");
378
+ const [note, setNote] = (0, react.useState)("");
379
+ const requestId = (0, react.useRef)(0);
380
+ async function call(endpoint, payload = {}) {
381
+ return unwrap(await client.connection.rpc.call(MOOD_RPC_CHANNEL, endpoint, payload));
382
+ }
383
+ (0, react.useEffect)(() => {
384
+ const id = ++requestId.current;
385
+ let mounted = true;
386
+ call("status").then((next) => {
387
+ if (!isCurrentMoodRequest({
388
+ mounted,
389
+ sessionId: "",
390
+ viewSessionId: "",
391
+ requestId: id,
392
+ latestRequestId: requestId.current
393
+ })) return;
394
+ setStatus(next);
395
+ }).catch(() => {
396
+ if (mounted && id === requestId.current) setError("无法读取需求澄清设置。");
397
+ });
398
+ return () => {
399
+ mounted = false;
400
+ };
401
+ }, [client]);
402
+ async function action(run) {
403
+ setBusy(true);
404
+ setNote("");
405
+ setError("");
406
+ try {
407
+ await run();
408
+ } catch (cause) {
409
+ setError(cause instanceof Error ? cause.message : "操作失败。");
410
+ } finally {
411
+ setBusy(false);
412
+ }
413
+ }
414
+ const mode = status?.settings.mode ?? defaultSettings().mode;
415
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
416
+ className: "mood-settings",
417
+ "data-testid": "mood-settings",
418
+ children: [
419
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: text.settings }),
420
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
421
+ role: "alert",
422
+ children: error
423
+ }) : null,
424
+ note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
425
+ role: "status",
426
+ children: note
427
+ }) : null,
428
+ !status ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
429
+ role: "status",
430
+ children: text.loading
431
+ }) : null,
432
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
433
+ role: "radiogroup",
434
+ "aria-label": text.settings,
435
+ onKeyDown: (event) => {
436
+ const next = modeFromKey(mode, event.key);
437
+ if (!next || busy || !status) return;
438
+ event.preventDefault();
439
+ action(async () => {
440
+ const updated = await call("mode", {
441
+ mode: next,
442
+ expectedRevision: status.settings.revision
443
+ });
444
+ setStatus(updated);
445
+ setNote("已保存。");
446
+ });
447
+ },
448
+ children: [
449
+ ["auto", text.auto],
450
+ ["manual", text.manual],
451
+ ["strict", text.strict]
452
+ ].map(([value, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
453
+ id: `${groupId}-${value}`,
454
+ type: "radio",
455
+ name: groupId,
456
+ value,
457
+ checked: mode === value,
458
+ disabled: busy || !status,
459
+ onChange: () => {
460
+ if (!status) return;
461
+ action(async () => {
462
+ const updated = await call("mode", {
463
+ mode: value,
464
+ expectedRevision: status.settings.revision
465
+ });
466
+ setStatus(updated);
467
+ setNote("已保存。");
468
+ });
469
+ }
470
+ }), label] }, value))
471
+ }),
472
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
473
+ className: "mood-meta",
474
+ children: text.hint
475
+ })
476
+ ]
477
+ });
478
+ }
479
+ function MoodContractCard({ client, sessionId, locale, hidden, surface = "chat" }) {
480
+ const text = copy(locale === "en" ? "en" : "zh");
481
+ const [status, setStatus] = (0, react.useState)();
482
+ const [busy, setBusy] = (0, react.useState)(false);
483
+ const [error, setError] = (0, react.useState)("");
484
+ const [editing, setEditing] = (0, react.useState)(false);
485
+ const [goal, setGoal] = (0, react.useState)("");
486
+ const requestId = (0, react.useRef)(0);
487
+ const viewSession = (0, react.useRef)(sessionId ?? "");
488
+ const liveRef = (0, react.useRef)(true);
489
+ const busyRef = (0, react.useRef)(false);
490
+ const editingRef = (0, react.useRef)(false);
491
+ busyRef.current = busy;
492
+ editingRef.current = editing;
493
+ async function call(endpoint, payload) {
494
+ return unwrap(await client.connection.rpc.call(MOOD_RPC_CHANNEL, endpoint, payload));
495
+ }
496
+ (0, react.useEffect)(() => {
497
+ liveRef.current = true;
498
+ return () => {
499
+ liveRef.current = false;
500
+ };
501
+ }, []);
502
+ (0, react.useEffect)(() => {
503
+ viewSession.current = sessionId ?? "";
504
+ const id = ++requestId.current;
505
+ setStatus(void 0);
506
+ setEditing(false);
507
+ setError("");
508
+ if (!sessionId) return;
509
+ let mounted = true;
510
+ call("status", { sessionId }).then((next) => {
511
+ if (!isCurrentMoodRequest({
512
+ mounted,
513
+ sessionId,
514
+ viewSessionId: viewSession.current,
515
+ requestId: id,
516
+ latestRequestId: requestId.current
517
+ })) return;
518
+ setStatus(next);
519
+ setGoal(next.session?.contract?.goal ?? "");
520
+ }).catch(() => {
521
+ if (!isCurrentMoodRequest({
522
+ mounted,
523
+ sessionId,
524
+ viewSessionId: viewSession.current,
525
+ requestId: id,
526
+ latestRequestId: requestId.current
527
+ })) return;
528
+ setError("无法读取任务约定。");
529
+ });
530
+ return () => {
531
+ mounted = false;
532
+ };
533
+ }, [client, sessionId]);
534
+ useFeatureRefresh(client, sessionId ?? "", () => {
535
+ peekMoodStatus({
536
+ call: (endpoint, payload) => client.connection.rpc.call(MOOD_RPC_CHANNEL, endpoint, payload),
537
+ sessionId: viewSession.current,
538
+ requestId: requestId.current,
539
+ latestRequestId: () => requestId.current,
540
+ viewSessionId: () => viewSession.current,
541
+ mounted: () => liveRef.current,
542
+ busy: () => busyRef.current,
543
+ editing: () => editingRef.current
544
+ }).then((next) => {
545
+ if (!next) return;
546
+ setStatus(next);
547
+ if (!editingRef.current) setGoal(next.session?.contract?.goal ?? "");
548
+ });
549
+ }, Boolean(status?.session?.pendingManual) && !busy, Boolean(sessionId) && (surface === "settings" || !hidden));
550
+ const contract = status?.session?.contract;
551
+ const pendingManual = Boolean(status?.session?.pendingManual);
552
+ const recovery = shouldOfferRecovery(status);
553
+ if (surface !== "settings" && !shouldShowCard(Boolean(hidden), contract, pendingManual, recovery)) return null;
554
+ async function action(run) {
555
+ const id = requestId.current;
556
+ const view = viewSession.current;
557
+ setBusy(true);
558
+ setError("");
559
+ try {
560
+ await run();
561
+ } catch (cause) {
562
+ if (!isCurrentMoodRequest({
563
+ mounted: true,
564
+ sessionId: view,
565
+ viewSessionId: viewSession.current,
566
+ requestId: id,
567
+ latestRequestId: requestId.current
568
+ })) return;
569
+ setError(cause instanceof Error ? cause.message : "操作失败。");
570
+ } finally {
571
+ if (isCurrentMoodRequest({
572
+ mounted: true,
573
+ sessionId: view,
574
+ viewSessionId: viewSession.current,
575
+ requestId: id,
576
+ latestRequestId: requestId.current
577
+ })) setBusy(false);
578
+ }
579
+ }
580
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
581
+ className: "mood-card",
582
+ "data-testid": "mood-contract-card",
583
+ "data-session": sessionId || void 0,
584
+ children: [
585
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: text.card }), contract ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
586
+ className: "mood-meta",
587
+ children: readinessLabel(contract.readiness)
588
+ }) : null] }),
589
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
590
+ role: "alert",
591
+ children: error
592
+ }) : null,
593
+ recovery ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
594
+ role: "status",
595
+ children: text.recovery
596
+ }) : null,
597
+ contract ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
598
+ text.goal,
599
+ ":",
600
+ contract.goal || text.empty
601
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.empty }),
602
+ contract?.evidence.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
603
+ "aria-label": text.evidence,
604
+ children: contract.evidence.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: item.excerpt ?? `#${item.seq}` }, `${item.kind}-${item.seq}`))
605
+ }) : null,
606
+ (status?.session?.clarification ?? []).length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
607
+ "aria-label": text.questions,
608
+ children: (status?.session?.clarification ?? []).map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
609
+ "[",
610
+ item.status,
611
+ "] ",
612
+ item.question,
613
+ item.answer ? ` → ${item.answer}` : ""
614
+ ] }, item.id))
615
+ }) : null,
616
+ editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [text.goal, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
617
+ "aria-label": text.goal,
618
+ rows: 3,
619
+ value: goal,
620
+ disabled: busy,
621
+ onChange: (event) => setGoal(event.target.value)
622
+ })] }) : null,
623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
624
+ className: "mood-actions",
625
+ children: [
626
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
627
+ type: "button",
628
+ disabled: busy || !contract,
629
+ onClick: () => {
630
+ if (editing && contract && sessionId) {
631
+ const view = sessionId;
632
+ const id = requestId.current;
633
+ action(async () => {
634
+ const next = await call("edit", {
635
+ sessionId: view,
636
+ expectedRevision: contract.revision,
637
+ patch: { goal }
638
+ });
639
+ if (!isCurrentMoodRequest({
640
+ mounted: true,
641
+ sessionId: view,
642
+ viewSessionId: viewSession.current,
643
+ requestId: id,
644
+ latestRequestId: requestId.current
645
+ })) return;
646
+ setStatus(next);
647
+ setEditing(false);
648
+ });
649
+ return;
650
+ }
651
+ setEditing(true);
652
+ },
653
+ children: editing ? text.save : text.amend
654
+ }),
655
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
656
+ type: "button",
657
+ disabled: busy || !sessionId,
658
+ onClick: () => {
659
+ const view = sessionId;
660
+ if (!view) return;
661
+ const id = requestId.current;
662
+ action(async () => {
663
+ const next = await call("manual", { sessionId: view });
664
+ if (!isCurrentMoodRequest({
665
+ mounted: true,
666
+ sessionId: view,
667
+ viewSessionId: viewSession.current,
668
+ requestId: id,
669
+ latestRequestId: requestId.current
670
+ })) return;
671
+ setStatus(next);
672
+ });
673
+ },
674
+ children: text.reanalyze
675
+ }),
676
+ recovery ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
677
+ type: "button",
678
+ disabled: busy || !sessionId || !status?.session?.held,
679
+ onClick: () => {
680
+ const view = sessionId;
681
+ if (!view) return;
682
+ const id = requestId.current;
683
+ action(async () => {
684
+ const next = await call("retry", { sessionId: view });
685
+ if (!isCurrentMoodRequest({
686
+ mounted: true,
687
+ sessionId: view,
688
+ viewSessionId: viewSession.current,
689
+ requestId: id,
690
+ latestRequestId: requestId.current
691
+ })) return;
692
+ setStatus(next);
693
+ });
694
+ },
695
+ children: text.retry
696
+ }) : null
697
+ ]
698
+ })
699
+ ]
700
+ });
701
+ }
702
+ function moodPanelKey(sessionId, locale) {
703
+ return `${sessionId}:${locale}`;
704
+ }
705
+ function MoodSettings({ client, props }) {
706
+ const seat = useNativeSeat(client, props);
707
+ const text = copy(seat.locale);
708
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
709
+ className: "mood-settings-root",
710
+ "data-testid": "mood-settings-root",
711
+ "data-session": seat.sessionId || void 0,
712
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodModePanel, {
713
+ client,
714
+ locale: seat.locale
715
+ }), seat.sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodContractCard, {
716
+ client,
717
+ sessionId: seat.sessionId,
718
+ locale: seat.locale,
719
+ surface: "settings"
720
+ }, `contract:${moodPanelKey(seat.sessionId, seat.locale)}`) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
721
+ className: "mood-meta",
722
+ children: text.sessionHint
723
+ })]
724
+ });
725
+ }
726
+ function MoodChatCard({ client, props }) {
727
+ const seat = useNativeSeat(client, props);
728
+ if (seat.hidden || !seat.sessionId) return null;
729
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodContractCard, {
730
+ client,
731
+ sessionId: seat.sessionId,
732
+ locale: seat.locale,
733
+ hidden: seat.hidden
734
+ }, moodPanelKey(seat.sessionId, seat.locale));
735
+ }
736
+ const styles = `
737
+ .mood-settings-root,.mood-settings,.mood-card{max-width:760px;display:grid;gap:12px;color:inherit;font:400 var(--font-size-2,14px)/1.5 var(--default-font-family,system-ui,sans-serif)}
738
+ .mood-settings-root{gap:16px}
739
+ .mood-settings h3,.mood-card h3{margin:0;font-size:var(--font-size-3,16px);font-weight:600}
740
+ .mood-settings p,.mood-card p,.mood-settings-root p{margin:0;line-height:1.5}
741
+ .mood-meta{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}
742
+ .mood-settings [role="radiogroup"]{display:flex;flex-wrap:wrap;gap:12px 20px}
743
+ .mood-settings label{display:flex;gap:8px;align-items:center;min-height:32px}
744
+ .mood-card header{display:flex;justify-content:space-between;gap:12px;align-items:center}
745
+ .mood-card ul{margin:0;padding-left:1.2em}
746
+ .mood-card{padding:14px 16px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:12px}
747
+ .mood-card textarea{box-sizing:border-box;width:100%;min-width:0;padding:8px 10px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 22%,transparent));border-radius:8px;background:var(--color-surface,transparent);color:inherit;font:inherit;transition:border-color 150ms ease,box-shadow 150ms ease}
748
+ .mood-card textarea:focus{border-color:var(--accent-9,#3b82f6);box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-9,#3b82f6) 25%,transparent)}
749
+ .mood-actions{display:flex;flex-wrap:wrap;gap:8px}
750
+ .mood-settings button,.mood-card 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;transition:background-color 150ms ease,color 150ms ease,border-color 150ms ease,box-shadow 150ms ease,transform 150ms ease}
751
+ .mood-settings button:hover:not(:disabled),.mood-card button:hover:not(:disabled){background:var(--gray-3,color-mix(in srgb,currentColor 6%,transparent));border-color:color-mix(in srgb,currentColor 35%,transparent)}
752
+ .mood-settings button:active:not(:disabled),.mood-card button:active:not(:disabled){transform:scale(.97)}
753
+ .mood-settings button:disabled,.mood-card button:disabled{opacity:.45;cursor:not-allowed}
754
+ .mood-settings :focus-visible,.mood-card :focus-visible,.mood-settings-root :focus-visible{outline:2px solid var(--accent-9,currentColor);outline-offset:3px}
755
+ @media(prefers-reduced-motion:reduce){.mood-settings button,.mood-card button,.mood-card textarea{transition:none}}
756
+ `;
757
+ function apply(ctx) {
758
+ const client = ctx;
759
+ ctx.effect(() => {
760
+ if (typeof document === "undefined") return () => {};
761
+ const style = document.createElement("style");
762
+ style.setAttribute("data-plugin", "@klarkxy/dsh-mood");
763
+ style.textContent = styles;
764
+ document.head.appendChild(style);
765
+ return () => style.remove();
766
+ }, "dsh-mood.styles");
767
+ ctx.effect(() => client.slots.inject("settings.section", () => client.slots.register({
768
+ name: "settings.section",
769
+ id: "mood",
770
+ order: 55,
771
+ label: "需求澄清"
772
+ }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodSettings, {
773
+ client,
774
+ props
775
+ }))), "dsh-mood.settings");
776
+ ctx.effect(() => client.slots.inject(CHAT_EVENTS_SLOT, () => client.slots.register({
777
+ name: CHAT_EVENTS_SLOT,
778
+ id: "mood",
779
+ order: 10,
780
+ label: "需求约定"
781
+ }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodChatCard, {
782
+ client,
783
+ props
784
+ }))), "dsh-mood.card");
785
+ }
786
+ //#endregion
787
+ exports.MoodChatCard = MoodChatCard;
788
+ exports.MoodSettings = MoodSettings;
789
+ exports.apply = apply;
790
+ exports.copy = copy;
791
+ exports.inject = inject;
792
+ exports.isCurrentMoodRequest = isCurrentMoodRequest;
793
+ exports.modeFromKey = modeFromKey;
794
+ exports.moodPanelKey = moodPanelKey;
795
+ exports.name = name;
796
+ exports.peekMoodStatus = peekMoodStatus;
797
+ exports.shouldOfferRecovery = shouldOfferRecovery;
798
+ exports.shouldShowCard = shouldShowCard;
799
+ exports.shouldSkipMoodRefresh = shouldSkipMoodRefresh;
800
+
801
+ //# sourceMappingURL=client.inner.cjs.map
802
+ return module.exports;
803
+ }
804
+ });