@yugoo-design/dsh-design-studio-dsh 0.1.2 → 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 +427 -3
- package/lib/authoring/panel-components.json +10790 -0
- package/lib/authoring/studio-creators/material.json +1 -0
- package/lib/authoring-agent-Ci08ifdJ.js +634 -0
- package/lib/authoring-agent.d.ts +24 -0
- package/lib/authoring-agent.js +2 -0
- package/lib/authoring-agent.types-QLa61eE6.d.ts +88 -0
- package/lib/authoring-entry.d.ts +20 -0
- package/lib/authoring-entry.js +77 -0
- package/lib/authoring-host-BlsA6wXz.js +347 -0
- package/lib/authoring-host.d.ts +5 -0
- package/lib/authoring-host.js +2 -0
- package/lib/authoring-host.types-d5UT0WEk.d.ts +77 -0
- package/lib/authoring-reference-store.d.ts +39 -0
- package/lib/authoring-reference-store.js +385 -0
- package/lib/authoring-service-qGEk-IE_.js +342 -0
- package/lib/authoring-service.d.ts +112 -0
- package/lib/authoring-service.js +2 -0
- package/lib/authoring-session.d.ts +5 -0
- package/lib/authoring-session.js +203 -0
- package/lib/authoring-session.types-MHEaEFkO.d.ts +58 -0
- package/lib/authoring-sources-BzRuEuub.d.ts +17 -0
- package/lib/authoring-sources.d.ts +2 -0
- package/lib/authoring-sources.js +85 -0
- package/lib/authoring-workbench.d.ts +15 -0
- package/lib/authoring-workbench.js +782 -0
- package/lib/authoring.page-rpc-Bg6VAT0Z.d.ts +30 -0
- package/lib/authoring.page-rpc.d.ts +2 -0
- package/lib/authoring.page-rpc.js +55 -0
- package/lib/authoring.remote-client.d.ts +2557 -0
- package/lib/authoring.remote-client.js +435 -0
- package/lib/client-adapter-BgD1mpCC.d.ts +65 -0
- package/lib/client-adapter.d.ts +2 -64
- package/lib/client.js +1 -2
- package/lib/css-assets/dsh-design-studio-page/9178281a0695f79ff8bff8eca529231b67f896dc1c12c8dbaaad24b32dd3fc07.eot +0 -0
- package/lib/css-assets/dsh-design-studio-page/b366faf39f6bcc48f10b10f2860e5ebe57091cb2a15beeed8a0977e24560bbba.svg +224 -0
- package/lib/css-assets/dsh-design-studio-page/manifest.json +20 -0
- package/lib/index.js +4 -105
- package/lib/kit-files-DKBlBOG8.js +159 -0
- package/lib/page-client.js +4 -23849
- package/lib/page-path-Bj5jO20L.d.ts +25 -0
- package/lib/page-path.d.ts +2 -2
- package/lib/page-path.js +70 -2
- package/lib/page-rpc-DiazVY00.d.ts +24 -0
- package/lib/page-rpc.d.ts +2 -15
- package/lib/page-rpc.js +9 -4
- package/lib/page.d.ts +5 -3
- package/lib/page.js +219 -2
- package/lib/remote-method-ab_lHwWN.js +16 -0
- package/lib/typert.host.d.ts +827 -2
- package/lib/typert.host.js +2 -1
- package/lib/typert.remote-client-CWz-anTr.d.ts +37 -0
- package/lib/typert.remote-client.d.ts +1 -36
- package/licenses/dsh-distribution.json +8644 -149
- package/package.json +73 -10
- package/lib/page-path-wn8e_fid.d.ts +0 -14
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { ConversationMessage, ConversationMessages, InstanceWorkbench, StudioFeedback } from "@yugoo-design/dsh-design-studio-ui";
|
|
3
|
+
import { PanelSamplePreview } from "@yugoo-design/dsh-design-panel/sample-preview";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/authoring-workbench.tsx
|
|
6
|
+
/** Browser projection only: native execution, candidate preparation and saving keep their existing owners. */
|
|
7
|
+
const StudioAuthoringPromptContext = createContext(void 0);
|
|
8
|
+
const conversationEntry = (attempt, saved) => ({
|
|
9
|
+
id: attempt.request.invocationId,
|
|
10
|
+
prompt: attempt.request.prompt,
|
|
11
|
+
status: attempt.observation?.status,
|
|
12
|
+
uncertain: attempt.uncertain,
|
|
13
|
+
candidateFiles: attempt.prepared?.candidate.diff.length,
|
|
14
|
+
candidateValid: attempt.prepared?.candidate.valid,
|
|
15
|
+
saved,
|
|
16
|
+
nativeRows: attempt.observation?.conversation?.rows,
|
|
17
|
+
omitted: attempt.observation?.conversation?.omitted
|
|
18
|
+
});
|
|
19
|
+
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
20
|
+
const address = (ref) => ({
|
|
21
|
+
sessionId: ref.sessionId,
|
|
22
|
+
epoch: ref.epoch
|
|
23
|
+
});
|
|
24
|
+
function sameReference(ref, request) {
|
|
25
|
+
return ref.invocationId === request.invocationId && ref.scope.id === request.id && ref.scope.expectedRevision === request.expectedRevision && ref.scope.bundle?.name === request.bundle?.name && ref.scope.bundle?.version === request.bundle?.version && ref.scope.allowedPaths.length === request.paths.length && ref.scope.allowedPaths.every((path) => request.paths.includes(path));
|
|
26
|
+
}
|
|
27
|
+
function sameRun(left, right) {
|
|
28
|
+
return left.sessionId === right.sessionId && left.epoch === right.epoch && left.parentSessionId === right.parentSessionId && left.messageId === right.messageId && left.afterSeq === right.afterSeq && sameReference(left, {
|
|
29
|
+
invocationId: right.invocationId,
|
|
30
|
+
id: right.scope.id,
|
|
31
|
+
expectedRevision: right.scope.expectedRevision,
|
|
32
|
+
paths: right.scope.allowedPaths,
|
|
33
|
+
bundle: right.scope.bundle,
|
|
34
|
+
prompt: ""
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function matchesPrepared(prepared, request) {
|
|
38
|
+
return prepared.request.id === request.id && prepared.request.expectedRevision === request.expectedRevision && prepared.candidate.id === request.id && prepared.candidate.baseRevision === request.expectedRevision && prepared.candidate.revision === request.expectedRevision + 1;
|
|
39
|
+
}
|
|
40
|
+
/** Keep the editor mounted on connection changes, but never send old edits into a new parent's port. */
|
|
41
|
+
function guardInstances(source, active) {
|
|
42
|
+
return new Proxy(Object.create(source), { get(_target, key) {
|
|
43
|
+
const value = Reflect.get(source, key);
|
|
44
|
+
if (typeof value !== "function") return value;
|
|
45
|
+
return async (...args) => {
|
|
46
|
+
if (!active()) throw new Error("The authoring connection changed. Your input is retained; reopen this working copy to reconnect.");
|
|
47
|
+
const result = await Reflect.apply(value, source, args);
|
|
48
|
+
if (!active()) throw new Error("The authoring connection changed while the request settled. Reopen to verify the saved source; your input is retained.");
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
} });
|
|
52
|
+
}
|
|
53
|
+
function StudioAuthoringWorkbench(props) {
|
|
54
|
+
return /* @__PURE__ */ jsx(Workbench, { ...props }, props.id);
|
|
55
|
+
}
|
|
56
|
+
function Workbench({ client, id, locale, onBack, onChanged, onDirtyChange, renderPreview, initialSelection, onExport, presentation = "full", preparedLocator, initialPrompt }) {
|
|
57
|
+
const contextPrompt = useContext(StudioAuthoringPromptContext);
|
|
58
|
+
const prefill = initialPrompt ?? contextPrompt;
|
|
59
|
+
const zh = locale === "zh";
|
|
60
|
+
const text = (en, cn) => zh ? cn : en;
|
|
61
|
+
const initial = useRef(client), current = useRef(client);
|
|
62
|
+
current.current = client;
|
|
63
|
+
const invalidated = useRef(false), live = useRef(true), sequence = useRef(0), reads = useRef(0);
|
|
64
|
+
const connected = client === initial.current && !invalidated.current;
|
|
65
|
+
const active = useCallback(() => live.current && !invalidated.current && current.current === initial.current, []);
|
|
66
|
+
const [instances] = useState(() => guardInstances(client.instances, active));
|
|
67
|
+
const reviewClient = useMemo(() => guardInstances(initial.current.instances, active), [client, active]);
|
|
68
|
+
const editor = useRef({
|
|
69
|
+
dirty: false,
|
|
70
|
+
busy: false
|
|
71
|
+
});
|
|
72
|
+
const [snapshot, setSnapshot] = useState(editor.current);
|
|
73
|
+
const syncEditor = useCallback((next) => {
|
|
74
|
+
editor.current = next;
|
|
75
|
+
setSnapshot((old) => old.record === next.record && old.dirty === next.dirty && old.busy === next.busy && old.previewBundle === next.previewBundle && old.proposalReplacement === next.proposalReplacement ? old : next);
|
|
76
|
+
}, []);
|
|
77
|
+
const [intent, setIntent] = useState(prefill ?? ""), [attempt, setAttempt] = useState();
|
|
78
|
+
const intentVersion = useRef(0);
|
|
79
|
+
const usedPrefill = useRef(!!prefill);
|
|
80
|
+
const currentIntent = useRef(intent);
|
|
81
|
+
currentIntent.current = intent;
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!prefill || usedPrefill.current) return;
|
|
84
|
+
usedPrefill.current = true;
|
|
85
|
+
if (!currentIntent.current.trim()) {
|
|
86
|
+
intentVersion.current++;
|
|
87
|
+
setIntent(prefill);
|
|
88
|
+
}
|
|
89
|
+
}, [prefill]);
|
|
90
|
+
const [history, setHistory] = useState([]);
|
|
91
|
+
const attemptRef = useRef(attempt);
|
|
92
|
+
attemptRef.current = attempt;
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (attempt?.owned && !attempt.uncertain && attempt.observation?.consumed && attempt.observation.status !== "unknown" && attempt.intentVersion === intentVersion.current) setIntent((value) => value === attempt.request.prompt ? "" : value);
|
|
95
|
+
}, [attempt]);
|
|
96
|
+
const [busy, setBusy] = useState(false), busyRef = useRef(false);
|
|
97
|
+
const [error, setError] = useState(""), [review, setReview] = useState();
|
|
98
|
+
const [reviewSaved, setReviewSaved] = useState(false);
|
|
99
|
+
const [contentCandidate, setContentCandidate] = useState();
|
|
100
|
+
const [contentError, setContentError] = useState(""), [contentLoading, setContentLoading] = useState(false);
|
|
101
|
+
const [savedRequest, setSavedRequest] = useState();
|
|
102
|
+
const reviewRequest = useRef();
|
|
103
|
+
const reviewInvocation = useRef();
|
|
104
|
+
const openedRequest = useRef();
|
|
105
|
+
const openedFullCandidates = useRef(/* @__PURE__ */ new Set());
|
|
106
|
+
const fullCandidateKey = attempt?.prepared ? JSON.stringify([
|
|
107
|
+
attempt.request.invocationId,
|
|
108
|
+
attempt.prepared.request.id,
|
|
109
|
+
attempt.prepared.request.expectedRevision,
|
|
110
|
+
attempt.prepared.candidate.digest
|
|
111
|
+
]) : void 0;
|
|
112
|
+
const locatorKey = JSON.stringify(preparedLocator);
|
|
113
|
+
const dirty = useRef(false), notify = useRef(onDirtyChange);
|
|
114
|
+
notify.current = onDirtyChange;
|
|
115
|
+
const extraDirty = useRef(false);
|
|
116
|
+
extraDirty.current = !!intent || !!attempt && !reviewSaved || !!review && !reviewSaved;
|
|
117
|
+
const reportDirty = useCallback(() => {
|
|
118
|
+
if (live.current) notify.current?.(dirty.current || extraDirty.current);
|
|
119
|
+
}, []);
|
|
120
|
+
const editorDirty = useCallback((value) => {
|
|
121
|
+
dirty.current = value;
|
|
122
|
+
reportDirty();
|
|
123
|
+
}, [reportDirty]);
|
|
124
|
+
useEffect(reportDirty, [
|
|
125
|
+
intent,
|
|
126
|
+
attempt,
|
|
127
|
+
review,
|
|
128
|
+
reviewSaved,
|
|
129
|
+
reportDirty
|
|
130
|
+
]);
|
|
131
|
+
useLayoutEffect(() => {
|
|
132
|
+
if (client !== initial.current) {
|
|
133
|
+
invalidated.current = true;
|
|
134
|
+
sequence.current++;
|
|
135
|
+
}
|
|
136
|
+
}, [client]);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
live.current = true;
|
|
139
|
+
reportDirty();
|
|
140
|
+
const leave = (event) => {
|
|
141
|
+
if (extraDirty.current) {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
event.returnValue = "";
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
window.addEventListener("beforeunload", leave);
|
|
147
|
+
return () => {
|
|
148
|
+
live.current = false;
|
|
149
|
+
sequence.current++;
|
|
150
|
+
window.removeEventListener("beforeunload", leave);
|
|
151
|
+
notify.current?.(false);
|
|
152
|
+
};
|
|
153
|
+
}, [reportDirty]);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!preparedLocator || !connected) return;
|
|
156
|
+
const locator = structuredClone(preparedLocator);
|
|
157
|
+
let stopped = false;
|
|
158
|
+
setContentCandidate(void 0);
|
|
159
|
+
setContentError("");
|
|
160
|
+
setContentLoading(true);
|
|
161
|
+
initial.current.readPrepared(locator).then((value) => {
|
|
162
|
+
if (stopped || !active()) return;
|
|
163
|
+
if (!value || locator.id !== id || value.request.id !== locator.id || value.request.expectedRevision !== locator.expectedRevision || value.candidate.id !== locator.id || value.candidate.baseRevision !== locator.expectedRevision || value.candidate.revision !== locator.expectedRevision + 1 || value.candidate.digest !== locator.digest || value.request.bundle && (value.request.bundle.name !== locator.bundle?.name || value.request.bundle.version !== locator.bundle?.version) || value.candidate.preview && (value.candidate.preview.bundle.name !== locator.bundle?.name || value.candidate.preview.bundle.version !== locator.bundle?.version)) throw new Error("The candidate does not match this exact working copy, revision, Bundle pin and digest.");
|
|
164
|
+
setContentCandidate(value);
|
|
165
|
+
}).catch((failure) => {
|
|
166
|
+
if (!stopped && active()) setContentError(message(failure));
|
|
167
|
+
}).finally(() => {
|
|
168
|
+
if (!stopped && active()) setContentLoading(false);
|
|
169
|
+
});
|
|
170
|
+
return () => {
|
|
171
|
+
stopped = true;
|
|
172
|
+
};
|
|
173
|
+
}, [
|
|
174
|
+
locatorKey,
|
|
175
|
+
presentation,
|
|
176
|
+
connected,
|
|
177
|
+
active,
|
|
178
|
+
id
|
|
179
|
+
]);
|
|
180
|
+
const update = (invocationId, change) => {
|
|
181
|
+
if (!active()) return;
|
|
182
|
+
setAttempt((value) => value?.request.invocationId === invocationId ? change(value) : value);
|
|
183
|
+
};
|
|
184
|
+
async function reconcile(value) {
|
|
185
|
+
if (!active()) return;
|
|
186
|
+
const reading = ++reads.current;
|
|
187
|
+
const valid = () => active() && reads.current === reading;
|
|
188
|
+
try {
|
|
189
|
+
let ref = value.reference;
|
|
190
|
+
if (!ref) {
|
|
191
|
+
const refs = (await initial.current.listReferences()).filter((ref) => sameReference(ref, value.request));
|
|
192
|
+
if (!valid()) return;
|
|
193
|
+
if (refs.length !== 1) throw new Error(text("This request is not yet uniquely recoverable. Check again; do not send it again.", "暂时无法唯一确认这次请求。请再次核对,不要重复发送。"));
|
|
194
|
+
ref = refs[0];
|
|
195
|
+
update(value.request.invocationId, (old) => ({
|
|
196
|
+
...old,
|
|
197
|
+
reference: ref
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
const [observed, prepared] = await Promise.allSettled([initial.current.observe(address(ref)), initial.current.readPrepared(address(ref))]);
|
|
201
|
+
if (!valid()) return;
|
|
202
|
+
if (prepared.status === "fulfilled" && prepared.value && !matchesPrepared(prepared.value, value.request)) throw new Error("The candidate belongs to a different source revision or request.");
|
|
203
|
+
const observation = observed.status === "fulfilled" && sameRun(observed.value.reference, ref) ? observed.value : void 0;
|
|
204
|
+
update(value.request.invocationId, (old) => {
|
|
205
|
+
const previous = old.observation;
|
|
206
|
+
const next = observation && (!previous || observation.throughSeq >= previous.throughSeq && (!previous.terminal || observation.terminal)) ? observation : previous;
|
|
207
|
+
return {
|
|
208
|
+
...old,
|
|
209
|
+
reference: ref,
|
|
210
|
+
observation: next,
|
|
211
|
+
prepared: prepared.status === "fulfilled" ? prepared.value : old.prepared,
|
|
212
|
+
owned: prepared.status === "fulfilled",
|
|
213
|
+
uncertain: prepared.status === "rejected" || !observation || observation.status === "unknown",
|
|
214
|
+
error: prepared.status === "rejected" ? text("This connection cannot retrieve the owned candidate or control this run. Reopen to verify its outcome.", "当前连接无法取得候选或控制这次运行,请重新打开以核对结果。") : !observation ? text("Execution status could not be checked. An available candidate can still be reviewed; execution will not be resent.", "执行状态尚未核实。已取得的候选仍可审阅,不会重发执行。") : void 0
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
} catch (failure) {
|
|
218
|
+
if (valid()) update(value.request.invocationId, (old) => ({
|
|
219
|
+
...old,
|
|
220
|
+
uncertain: true,
|
|
221
|
+
owned: false,
|
|
222
|
+
error: message(failure)
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const invocationId = attempt?.request.invocationId, nativeKey = attempt?.reference?.epoch;
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (presentation !== "full" || !connected || !invocationId || busy) return;
|
|
229
|
+
let stopped = false, timer;
|
|
230
|
+
const tick = async () => {
|
|
231
|
+
const value = attemptRef.current;
|
|
232
|
+
if (!value || stopped) return;
|
|
233
|
+
await reconcile(value);
|
|
234
|
+
const latest = attemptRef.current;
|
|
235
|
+
if (!stopped && active() && (!latest?.observation?.terminal || latest.uncertain)) timer = setTimeout(() => {
|
|
236
|
+
tick();
|
|
237
|
+
}, 1200);
|
|
238
|
+
};
|
|
239
|
+
tick();
|
|
240
|
+
return () => {
|
|
241
|
+
stopped = true;
|
|
242
|
+
reads.current++;
|
|
243
|
+
clearTimeout(timer);
|
|
244
|
+
};
|
|
245
|
+
}, [
|
|
246
|
+
presentation,
|
|
247
|
+
connected,
|
|
248
|
+
invocationId,
|
|
249
|
+
nativeKey,
|
|
250
|
+
busy
|
|
251
|
+
]);
|
|
252
|
+
const sourceReady = (value) => !value.dirty || !!review && !!value.proposalReplacement?.canReplace() && review.request.id === value.record?.id && review.request.expectedRevision === value.record?.revision;
|
|
253
|
+
async function begin(snapshot) {
|
|
254
|
+
if (busyRef.current || contentLoading || !active() || !instances.saveChanges || !sourceReady(snapshot) || snapshot.busy || !snapshot.record || !intent.trim()) return;
|
|
255
|
+
const prior = attemptRef.current, record = snapshot.record;
|
|
256
|
+
const submittedIntentVersion = intentVersion.current;
|
|
257
|
+
const request = {
|
|
258
|
+
invocationId: crypto.randomUUID(),
|
|
259
|
+
id: record.id,
|
|
260
|
+
expectedRevision: record.revision,
|
|
261
|
+
paths: record.files?.filter((file) => file.editable).slice(0, 64).map((file) => file.path) ?? [],
|
|
262
|
+
bundle: snapshot.previewBundle ?? record.bundle ?? record.source.bundle,
|
|
263
|
+
prompt: intent
|
|
264
|
+
};
|
|
265
|
+
if (record.kind === "bundle" && !request.bundle || !request.paths.length || request.paths.length > 64) return;
|
|
266
|
+
if (prior && (!prior.reference || prior.uncertain || !prior.owned || !prior.observation?.terminal || !prior.observation.consumed || prior.observation.pendingInput || intent === prior.request.prompt)) return;
|
|
267
|
+
busyRef.current = true;
|
|
268
|
+
setBusy(true);
|
|
269
|
+
setError("");
|
|
270
|
+
const token = ++sequence.current;
|
|
271
|
+
const valid = () => active() && token === sequence.current;
|
|
272
|
+
try {
|
|
273
|
+
const now = editor.current;
|
|
274
|
+
if (!sourceReady(now) || now.busy || now.record?.id !== record.id || now.record.revision !== record.revision) throw new Error(text("Your source changed before authoring started. Save or discard edits, then send a new request.", "开始编辑前原始文件发生了修改。请先保存或放弃编辑,再明确发送新请求。"));
|
|
275
|
+
const value = {
|
|
276
|
+
request,
|
|
277
|
+
intentVersion: submittedIntentVersion,
|
|
278
|
+
owned: false,
|
|
279
|
+
uncertain: true
|
|
280
|
+
};
|
|
281
|
+
if (prior) setHistory((old) => [...old, conversationEntry(prior, reviewSaved)]);
|
|
282
|
+
if (review) reviewInvocation.current = request.invocationId;
|
|
283
|
+
attemptRef.current = value;
|
|
284
|
+
setAttempt(value);
|
|
285
|
+
setReviewSaved(false);
|
|
286
|
+
try {
|
|
287
|
+
const ref = prior?.reference ? await initial.current.resume({
|
|
288
|
+
...request,
|
|
289
|
+
previous: address(prior.reference),
|
|
290
|
+
explicit: true
|
|
291
|
+
}) : await initial.current.start(request);
|
|
292
|
+
if (!valid()) return;
|
|
293
|
+
if (!sameReference(ref, request)) throw new Error("The native response belongs to a different request.");
|
|
294
|
+
const accepted = {
|
|
295
|
+
...value,
|
|
296
|
+
reference: ref
|
|
297
|
+
};
|
|
298
|
+
attemptRef.current = accepted;
|
|
299
|
+
setAttempt(accepted);
|
|
300
|
+
await reconcile(accepted);
|
|
301
|
+
} catch (failure) {
|
|
302
|
+
if (!valid()) return;
|
|
303
|
+
update(request.invocationId, (old) => ({
|
|
304
|
+
...old,
|
|
305
|
+
error: text("The response was not confirmed. Check this request before taking another action. ", "响应尚未确认,请先核对这次请求。") + message(failure),
|
|
306
|
+
uncertain: true
|
|
307
|
+
}));
|
|
308
|
+
await reconcile(value);
|
|
309
|
+
}
|
|
310
|
+
} catch (failure) {
|
|
311
|
+
if (valid()) setError(message(failure));
|
|
312
|
+
} finally {
|
|
313
|
+
if (valid()) {
|
|
314
|
+
busyRef.current = false;
|
|
315
|
+
setBusy(false);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function cancel() {
|
|
320
|
+
const value = attemptRef.current;
|
|
321
|
+
if (busyRef.current || !active() || !value?.reference || !value.owned || value.uncertain || !value.observation || value.observation.terminal || !["queued", "running"].includes(value.observation.status)) return;
|
|
322
|
+
busyRef.current = true;
|
|
323
|
+
reads.current++;
|
|
324
|
+
setBusy(true);
|
|
325
|
+
const token = ++sequence.current;
|
|
326
|
+
try {
|
|
327
|
+
const observed = await initial.current.cancel(address(value.reference), { expectedTurn: value.observation.turn });
|
|
328
|
+
if (!active() || token !== sequence.current) return;
|
|
329
|
+
if (!sameRun(observed.reference, value.reference)) throw new Error("The cancellation response belongs to a different request.");
|
|
330
|
+
update(value.request.invocationId, (old) => ({
|
|
331
|
+
...old,
|
|
332
|
+
observation: observed,
|
|
333
|
+
uncertain: observed.status === "unknown",
|
|
334
|
+
error: void 0
|
|
335
|
+
}));
|
|
336
|
+
} catch (failure) {
|
|
337
|
+
update(value.request.invocationId, (old) => ({
|
|
338
|
+
...old,
|
|
339
|
+
uncertain: true,
|
|
340
|
+
owned: false,
|
|
341
|
+
error: message(failure)
|
|
342
|
+
}));
|
|
343
|
+
} finally {
|
|
344
|
+
if (active() && token === sequence.current) {
|
|
345
|
+
busyRef.current = false;
|
|
346
|
+
setBusy(false);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const preview = (value, _adopt, options) => renderPreview ? renderPreview(value, () => {}, {
|
|
351
|
+
...options,
|
|
352
|
+
readOnly: true
|
|
353
|
+
}) : /* @__PURE__ */ jsx(PanelSamplePreview, {
|
|
354
|
+
skill: value.bundle.name,
|
|
355
|
+
panelYaml: value.panelYaml,
|
|
356
|
+
sample: value.sample,
|
|
357
|
+
locale: zh ? "zh-CN" : "en-US"
|
|
358
|
+
});
|
|
359
|
+
const contentMatches = (value) => {
|
|
360
|
+
const record = value.record, locator = preparedLocator;
|
|
361
|
+
if (!record || !locator || !contentCandidate || record.id !== locator.id || record.revision !== locator.expectedRevision || contentCandidate.request.expectedRevision !== locator.expectedRevision || contentCandidate.candidate.digest !== locator.digest) return false;
|
|
362
|
+
const pin = locator.bundle;
|
|
363
|
+
return record.kind === "bundle" ? record.bundle?.name === pin?.name && record.bundle?.version === pin?.version : pin === void 0 ? contentCandidate.request.bundle === void 0 : record.mounts.some((mount) => mount.bundle.name === pin.name && mount.bundle.version === pin.version);
|
|
364
|
+
};
|
|
365
|
+
const openContentReview = () => {
|
|
366
|
+
if (!active() || !contentCandidate || !contentMatches(editor.current) || editor.current.dirty || editor.current.busy || busy || !instances.saveChanges || savedRequest === preparedLocator?.requestId) return;
|
|
367
|
+
reviewRequest.current = preparedLocator?.requestId;
|
|
368
|
+
openedRequest.current = locatorKey;
|
|
369
|
+
setReview(structuredClone(contentCandidate));
|
|
370
|
+
setReviewSaved(false);
|
|
371
|
+
};
|
|
372
|
+
useEffect(() => {
|
|
373
|
+
if (!preparedLocator || openedRequest.current === locatorKey || review || attempt || !active() || !contentCandidate || !contentMatches(snapshot) || snapshot.dirty || snapshot.busy || busy || !instances.saveChanges || savedRequest === preparedLocator.requestId) return;
|
|
374
|
+
openedRequest.current = locatorKey;
|
|
375
|
+
reviewRequest.current = preparedLocator.requestId;
|
|
376
|
+
setReview(structuredClone(contentCandidate));
|
|
377
|
+
setReviewSaved(false);
|
|
378
|
+
}, [
|
|
379
|
+
presentation,
|
|
380
|
+
locatorKey,
|
|
381
|
+
contentCandidate,
|
|
382
|
+
snapshot,
|
|
383
|
+
connected,
|
|
384
|
+
busy,
|
|
385
|
+
review,
|
|
386
|
+
attempt,
|
|
387
|
+
savedRequest,
|
|
388
|
+
active,
|
|
389
|
+
instances
|
|
390
|
+
]);
|
|
391
|
+
const latestMatches = (current) => !!attempt?.owned && !!attempt.prepared && current.record?.id === attempt.prepared.request.id && current.record.revision === attempt.prepared.request.expectedRevision && matchesPrepared(attempt.prepared, attempt.request);
|
|
392
|
+
const newerCandidate = !reviewSaved && !!attempt?.prepared && (review ? reviewInvocation.current === attempt.request.invocationId && review.candidate.digest !== attempt.prepared.candidate.digest : snapshot.dirty);
|
|
393
|
+
const reviewLatest = () => {
|
|
394
|
+
const current = editor.current;
|
|
395
|
+
if (!active() || busyRef.current || current.busy || !instances.saveChanges || !latestMatches(current) || !attempt?.prepared) return;
|
|
396
|
+
if (newerCandidate ? !current.proposalReplacement?.discard() : current.dirty) return;
|
|
397
|
+
reviewRequest.current = void 0;
|
|
398
|
+
reviewInvocation.current = attempt.request.invocationId;
|
|
399
|
+
if (fullCandidateKey) openedFullCandidates.current.add(fullCandidateKey);
|
|
400
|
+
setReview(structuredClone(attempt.prepared));
|
|
401
|
+
setReviewSaved(false);
|
|
402
|
+
};
|
|
403
|
+
useEffect(() => {
|
|
404
|
+
if (presentation !== "full" || !fullCandidateKey || (review ? review.candidate.digest === attempt?.prepared?.candidate.digest : openedFullCandidates.current.has(fullCandidateKey)) || !active() || busyRef.current || !instances.saveChanges || !attempt?.owned || !attempt.prepared) return;
|
|
405
|
+
const current = editor.current, prepared = attempt.prepared;
|
|
406
|
+
if (current.busy || !latestMatches(current) || !current.proposalReplacement?.canReplace() || review && (reviewInvocation.current !== attempt.request.invocationId || !matchesPrepared(review, attempt.request))) return;
|
|
407
|
+
openedFullCandidates.current.add(fullCandidateKey);
|
|
408
|
+
reviewRequest.current = void 0;
|
|
409
|
+
reviewInvocation.current = attempt.request.invocationId;
|
|
410
|
+
setReview(structuredClone(prepared));
|
|
411
|
+
setReviewSaved(false);
|
|
412
|
+
}, [
|
|
413
|
+
presentation,
|
|
414
|
+
fullCandidateKey,
|
|
415
|
+
review,
|
|
416
|
+
attempt,
|
|
417
|
+
snapshot,
|
|
418
|
+
busy,
|
|
419
|
+
active,
|
|
420
|
+
instances
|
|
421
|
+
]);
|
|
422
|
+
return /* @__PURE__ */ jsxs("main", {
|
|
423
|
+
className: `ygd-studio-main${presentation === "full" ? " ygd-studio-authoring-layout" : ""}`,
|
|
424
|
+
children: [presentation === "full" && /* @__PURE__ */ jsx(AuthoringControls, {
|
|
425
|
+
snapshot,
|
|
426
|
+
zh,
|
|
427
|
+
intent,
|
|
428
|
+
setIntent: (value) => {
|
|
429
|
+
intentVersion.current++;
|
|
430
|
+
setIntent(value);
|
|
431
|
+
},
|
|
432
|
+
attempt,
|
|
433
|
+
history,
|
|
434
|
+
busy: busy || contentLoading,
|
|
435
|
+
connected,
|
|
436
|
+
error,
|
|
437
|
+
hasSave: !!instances.saveChanges,
|
|
438
|
+
saved: reviewSaved,
|
|
439
|
+
reviewing: !!review,
|
|
440
|
+
canReopen: !!fullCandidateKey && openedFullCandidates.current.has(fullCandidateKey),
|
|
441
|
+
newerCandidate,
|
|
442
|
+
begin: () => {
|
|
443
|
+
begin(editor.current);
|
|
444
|
+
},
|
|
445
|
+
cancel: () => {
|
|
446
|
+
cancel();
|
|
447
|
+
},
|
|
448
|
+
check: () => {
|
|
449
|
+
if (attempt) reconcile(attempt);
|
|
450
|
+
},
|
|
451
|
+
review: reviewLatest
|
|
452
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
453
|
+
className: "ygd-studio-authoring-content",
|
|
454
|
+
"aria-label": text("Working copy content", "工作副本内容"),
|
|
455
|
+
children: /* @__PURE__ */ jsx(InstanceWorkbench, {
|
|
456
|
+
client: instances,
|
|
457
|
+
id,
|
|
458
|
+
locale,
|
|
459
|
+
onBack,
|
|
460
|
+
embedded: true,
|
|
461
|
+
initialView: "preview",
|
|
462
|
+
sourceChanges: review ? {
|
|
463
|
+
prepared: review,
|
|
464
|
+
client: reviewClient,
|
|
465
|
+
onDismiss: () => {
|
|
466
|
+
if (fullCandidateKey) openedFullCandidates.current.add(fullCandidateKey);
|
|
467
|
+
setReview(void 0);
|
|
468
|
+
},
|
|
469
|
+
onSaved: (record) => {
|
|
470
|
+
const submitted = attemptRef.current;
|
|
471
|
+
const savedCurrentProposal = !submitted || !!submitted.prepared && matchesPrepared(review, submitted.request) && submitted.prepared.candidate.digest === review.candidate.digest;
|
|
472
|
+
setReviewSaved(savedCurrentProposal);
|
|
473
|
+
setSavedRequest(reviewRequest.current);
|
|
474
|
+
setReview(void 0);
|
|
475
|
+
onChanged(record);
|
|
476
|
+
}
|
|
477
|
+
} : void 0,
|
|
478
|
+
onChanged: (record) => {
|
|
479
|
+
setReview(void 0);
|
|
480
|
+
onChanged(record);
|
|
481
|
+
},
|
|
482
|
+
initialSelection,
|
|
483
|
+
onExport,
|
|
484
|
+
renderPreview: preview,
|
|
485
|
+
onDirtyChange: editorDirty,
|
|
486
|
+
renderSupplement: (value) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EditorSnapshot, {
|
|
487
|
+
value,
|
|
488
|
+
onChange: syncEditor
|
|
489
|
+
}), preparedLocator && !attempt && (!review || !connected || contentLoading || contentError) && /* @__PURE__ */ jsxs("div", {
|
|
490
|
+
className: "ygd-studio-doc-toolbar",
|
|
491
|
+
children: [
|
|
492
|
+
/* @__PURE__ */ jsx("p", {
|
|
493
|
+
role: "status",
|
|
494
|
+
children: savedRequest === preparedLocator.requestId ? text("Candidate saved to the working copy.", "候选已保存到工作副本。") : !connected ? text("The connection changed. Reopen to verify this candidate.", "连接已变化,请重新打开以核对此候选。") : contentLoading ? text("Checking proposed changes…", "正在核对修改建议…") : review ? "" : value.dirty || value.busy ? text("Save or discard source edits before reviewing changes.", "请先保存或放弃原始文件编辑,再审阅修改。") : contentCandidate && !contentMatches(value) ? text("The working copy or Bundle pin has changed. Reopen the current proposal.", "工作副本或技能包版本已变化,请重新打开当前建议。") : contentCandidate ? text(`${contentCandidate.candidate.diff.length} files changed. Review before saving.`, `涉及 ${contentCandidate.candidate.diff.length} 个文件,审阅后可保存。`) : ""
|
|
495
|
+
}),
|
|
496
|
+
contentError && /* @__PURE__ */ jsx(StudioFeedback, {
|
|
497
|
+
feedbackId: `content:${contentError}`,
|
|
498
|
+
tone: "error",
|
|
499
|
+
locale,
|
|
500
|
+
title: text("Candidate could not be opened", "未能打开候选"),
|
|
501
|
+
message: contentError
|
|
502
|
+
}),
|
|
503
|
+
!review && contentCandidate && openedRequest.current === locatorKey && savedRequest !== preparedLocator.requestId && /* @__PURE__ */ jsx("button", {
|
|
504
|
+
type: "button",
|
|
505
|
+
className: "ygd-studio-btn-primary",
|
|
506
|
+
disabled: !connected || !contentMatches(value) || value.dirty || value.busy || busy || !instances.saveChanges,
|
|
507
|
+
onClick: openContentReview,
|
|
508
|
+
children: text("Review proposed changes", "审阅修改建议")
|
|
509
|
+
})
|
|
510
|
+
]
|
|
511
|
+
})] })
|
|
512
|
+
})
|
|
513
|
+
})]
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
function EditorSnapshot({ value, onChange }) {
|
|
517
|
+
useLayoutEffect(() => {
|
|
518
|
+
onChange(value);
|
|
519
|
+
}, [
|
|
520
|
+
value.record,
|
|
521
|
+
value.dirty,
|
|
522
|
+
value.busy,
|
|
523
|
+
value.previewBundle,
|
|
524
|
+
value.proposalReplacement,
|
|
525
|
+
onChange
|
|
526
|
+
]);
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
function AuthoringControls({ snapshot, zh, intent, setIntent, attempt, history, busy, connected, error, hasSave, saved, reviewing, canReopen, newerCandidate, begin, cancel, check, review }) {
|
|
530
|
+
const log = useRef(null), composer = useRef(null), followTail = useRef(true);
|
|
531
|
+
useEffect(() => {
|
|
532
|
+
if (log.current) log.current.scrollTop = log.current.scrollHeight;
|
|
533
|
+
}, [attempt?.request.invocationId]);
|
|
534
|
+
useLayoutEffect(() => {
|
|
535
|
+
if (followTail.current && log.current) log.current.scrollTop = log.current.scrollHeight;
|
|
536
|
+
}, [JSON.stringify(attempt?.observation?.conversation), attempt?.observation?.status]);
|
|
537
|
+
const t = (en, cn) => zh ? cn : en;
|
|
538
|
+
const record = snapshot.record, count = record?.files?.filter((file) => file.editable).length ?? 0;
|
|
539
|
+
const unavailable = !connected ? t("The connection changed. Input is retained; reopen this working copy to reconnect.", "连接已变化,输入已保留;请重新打开此副本以连接。") : !record ? t("Opening the working copy…", "正在打开工作副本…") : record.kind === "bundle" && !(snapshot.previewBundle ?? record.bundle ?? record.source.bundle) ? t("The Bundle definition is unavailable. Reopen this working copy.", "技能包定义不可用,请重新打开此副本。") : !hasSave ? t("This connection cannot save reviewed candidates.", "当前连接不支持保存审阅后的候选。") : count === 0 ? t("This copy has no editable source files.", "此副本没有可编辑的原始文件。") : "";
|
|
540
|
+
const stale = !saved && !!attempt?.prepared && attempt.prepared.request.expectedRevision !== record?.revision;
|
|
541
|
+
const resume = !!attempt && !!attempt.reference && !attempt.uncertain && attempt.owned && !!attempt.observation?.terminal && attempt.observation.consumed && !attempt.observation.pendingInput && intent !== attempt.request.prompt;
|
|
542
|
+
const working = connected && !!attempt?.observation && !attempt.observation.terminal && (attempt.observation.status === "running" || attempt.observation.status === "queued");
|
|
543
|
+
const stoppable = working && !!attempt?.reference && attempt.owned && !attempt.uncertain;
|
|
544
|
+
const needsCheck = connected && !!attempt && (attempt.uncertain || !attempt.observation || !attempt.owned);
|
|
545
|
+
const status = !connected ? t("Connection changed; execution status is unverified.", "连接已变化,执行状态尚未核实。") : !attempt ? t("Describe a change to this copy. Suggestions are reviewed before saving.", "描述对此副本的修改意图,建议将在审阅后保存。") : attempt.uncertain ? t("The request outcome needs checking. No automatic resend will occur.", "这次请求的结果尚需核对,不会自动重发。") : {
|
|
546
|
+
queued: t("Queued", "等待执行"),
|
|
547
|
+
running: t("Working", "正在处理"),
|
|
548
|
+
completed: t("Execution finished", "执行已结束"),
|
|
549
|
+
cancelled: t("Execution cancelled", "执行已取消"),
|
|
550
|
+
failed: t("Execution failed", "执行失败"),
|
|
551
|
+
unknown: t("Outcome unknown", "结果未知")
|
|
552
|
+
}[attempt.observation?.status ?? "unknown"];
|
|
553
|
+
const pristineReview = reviewing && !!snapshot.proposalReplacement?.canReplace() && !stale;
|
|
554
|
+
const canContinue = !unavailable && !snapshot.busy && !busy && (!snapshot.dirty || pristineReview);
|
|
555
|
+
const canSend = canContinue && !!intent.trim() && (!attempt || resume);
|
|
556
|
+
const continueRepair = () => {
|
|
557
|
+
if (!intent.trim() || intent === attempt?.request.prompt) {
|
|
558
|
+
const issues = attempt?.prepared?.candidate.diagnostics.map((item) => item.message).join("\n");
|
|
559
|
+
setIntent(t("Fix the issues in the current proposed changes. Preserve the original Skill method and resources, and submit the complete corrected proposal for review.", "请修复当前修改建议中的问题,保留原有 Skill 方法和资源,提交完整的修正建议供我审阅。") + (issues ? "\n\n" + issues : ""));
|
|
560
|
+
}
|
|
561
|
+
composer.current?.focus();
|
|
562
|
+
};
|
|
563
|
+
const entry = attempt ? conversationEntry(attempt, saved) : void 0;
|
|
564
|
+
const renderEntry = (value, current) => /* @__PURE__ */ jsxs("div", {
|
|
565
|
+
className: "ygd-studio-conversation-turn",
|
|
566
|
+
children: [
|
|
567
|
+
/* @__PURE__ */ jsx(ConversationMessage, {
|
|
568
|
+
row: {
|
|
569
|
+
id: value.id,
|
|
570
|
+
role: "user",
|
|
571
|
+
label: t("You", "你"),
|
|
572
|
+
text: value.prompt,
|
|
573
|
+
status: "completed"
|
|
574
|
+
},
|
|
575
|
+
renderText: (text) => /* @__PURE__ */ jsx("p", {
|
|
576
|
+
style: { whiteSpace: "pre-wrap" },
|
|
577
|
+
children: text
|
|
578
|
+
})
|
|
579
|
+
}),
|
|
580
|
+
!!value.omitted && /* @__PURE__ */ jsx("p", {
|
|
581
|
+
className: "ygd-studio-conversation-note",
|
|
582
|
+
children: t("Showing recent activity.", "显示最近的工作记录。")
|
|
583
|
+
}),
|
|
584
|
+
/* @__PURE__ */ jsx(ConversationMessages, {
|
|
585
|
+
rows: value.nativeRows ?? [],
|
|
586
|
+
active: current && working,
|
|
587
|
+
terminal: !current || !!attempt?.observation?.terminal,
|
|
588
|
+
locale: zh ? "zh" : "en"
|
|
589
|
+
}),
|
|
590
|
+
/* @__PURE__ */ jsxs("div", {
|
|
591
|
+
className: "ygd-studio-conversation-message",
|
|
592
|
+
children: [
|
|
593
|
+
/* @__PURE__ */ jsxs("p", {
|
|
594
|
+
role: current ? "status" : void 0,
|
|
595
|
+
children: [
|
|
596
|
+
current && working && /* @__PURE__ */ jsx("span", {
|
|
597
|
+
className: "companion-spinner",
|
|
598
|
+
"aria-hidden": "true"
|
|
599
|
+
}),
|
|
600
|
+
current ? status : value.uncertain ? t("Outcome needs checking.", "结果待核对。") : {
|
|
601
|
+
queued: t("Queued", "等待执行"),
|
|
602
|
+
running: t("Working", "正在处理"),
|
|
603
|
+
completed: t("Execution finished", "执行已结束"),
|
|
604
|
+
cancelled: t("Execution cancelled", "执行已取消"),
|
|
605
|
+
failed: t("Execution failed", "执行失败"),
|
|
606
|
+
unknown: t("Outcome unknown", "结果未知")
|
|
607
|
+
}[value.status ?? "unknown"],
|
|
608
|
+
value.saved ? t(" · Changes saved to the working copy.", " · 修改已保存到工作副本。") : value.candidateValid ? t(" · Changes available for review; not saved.", " · 修改可审阅,尚未保存。") : ""
|
|
609
|
+
]
|
|
610
|
+
}),
|
|
611
|
+
value.candidateValid && value.candidateFiles !== void 0 && /* @__PURE__ */ jsx("p", {
|
|
612
|
+
className: "ygd-studio-conversation-note",
|
|
613
|
+
children: t(`${value.candidateFiles} files changed. Review their effect in the content area.`, `涉及 ${value.candidateFiles} 个文件,可在右侧审阅修改效果。`)
|
|
614
|
+
}),
|
|
615
|
+
current && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
616
|
+
newerCandidate && /* @__PURE__ */ jsx("p", {
|
|
617
|
+
className: "ygd-studio-conversation-note",
|
|
618
|
+
children: t("Newer proposed changes are available. Your current edits are retained; review the latest to replace them.", "有更新的修改建议。当前编辑已保留;选择审阅最新建议后可替换。")
|
|
619
|
+
}),
|
|
620
|
+
/* @__PURE__ */ jsxs("div", {
|
|
621
|
+
className: "ygd-studio-conversation-actions",
|
|
622
|
+
children: [
|
|
623
|
+
newerCandidate && /* @__PURE__ */ jsx("button", {
|
|
624
|
+
type: "button",
|
|
625
|
+
className: "ygd-studio-btn-primary",
|
|
626
|
+
disabled: !connected || busy || snapshot.busy || !attempt?.owned || stale || !hasSave || !snapshot.proposalReplacement?.canDiscard(),
|
|
627
|
+
onClick: review,
|
|
628
|
+
children: t("Review latest changes", "审阅最新建议")
|
|
629
|
+
}),
|
|
630
|
+
canReopen && !reviewing && !newerCandidate && attempt?.prepared && !saved && /* @__PURE__ */ jsx("button", {
|
|
631
|
+
type: "button",
|
|
632
|
+
className: "ygd-studio-btn-primary",
|
|
633
|
+
disabled: !connected || busy || reviewing || snapshot.dirty || snapshot.busy || !attempt.owned || stale || !hasSave,
|
|
634
|
+
onClick: review,
|
|
635
|
+
children: t("Review proposed changes", "审阅修改建议")
|
|
636
|
+
}),
|
|
637
|
+
needsCheck && !attempt?.error && /* @__PURE__ */ jsx("button", {
|
|
638
|
+
type: "button",
|
|
639
|
+
className: "ygd-studio-btn-secondary",
|
|
640
|
+
disabled: busy,
|
|
641
|
+
onClick: check,
|
|
642
|
+
children: t("Check request status", "核对请求结果")
|
|
643
|
+
})
|
|
644
|
+
]
|
|
645
|
+
}),
|
|
646
|
+
attempt?.observation?.terminal && !attempt.prepared && /* @__PURE__ */ jsx("p", {
|
|
647
|
+
className: "ygd-studio-conversation-note",
|
|
648
|
+
children: t("No changes are available for review.", "暂无可审阅的修改。")
|
|
649
|
+
}),
|
|
650
|
+
attempt?.error && /* @__PURE__ */ jsx(StudioFeedback, {
|
|
651
|
+
feedbackId: `run:${value.id}:${attempt.error}`,
|
|
652
|
+
tone: "error",
|
|
653
|
+
locale: zh ? "zh" : "en",
|
|
654
|
+
title: t("The request needs checking", "需要核对这次请求"),
|
|
655
|
+
message: attempt.error,
|
|
656
|
+
actions: connected ? [{
|
|
657
|
+
label: t("Check request status", "核对请求结果"),
|
|
658
|
+
onClick: check,
|
|
659
|
+
disabled: busy
|
|
660
|
+
}] : void 0
|
|
661
|
+
}),
|
|
662
|
+
!attempt?.error && !attempt?.uncertain && attempt?.observation?.terminal && !saved && (!attempt.prepared || !attempt.prepared.candidate.valid) && /* @__PURE__ */ jsx(StudioFeedback, {
|
|
663
|
+
feedbackId: `repair:${value.id}:${attempt.prepared?.candidate.digest ?? "none"}`,
|
|
664
|
+
tone: "error",
|
|
665
|
+
locale: zh ? "zh" : "en",
|
|
666
|
+
title: attempt.prepared ? t("Changes need repair before saving", "修改仍需完善后才能保存") : t("Execution ended without a candidate", "执行结束,尚无候选"),
|
|
667
|
+
message: t("Your input and available changes are retained. Describe the repair below and send it to continue this conversation.", "输入和已有修改均已保留。在下方描述修复诉求并发送,即可继续本次对话。"),
|
|
668
|
+
details: attempt.prepared?.candidate.diagnostics.map((item) => ({ message: item.message })),
|
|
669
|
+
actions: [{
|
|
670
|
+
label: t("Continue repair", "继续修复"),
|
|
671
|
+
disabled: !canContinue || !attempt.reference || !attempt.owned || !attempt.observation.consumed || attempt.observation.pendingInput,
|
|
672
|
+
onClick: continueRepair
|
|
673
|
+
}]
|
|
674
|
+
})
|
|
675
|
+
] })
|
|
676
|
+
]
|
|
677
|
+
})
|
|
678
|
+
]
|
|
679
|
+
}, value.id);
|
|
680
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
681
|
+
className: "ygd-studio-conversation",
|
|
682
|
+
"aria-label": t("AI changes", "智能修改"),
|
|
683
|
+
children: [
|
|
684
|
+
/* @__PURE__ */ jsxs("header", {
|
|
685
|
+
className: "ygd-studio-conversation-header",
|
|
686
|
+
children: [/* @__PURE__ */ jsx("h2", { children: t("Conversation", "对话") }), /* @__PURE__ */ jsx("p", { children: t("Describe your intent and review the result alongside it.", "表达修改诉求,对照右侧内容逐步完善。") })]
|
|
687
|
+
}),
|
|
688
|
+
/* @__PURE__ */ jsxs("div", {
|
|
689
|
+
ref: log,
|
|
690
|
+
className: "ygd-studio-conversation-log",
|
|
691
|
+
onScroll: (event) => {
|
|
692
|
+
const el = event.currentTarget;
|
|
693
|
+
followTail.current = el.scrollHeight - el.scrollTop - el.clientHeight < 32;
|
|
694
|
+
},
|
|
695
|
+
role: "log",
|
|
696
|
+
"aria-live": "off",
|
|
697
|
+
"aria-label": t("Requests in this editor", "本次编辑中的对话"),
|
|
698
|
+
children: [
|
|
699
|
+
!entry && /* @__PURE__ */ jsxs("div", {
|
|
700
|
+
className: "ygd-studio-conversation-welcome",
|
|
701
|
+
children: [/* @__PURE__ */ jsx("strong", { children: record?.kind === "kit" ? t("What would make this Kit work better for you?", "你希望怎样调整这个设计套件?") : t("What would make this Bundle work better for you?", "你希望怎样调整这个技能包?") }), /* @__PURE__ */ jsx("p", { children: record?.kind === "kit" ? t("Describe its purpose, design method or Bundle selection. Changes will be proposed for review before saving.", "可以描述套件用途、设计方法或技能包选择。修改会先供你审阅,确认后再保存。") : t("Describe its method, fields or Panel. Changes will be proposed for review before saving.", "可以描述设计方法、字段或面板的调整。修改会先供你审阅,确认后再保存。") })]
|
|
702
|
+
}),
|
|
703
|
+
history.map((value) => renderEntry(value, false)),
|
|
704
|
+
entry && renderEntry(entry, true)
|
|
705
|
+
]
|
|
706
|
+
}),
|
|
707
|
+
/* @__PURE__ */ jsxs("form", {
|
|
708
|
+
className: "ygd-studio-conversation-composer",
|
|
709
|
+
onSubmit: (event) => {
|
|
710
|
+
event.preventDefault();
|
|
711
|
+
if (canSend) begin();
|
|
712
|
+
},
|
|
713
|
+
children: [
|
|
714
|
+
reviewing && /* @__PURE__ */ jsx("p", {
|
|
715
|
+
className: "ygd-studio-conversation-note",
|
|
716
|
+
children: pristineReview ? t("Review the changes on the right. When execution ends, send a new intent to refine them.", "在右侧审阅修改;执行结束后,可发送新诉求继续完善。") : t("Your source edits are retained. Save them or revert them before asking AI to replace the proposal.", "原始文件编辑已保留;保存或还原人工修改后,可请求 AI 更新建议。")
|
|
717
|
+
}),
|
|
718
|
+
unavailable && /* @__PURE__ */ jsx(StudioFeedback, {
|
|
719
|
+
feedbackId: `connection:${unavailable}`,
|
|
720
|
+
tone: "info",
|
|
721
|
+
dismissible: false,
|
|
722
|
+
locale: zh ? "zh" : "en",
|
|
723
|
+
title: !connected ? t("Connection changed; reopen this working copy to reconnect.", "连接已变化,请重新打开此副本以连接。") : t("AI connection unavailable", "AI 连接暂不可用"),
|
|
724
|
+
message: unavailable
|
|
725
|
+
}),
|
|
726
|
+
!reviewing && (snapshot.dirty || snapshot.busy) && /* @__PURE__ */ jsx("p", {
|
|
727
|
+
className: "ygd-studio-conversation-note",
|
|
728
|
+
children: t("Save or discard source edits before requesting or reviewing changes.", "请先保存或放弃原始文件编辑,再请求或审阅修改。")
|
|
729
|
+
}),
|
|
730
|
+
stale && /* @__PURE__ */ jsx("p", {
|
|
731
|
+
className: "ygd-studio-conversation-note",
|
|
732
|
+
children: t("The source has changed since this candidate. Reopen to verify the saved source, or explicitly continue with a new intent.", "此候选生成后原始文件已变化。请重新打开核对已保存内容,或明确用新意图继续。")
|
|
733
|
+
}),
|
|
734
|
+
error && /* @__PURE__ */ jsx(StudioFeedback, {
|
|
735
|
+
feedbackId: `dispatch:${error}`,
|
|
736
|
+
tone: "error",
|
|
737
|
+
locale: zh ? "zh" : "en",
|
|
738
|
+
title: t("Request could not be sent", "未能发送请求"),
|
|
739
|
+
message: error
|
|
740
|
+
}),
|
|
741
|
+
/* @__PURE__ */ jsxs("label", {
|
|
742
|
+
className: "ygd-studio-field",
|
|
743
|
+
children: [/* @__PURE__ */ jsx("span", { children: t("What would you like to change?", "你希望修改什么?") }), /* @__PURE__ */ jsx("textarea", {
|
|
744
|
+
ref: composer,
|
|
745
|
+
className: "ygd-studio-text-input ygd-studio-textarea",
|
|
746
|
+
rows: 3,
|
|
747
|
+
value: intent,
|
|
748
|
+
placeholder: t("Describe a change…", "描述希望如何修改…"),
|
|
749
|
+
onChange: (event) => setIntent(event.target.value),
|
|
750
|
+
onKeyDown: (event) => {
|
|
751
|
+
if ((event.ctrlKey || event.metaKey) && event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
752
|
+
event.preventDefault();
|
|
753
|
+
if (canSend) begin();
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
})]
|
|
757
|
+
}),
|
|
758
|
+
/* @__PURE__ */ jsxs("div", {
|
|
759
|
+
className: "ygd-studio-conversation-send",
|
|
760
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
761
|
+
className: "ygd-studio-conversation-note",
|
|
762
|
+
children: t("Ctrl / ⌘ + Enter to send", "Ctrl / ⌘ + Enter 发送")
|
|
763
|
+
}), stoppable ? /* @__PURE__ */ jsx("button", {
|
|
764
|
+
type: "button",
|
|
765
|
+
className: "ygd-studio-btn-primary",
|
|
766
|
+
disabled: busy,
|
|
767
|
+
onClick: cancel,
|
|
768
|
+
children: t("Stop", "停止")
|
|
769
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
770
|
+
type: "submit",
|
|
771
|
+
className: "ygd-studio-btn-primary",
|
|
772
|
+
disabled: !canSend,
|
|
773
|
+
children: t("Send", "发送")
|
|
774
|
+
})]
|
|
775
|
+
})
|
|
776
|
+
]
|
|
777
|
+
})
|
|
778
|
+
]
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
//#endregion
|
|
782
|
+
export { StudioAuthoringPromptContext, StudioAuthoringWorkbench };
|