@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/LICENSE +41 -0
- package/README.md +28 -0
- package/cordis.patch.yml +5 -0
- package/docs/README.zh-CN.md +21 -0
- package/lib/client.inner.cjs +634 -0
- package/lib/client.inner.cjs.map +1 -0
- package/lib/client.js +804 -0
- package/lib/contracts.d.ts +91 -0
- package/lib/contracts.js +70 -0
- package/lib/contracts.js.map +1 -0
- package/lib/index.d.ts +173 -0
- package/lib/index.js +1261 -0
- package/lib/index.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
+
//#region ../dsh-ai-services/src/client-utils.ts
|
|
5
|
+
/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
|
|
6
|
+
function selectedSessionId(client) {
|
|
7
|
+
return client.uiWorkspace.current ? client.uiWorkspace.current.getSnapshot()?.sessionId ?? "" : client.uiSession.adapter.current.getSnapshot().key ?? "";
|
|
8
|
+
}
|
|
9
|
+
/** Owner props win; native settings only supplies close, so resolve its selected session. */
|
|
10
|
+
function useNativeSeat(client, props) {
|
|
11
|
+
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]), () => "");
|
|
12
|
+
const language = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => client.locale.subscribe(fn), [client]), (0, react.useCallback)(() => client.locale.getSnapshot().active, [client]), () => "en");
|
|
13
|
+
const row = props && typeof props === "object" ? props : {};
|
|
14
|
+
const owner = row.owner && typeof row.owner === "object" ? row.owner : {};
|
|
15
|
+
const sessionId = typeof row.sessionId === "string" ? row.sessionId : typeof owner.sessionId === "string" ? owner.sessionId : current;
|
|
16
|
+
const requestedLocale = row.locale ?? owner.locale ?? language;
|
|
17
|
+
return {
|
|
18
|
+
sessionId,
|
|
19
|
+
locale: String(requestedLocale).startsWith("zh") ? "zh" : "en",
|
|
20
|
+
hidden: row.hidden === true || owner.hidden === true
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */
|
|
24
|
+
function useFeatureRefresh(client, sessionId, refresh, working = false, enabled = true) {
|
|
25
|
+
const refreshRef = (0, react.useRef)(refresh);
|
|
26
|
+
refreshRef.current = refresh;
|
|
27
|
+
(0, react.useEffect)(() => {
|
|
28
|
+
if (!enabled) return;
|
|
29
|
+
let timer;
|
|
30
|
+
const queue = () => {
|
|
31
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
timer = setTimeout(() => refreshRef.current(), 150);
|
|
34
|
+
};
|
|
35
|
+
const stopEvents = (sessionId ? client.sessions.binding?.(sessionId)?.eventSource : void 0)?.subscribe(queue);
|
|
36
|
+
const stopConnection = client.connection?.generation?.subscribe(queue);
|
|
37
|
+
const visibility = () => {
|
|
38
|
+
if (document.visibilityState === "visible") queue();
|
|
39
|
+
};
|
|
40
|
+
window.addEventListener("focus", queue);
|
|
41
|
+
document.addEventListener("visibilitychange", visibility);
|
|
42
|
+
document.addEventListener("toggle", queue, true);
|
|
43
|
+
return () => {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
stopEvents?.();
|
|
46
|
+
stopConnection?.();
|
|
47
|
+
window.removeEventListener("focus", queue);
|
|
48
|
+
document.removeEventListener("visibilitychange", visibility);
|
|
49
|
+
document.removeEventListener("toggle", queue, true);
|
|
50
|
+
};
|
|
51
|
+
}, [
|
|
52
|
+
client,
|
|
53
|
+
sessionId,
|
|
54
|
+
enabled
|
|
55
|
+
]);
|
|
56
|
+
(0, react.useEffect)(() => {
|
|
57
|
+
if (!enabled || !working) return;
|
|
58
|
+
let delay = 250;
|
|
59
|
+
const until = Date.now() + 3e5;
|
|
60
|
+
let timer;
|
|
61
|
+
const tick = () => {
|
|
62
|
+
if (Date.now() >= until) return;
|
|
63
|
+
if (document.visibilityState !== "hidden") refreshRef.current();
|
|
64
|
+
delay = Math.min(delay * 2, 2e3);
|
|
65
|
+
timer = setTimeout(tick, delay);
|
|
66
|
+
};
|
|
67
|
+
timer = setTimeout(tick, delay);
|
|
68
|
+
return () => clearTimeout(timer);
|
|
69
|
+
}, [
|
|
70
|
+
client,
|
|
71
|
+
sessionId,
|
|
72
|
+
working,
|
|
73
|
+
enabled
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/contracts.ts
|
|
78
|
+
/** Frozen shared chat-events seat. Must stay equal to @klarkxy/dsh-ai-services CHAT_EVENTS_SLOT. */
|
|
79
|
+
const CHAT_EVENTS_SLOT = "dsh-editor.chat.events";
|
|
80
|
+
const MOOD_RPC_CHANNEL = "/dsh-mood";
|
|
81
|
+
const defaultSettings = () => ({
|
|
82
|
+
revision: 0,
|
|
83
|
+
mode: "auto"
|
|
84
|
+
});
|
|
85
|
+
function readinessLabel(readiness) {
|
|
86
|
+
if (readiness === "clear-request") return "表述清楚";
|
|
87
|
+
if (readiness === "user-confirmed") return "作者已确认";
|
|
88
|
+
if (readiness === "disclosed-assumptions") return "按已披露假定继续";
|
|
89
|
+
if (readiness === "cancelled") return "已取消,未确认";
|
|
90
|
+
if (readiness === "stale") return "已过期";
|
|
91
|
+
return "待确认";
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/client.tsx
|
|
95
|
+
const name = "dsh-mood-client";
|
|
96
|
+
const inject = [
|
|
97
|
+
"slots",
|
|
98
|
+
"connection",
|
|
99
|
+
"sessions",
|
|
100
|
+
"locale",
|
|
101
|
+
"uiWorkspace",
|
|
102
|
+
"uiSession"
|
|
103
|
+
];
|
|
104
|
+
function unwrap(result) {
|
|
105
|
+
const row = result;
|
|
106
|
+
if (!row || typeof row !== "object" || !("ok" in row)) throw new Error("请求失败。");
|
|
107
|
+
if (!row.ok) throw new Error(row.error.message);
|
|
108
|
+
return row.value;
|
|
109
|
+
}
|
|
110
|
+
function copy(locale) {
|
|
111
|
+
if (locale === "en") return {
|
|
112
|
+
settings: "Requirements",
|
|
113
|
+
auto: "Auto",
|
|
114
|
+
manual: "Manual",
|
|
115
|
+
strict: "Strict",
|
|
116
|
+
hint: "Clear requests skip analysis. Confirmation is not file or publish approval.",
|
|
117
|
+
card: "Task contract",
|
|
118
|
+
amend: "Amend",
|
|
119
|
+
reanalyze: "Reanalyze",
|
|
120
|
+
retry: "Retry original",
|
|
121
|
+
save: "Save",
|
|
122
|
+
loading: "Loading…",
|
|
123
|
+
empty: "No contract yet.",
|
|
124
|
+
goal: "Goal",
|
|
125
|
+
evidence: "Evidence",
|
|
126
|
+
questions: "Questions",
|
|
127
|
+
recovery: "The original request is waiting. Retry or reanalyze it; it has not been executed.",
|
|
128
|
+
sessionHint: "Select a session to view its task contract."
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
settings: "需求澄清",
|
|
132
|
+
auto: "自动",
|
|
133
|
+
manual: "手动",
|
|
134
|
+
strict: "严格",
|
|
135
|
+
hint: "表述清楚的请求不调用分析。确认需求不能代替文件或发布审批。",
|
|
136
|
+
card: "任务约定",
|
|
137
|
+
amend: "修订",
|
|
138
|
+
reanalyze: "重新分析",
|
|
139
|
+
retry: "按原请求重试",
|
|
140
|
+
save: "保存",
|
|
141
|
+
loading: "正在读取…",
|
|
142
|
+
empty: "还没有任务约定。",
|
|
143
|
+
goal: "目标",
|
|
144
|
+
evidence: "证据",
|
|
145
|
+
questions: "澄清",
|
|
146
|
+
recovery: "原请求尚未执行。可重试或手动分析,不会另造一条提问。",
|
|
147
|
+
sessionHint: "选择一个会话后可以查看该会话的任务约定。"
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function modeFromKey(current, key) {
|
|
151
|
+
const order = [
|
|
152
|
+
"auto",
|
|
153
|
+
"manual",
|
|
154
|
+
"strict"
|
|
155
|
+
];
|
|
156
|
+
const index = order.indexOf(current);
|
|
157
|
+
if (key === "Home") return "auto";
|
|
158
|
+
if (key === "End") return "strict";
|
|
159
|
+
if (key === "ArrowRight" || key === "ArrowDown") return order[(index + 1) % order.length];
|
|
160
|
+
if (key === "ArrowLeft" || key === "ArrowUp") return order[(index + order.length - 1) % order.length];
|
|
161
|
+
}
|
|
162
|
+
function shouldShowCard(hidden, contract, pendingManual, recovery = false) {
|
|
163
|
+
if (hidden) return false;
|
|
164
|
+
return Boolean(contract) || pendingManual || recovery;
|
|
165
|
+
}
|
|
166
|
+
function isCurrentMoodRequest(input) {
|
|
167
|
+
return input.mounted && input.sessionId === input.viewSessionId && input.requestId === input.latestRequestId;
|
|
168
|
+
}
|
|
169
|
+
function shouldSkipMoodRefresh(input) {
|
|
170
|
+
return input.busy || input.editing === true;
|
|
171
|
+
}
|
|
172
|
+
/** Read-only status peek; does not bump request generation or abort an in-flight edit. */
|
|
173
|
+
async function peekMoodStatus(input) {
|
|
174
|
+
if (shouldSkipMoodRefresh({
|
|
175
|
+
busy: input.busy(),
|
|
176
|
+
editing: input.editing?.() === true
|
|
177
|
+
})) return void 0;
|
|
178
|
+
if (!input.sessionId || input.requestId === 0) return void 0;
|
|
179
|
+
try {
|
|
180
|
+
const next = unwrap(await input.call("status", { sessionId: input.sessionId }));
|
|
181
|
+
if (shouldSkipMoodRefresh({
|
|
182
|
+
busy: input.busy(),
|
|
183
|
+
editing: input.editing?.() === true
|
|
184
|
+
})) return void 0;
|
|
185
|
+
if (!isCurrentMoodRequest({
|
|
186
|
+
mounted: input.mounted(),
|
|
187
|
+
sessionId: input.sessionId,
|
|
188
|
+
viewSessionId: input.viewSessionId(),
|
|
189
|
+
requestId: input.requestId,
|
|
190
|
+
latestRequestId: input.latestRequestId()
|
|
191
|
+
})) return void 0;
|
|
192
|
+
return next;
|
|
193
|
+
} catch {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function shouldOfferRecovery(status) {
|
|
198
|
+
const session = status?.session;
|
|
199
|
+
if (!session) return false;
|
|
200
|
+
if (session.held || session.pendingManual) return true;
|
|
201
|
+
const readiness = session.contract?.readiness;
|
|
202
|
+
if (readiness === "pending" || readiness === "cancelled") return true;
|
|
203
|
+
return session.clarification.some((item) => item.status === "pending" || item.status === "cancelled");
|
|
204
|
+
}
|
|
205
|
+
function MoodModePanel({ client, locale }) {
|
|
206
|
+
const text = copy(locale);
|
|
207
|
+
const groupId = (0, react.useId)();
|
|
208
|
+
const [status, setStatus] = (0, react.useState)();
|
|
209
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
210
|
+
const [error, setError] = (0, react.useState)("");
|
|
211
|
+
const [note, setNote] = (0, react.useState)("");
|
|
212
|
+
const requestId = (0, react.useRef)(0);
|
|
213
|
+
async function call(endpoint, payload = {}) {
|
|
214
|
+
return unwrap(await client.connection.rpc.call(MOOD_RPC_CHANNEL, endpoint, payload));
|
|
215
|
+
}
|
|
216
|
+
(0, react.useEffect)(() => {
|
|
217
|
+
const id = ++requestId.current;
|
|
218
|
+
let mounted = true;
|
|
219
|
+
call("status").then((next) => {
|
|
220
|
+
if (!isCurrentMoodRequest({
|
|
221
|
+
mounted,
|
|
222
|
+
sessionId: "",
|
|
223
|
+
viewSessionId: "",
|
|
224
|
+
requestId: id,
|
|
225
|
+
latestRequestId: requestId.current
|
|
226
|
+
})) return;
|
|
227
|
+
setStatus(next);
|
|
228
|
+
}).catch(() => {
|
|
229
|
+
if (mounted && id === requestId.current) setError("无法读取需求澄清设置。");
|
|
230
|
+
});
|
|
231
|
+
return () => {
|
|
232
|
+
mounted = false;
|
|
233
|
+
};
|
|
234
|
+
}, [client]);
|
|
235
|
+
async function action(run) {
|
|
236
|
+
setBusy(true);
|
|
237
|
+
setNote("");
|
|
238
|
+
setError("");
|
|
239
|
+
try {
|
|
240
|
+
await run();
|
|
241
|
+
} catch (cause) {
|
|
242
|
+
setError(cause instanceof Error ? cause.message : "操作失败。");
|
|
243
|
+
} finally {
|
|
244
|
+
setBusy(false);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const mode = status?.settings.mode ?? defaultSettings().mode;
|
|
248
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
249
|
+
className: "mood-settings",
|
|
250
|
+
"data-testid": "mood-settings",
|
|
251
|
+
children: [
|
|
252
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: text.settings }),
|
|
253
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
254
|
+
role: "alert",
|
|
255
|
+
children: error
|
|
256
|
+
}) : null,
|
|
257
|
+
note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
258
|
+
role: "status",
|
|
259
|
+
children: note
|
|
260
|
+
}) : null,
|
|
261
|
+
!status ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
262
|
+
role: "status",
|
|
263
|
+
children: text.loading
|
|
264
|
+
}) : null,
|
|
265
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
266
|
+
role: "radiogroup",
|
|
267
|
+
"aria-label": text.settings,
|
|
268
|
+
onKeyDown: (event) => {
|
|
269
|
+
const next = modeFromKey(mode, event.key);
|
|
270
|
+
if (!next || busy || !status) return;
|
|
271
|
+
event.preventDefault();
|
|
272
|
+
action(async () => {
|
|
273
|
+
const updated = await call("mode", {
|
|
274
|
+
mode: next,
|
|
275
|
+
expectedRevision: status.settings.revision
|
|
276
|
+
});
|
|
277
|
+
setStatus(updated);
|
|
278
|
+
setNote("已保存。");
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
children: [
|
|
282
|
+
["auto", text.auto],
|
|
283
|
+
["manual", text.manual],
|
|
284
|
+
["strict", text.strict]
|
|
285
|
+
].map(([value, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
286
|
+
id: `${groupId}-${value}`,
|
|
287
|
+
type: "radio",
|
|
288
|
+
name: groupId,
|
|
289
|
+
value,
|
|
290
|
+
checked: mode === value,
|
|
291
|
+
disabled: busy || !status,
|
|
292
|
+
onChange: () => {
|
|
293
|
+
if (!status) return;
|
|
294
|
+
action(async () => {
|
|
295
|
+
const updated = await call("mode", {
|
|
296
|
+
mode: value,
|
|
297
|
+
expectedRevision: status.settings.revision
|
|
298
|
+
});
|
|
299
|
+
setStatus(updated);
|
|
300
|
+
setNote("已保存。");
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}), label] }, value))
|
|
304
|
+
}),
|
|
305
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
306
|
+
className: "mood-meta",
|
|
307
|
+
children: text.hint
|
|
308
|
+
})
|
|
309
|
+
]
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
function MoodContractCard({ client, sessionId, locale, hidden, surface = "chat" }) {
|
|
313
|
+
const text = copy(locale === "en" ? "en" : "zh");
|
|
314
|
+
const [status, setStatus] = (0, react.useState)();
|
|
315
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
316
|
+
const [error, setError] = (0, react.useState)("");
|
|
317
|
+
const [editing, setEditing] = (0, react.useState)(false);
|
|
318
|
+
const [goal, setGoal] = (0, react.useState)("");
|
|
319
|
+
const requestId = (0, react.useRef)(0);
|
|
320
|
+
const viewSession = (0, react.useRef)(sessionId ?? "");
|
|
321
|
+
const liveRef = (0, react.useRef)(true);
|
|
322
|
+
const busyRef = (0, react.useRef)(false);
|
|
323
|
+
const editingRef = (0, react.useRef)(false);
|
|
324
|
+
busyRef.current = busy;
|
|
325
|
+
editingRef.current = editing;
|
|
326
|
+
async function call(endpoint, payload) {
|
|
327
|
+
return unwrap(await client.connection.rpc.call(MOOD_RPC_CHANNEL, endpoint, payload));
|
|
328
|
+
}
|
|
329
|
+
(0, react.useEffect)(() => {
|
|
330
|
+
liveRef.current = true;
|
|
331
|
+
return () => {
|
|
332
|
+
liveRef.current = false;
|
|
333
|
+
};
|
|
334
|
+
}, []);
|
|
335
|
+
(0, react.useEffect)(() => {
|
|
336
|
+
viewSession.current = sessionId ?? "";
|
|
337
|
+
const id = ++requestId.current;
|
|
338
|
+
setStatus(void 0);
|
|
339
|
+
setEditing(false);
|
|
340
|
+
setError("");
|
|
341
|
+
if (!sessionId) return;
|
|
342
|
+
let mounted = true;
|
|
343
|
+
call("status", { sessionId }).then((next) => {
|
|
344
|
+
if (!isCurrentMoodRequest({
|
|
345
|
+
mounted,
|
|
346
|
+
sessionId,
|
|
347
|
+
viewSessionId: viewSession.current,
|
|
348
|
+
requestId: id,
|
|
349
|
+
latestRequestId: requestId.current
|
|
350
|
+
})) return;
|
|
351
|
+
setStatus(next);
|
|
352
|
+
setGoal(next.session?.contract?.goal ?? "");
|
|
353
|
+
}).catch(() => {
|
|
354
|
+
if (!isCurrentMoodRequest({
|
|
355
|
+
mounted,
|
|
356
|
+
sessionId,
|
|
357
|
+
viewSessionId: viewSession.current,
|
|
358
|
+
requestId: id,
|
|
359
|
+
latestRequestId: requestId.current
|
|
360
|
+
})) return;
|
|
361
|
+
setError("无法读取任务约定。");
|
|
362
|
+
});
|
|
363
|
+
return () => {
|
|
364
|
+
mounted = false;
|
|
365
|
+
};
|
|
366
|
+
}, [client, sessionId]);
|
|
367
|
+
useFeatureRefresh(client, sessionId ?? "", () => {
|
|
368
|
+
peekMoodStatus({
|
|
369
|
+
call: (endpoint, payload) => client.connection.rpc.call(MOOD_RPC_CHANNEL, endpoint, payload),
|
|
370
|
+
sessionId: viewSession.current,
|
|
371
|
+
requestId: requestId.current,
|
|
372
|
+
latestRequestId: () => requestId.current,
|
|
373
|
+
viewSessionId: () => viewSession.current,
|
|
374
|
+
mounted: () => liveRef.current,
|
|
375
|
+
busy: () => busyRef.current,
|
|
376
|
+
editing: () => editingRef.current
|
|
377
|
+
}).then((next) => {
|
|
378
|
+
if (!next) return;
|
|
379
|
+
setStatus(next);
|
|
380
|
+
if (!editingRef.current) setGoal(next.session?.contract?.goal ?? "");
|
|
381
|
+
});
|
|
382
|
+
}, Boolean(status?.session?.pendingManual) && !busy, Boolean(sessionId) && (surface === "settings" || !hidden));
|
|
383
|
+
const contract = status?.session?.contract;
|
|
384
|
+
const pendingManual = Boolean(status?.session?.pendingManual);
|
|
385
|
+
const recovery = shouldOfferRecovery(status);
|
|
386
|
+
if (surface !== "settings" && !shouldShowCard(Boolean(hidden), contract, pendingManual, recovery)) return null;
|
|
387
|
+
async function action(run) {
|
|
388
|
+
const id = requestId.current;
|
|
389
|
+
const view = viewSession.current;
|
|
390
|
+
setBusy(true);
|
|
391
|
+
setError("");
|
|
392
|
+
try {
|
|
393
|
+
await run();
|
|
394
|
+
} catch (cause) {
|
|
395
|
+
if (!isCurrentMoodRequest({
|
|
396
|
+
mounted: true,
|
|
397
|
+
sessionId: view,
|
|
398
|
+
viewSessionId: viewSession.current,
|
|
399
|
+
requestId: id,
|
|
400
|
+
latestRequestId: requestId.current
|
|
401
|
+
})) return;
|
|
402
|
+
setError(cause instanceof Error ? cause.message : "操作失败。");
|
|
403
|
+
} finally {
|
|
404
|
+
if (isCurrentMoodRequest({
|
|
405
|
+
mounted: true,
|
|
406
|
+
sessionId: view,
|
|
407
|
+
viewSessionId: viewSession.current,
|
|
408
|
+
requestId: id,
|
|
409
|
+
latestRequestId: requestId.current
|
|
410
|
+
})) setBusy(false);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
414
|
+
className: "mood-card",
|
|
415
|
+
"data-testid": "mood-contract-card",
|
|
416
|
+
"data-session": sessionId || void 0,
|
|
417
|
+
children: [
|
|
418
|
+
/* @__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", {
|
|
419
|
+
className: "mood-meta",
|
|
420
|
+
children: readinessLabel(contract.readiness)
|
|
421
|
+
}) : null] }),
|
|
422
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
423
|
+
role: "alert",
|
|
424
|
+
children: error
|
|
425
|
+
}) : null,
|
|
426
|
+
recovery ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
427
|
+
role: "status",
|
|
428
|
+
children: text.recovery
|
|
429
|
+
}) : null,
|
|
430
|
+
contract ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
431
|
+
text.goal,
|
|
432
|
+
":",
|
|
433
|
+
contract.goal || text.empty
|
|
434
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.empty }),
|
|
435
|
+
contract?.evidence.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
436
|
+
"aria-label": text.evidence,
|
|
437
|
+
children: contract.evidence.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: item.excerpt ?? `#${item.seq}` }, `${item.kind}-${item.seq}`))
|
|
438
|
+
}) : null,
|
|
439
|
+
(status?.session?.clarification ?? []).length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
440
|
+
"aria-label": text.questions,
|
|
441
|
+
children: (status?.session?.clarification ?? []).map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
442
|
+
"[",
|
|
443
|
+
item.status,
|
|
444
|
+
"] ",
|
|
445
|
+
item.question,
|
|
446
|
+
item.answer ? ` → ${item.answer}` : ""
|
|
447
|
+
] }, item.id))
|
|
448
|
+
}) : null,
|
|
449
|
+
editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [text.goal, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
450
|
+
"aria-label": text.goal,
|
|
451
|
+
rows: 3,
|
|
452
|
+
value: goal,
|
|
453
|
+
disabled: busy,
|
|
454
|
+
onChange: (event) => setGoal(event.target.value)
|
|
455
|
+
})] }) : null,
|
|
456
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
457
|
+
className: "mood-actions",
|
|
458
|
+
children: [
|
|
459
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
460
|
+
type: "button",
|
|
461
|
+
disabled: busy || !contract,
|
|
462
|
+
onClick: () => {
|
|
463
|
+
if (editing && contract && sessionId) {
|
|
464
|
+
const view = sessionId;
|
|
465
|
+
const id = requestId.current;
|
|
466
|
+
action(async () => {
|
|
467
|
+
const next = await call("edit", {
|
|
468
|
+
sessionId: view,
|
|
469
|
+
expectedRevision: contract.revision,
|
|
470
|
+
patch: { goal }
|
|
471
|
+
});
|
|
472
|
+
if (!isCurrentMoodRequest({
|
|
473
|
+
mounted: true,
|
|
474
|
+
sessionId: view,
|
|
475
|
+
viewSessionId: viewSession.current,
|
|
476
|
+
requestId: id,
|
|
477
|
+
latestRequestId: requestId.current
|
|
478
|
+
})) return;
|
|
479
|
+
setStatus(next);
|
|
480
|
+
setEditing(false);
|
|
481
|
+
});
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
setEditing(true);
|
|
485
|
+
},
|
|
486
|
+
children: editing ? text.save : text.amend
|
|
487
|
+
}),
|
|
488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
489
|
+
type: "button",
|
|
490
|
+
disabled: busy || !sessionId,
|
|
491
|
+
onClick: () => {
|
|
492
|
+
const view = sessionId;
|
|
493
|
+
if (!view) return;
|
|
494
|
+
const id = requestId.current;
|
|
495
|
+
action(async () => {
|
|
496
|
+
const next = await call("manual", { sessionId: view });
|
|
497
|
+
if (!isCurrentMoodRequest({
|
|
498
|
+
mounted: true,
|
|
499
|
+
sessionId: view,
|
|
500
|
+
viewSessionId: viewSession.current,
|
|
501
|
+
requestId: id,
|
|
502
|
+
latestRequestId: requestId.current
|
|
503
|
+
})) return;
|
|
504
|
+
setStatus(next);
|
|
505
|
+
});
|
|
506
|
+
},
|
|
507
|
+
children: text.reanalyze
|
|
508
|
+
}),
|
|
509
|
+
recovery ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
510
|
+
type: "button",
|
|
511
|
+
disabled: busy || !sessionId || !status?.session?.held,
|
|
512
|
+
onClick: () => {
|
|
513
|
+
const view = sessionId;
|
|
514
|
+
if (!view) return;
|
|
515
|
+
const id = requestId.current;
|
|
516
|
+
action(async () => {
|
|
517
|
+
const next = await call("retry", { sessionId: view });
|
|
518
|
+
if (!isCurrentMoodRequest({
|
|
519
|
+
mounted: true,
|
|
520
|
+
sessionId: view,
|
|
521
|
+
viewSessionId: viewSession.current,
|
|
522
|
+
requestId: id,
|
|
523
|
+
latestRequestId: requestId.current
|
|
524
|
+
})) return;
|
|
525
|
+
setStatus(next);
|
|
526
|
+
});
|
|
527
|
+
},
|
|
528
|
+
children: text.retry
|
|
529
|
+
}) : null
|
|
530
|
+
]
|
|
531
|
+
})
|
|
532
|
+
]
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function moodPanelKey(sessionId, locale) {
|
|
536
|
+
return `${sessionId}:${locale}`;
|
|
537
|
+
}
|
|
538
|
+
function MoodSettings({ client, props }) {
|
|
539
|
+
const seat = useNativeSeat(client, props);
|
|
540
|
+
const text = copy(seat.locale);
|
|
541
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
542
|
+
className: "mood-settings-root",
|
|
543
|
+
"data-testid": "mood-settings-root",
|
|
544
|
+
"data-session": seat.sessionId || void 0,
|
|
545
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodModePanel, {
|
|
546
|
+
client,
|
|
547
|
+
locale: seat.locale
|
|
548
|
+
}), seat.sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodContractCard, {
|
|
549
|
+
client,
|
|
550
|
+
sessionId: seat.sessionId,
|
|
551
|
+
locale: seat.locale,
|
|
552
|
+
surface: "settings"
|
|
553
|
+
}, `contract:${moodPanelKey(seat.sessionId, seat.locale)}`) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
554
|
+
className: "mood-meta",
|
|
555
|
+
children: text.sessionHint
|
|
556
|
+
})]
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
function MoodChatCard({ client, props }) {
|
|
560
|
+
const seat = useNativeSeat(client, props);
|
|
561
|
+
if (seat.hidden || !seat.sessionId) return null;
|
|
562
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodContractCard, {
|
|
563
|
+
client,
|
|
564
|
+
sessionId: seat.sessionId,
|
|
565
|
+
locale: seat.locale,
|
|
566
|
+
hidden: seat.hidden
|
|
567
|
+
}, moodPanelKey(seat.sessionId, seat.locale));
|
|
568
|
+
}
|
|
569
|
+
const styles = `
|
|
570
|
+
.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)}
|
|
571
|
+
.mood-settings-root{gap:16px}
|
|
572
|
+
.mood-settings h3,.mood-card h3{margin:0;font-size:var(--font-size-3,16px);font-weight:600}
|
|
573
|
+
.mood-settings p,.mood-card p,.mood-settings-root p{margin:0;line-height:1.5}
|
|
574
|
+
.mood-meta{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}
|
|
575
|
+
.mood-settings [role="radiogroup"]{display:flex;flex-wrap:wrap;gap:12px 20px}
|
|
576
|
+
.mood-settings label{display:flex;gap:8px;align-items:center;min-height:32px}
|
|
577
|
+
.mood-card header{display:flex;justify-content:space-between;gap:12px;align-items:center}
|
|
578
|
+
.mood-card ul{margin:0;padding-left:1.2em}
|
|
579
|
+
.mood-card{padding:14px 16px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:12px}
|
|
580
|
+
.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}
|
|
581
|
+
.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)}
|
|
582
|
+
.mood-actions{display:flex;flex-wrap:wrap;gap:8px}
|
|
583
|
+
.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}
|
|
584
|
+
.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)}
|
|
585
|
+
.mood-settings button:active:not(:disabled),.mood-card button:active:not(:disabled){transform:scale(.97)}
|
|
586
|
+
.mood-settings button:disabled,.mood-card button:disabled{opacity:.45;cursor:not-allowed}
|
|
587
|
+
.mood-settings :focus-visible,.mood-card :focus-visible,.mood-settings-root :focus-visible{outline:2px solid var(--accent-9,currentColor);outline-offset:3px}
|
|
588
|
+
@media(prefers-reduced-motion:reduce){.mood-settings button,.mood-card button,.mood-card textarea{transition:none}}
|
|
589
|
+
`;
|
|
590
|
+
function apply(ctx) {
|
|
591
|
+
const client = ctx;
|
|
592
|
+
ctx.effect(() => {
|
|
593
|
+
if (typeof document === "undefined") return () => {};
|
|
594
|
+
const style = document.createElement("style");
|
|
595
|
+
style.setAttribute("data-plugin", "@klarkxy/dsh-mood");
|
|
596
|
+
style.textContent = styles;
|
|
597
|
+
document.head.appendChild(style);
|
|
598
|
+
return () => style.remove();
|
|
599
|
+
}, "dsh-mood.styles");
|
|
600
|
+
ctx.effect(() => client.slots.inject("settings.section", () => client.slots.register({
|
|
601
|
+
name: "settings.section",
|
|
602
|
+
id: "mood",
|
|
603
|
+
order: 55,
|
|
604
|
+
label: "需求澄清"
|
|
605
|
+
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodSettings, {
|
|
606
|
+
client,
|
|
607
|
+
props
|
|
608
|
+
}))), "dsh-mood.settings");
|
|
609
|
+
ctx.effect(() => client.slots.inject(CHAT_EVENTS_SLOT, () => client.slots.register({
|
|
610
|
+
name: CHAT_EVENTS_SLOT,
|
|
611
|
+
id: "mood",
|
|
612
|
+
order: 10,
|
|
613
|
+
label: "需求约定"
|
|
614
|
+
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoodChatCard, {
|
|
615
|
+
client,
|
|
616
|
+
props
|
|
617
|
+
}))), "dsh-mood.card");
|
|
618
|
+
}
|
|
619
|
+
//#endregion
|
|
620
|
+
exports.MoodChatCard = MoodChatCard;
|
|
621
|
+
exports.MoodSettings = MoodSettings;
|
|
622
|
+
exports.apply = apply;
|
|
623
|
+
exports.copy = copy;
|
|
624
|
+
exports.inject = inject;
|
|
625
|
+
exports.isCurrentMoodRequest = isCurrentMoodRequest;
|
|
626
|
+
exports.modeFromKey = modeFromKey;
|
|
627
|
+
exports.moodPanelKey = moodPanelKey;
|
|
628
|
+
exports.name = name;
|
|
629
|
+
exports.peekMoodStatus = peekMoodStatus;
|
|
630
|
+
exports.shouldOfferRecovery = shouldOfferRecovery;
|
|
631
|
+
exports.shouldShowCard = shouldShowCard;
|
|
632
|
+
exports.shouldSkipMoodRefresh = shouldSkipMoodRefresh;
|
|
633
|
+
|
|
634
|
+
//# sourceMappingURL=client.inner.cjs.map
|