@spotpatch/runtime 1.12.0 → 1.14.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/dist/{chunk-BXRMVHNT.js → chunk-ASRUAKA5.js} +1 -1
- package/dist/chunk-ASRUAKA5.js.map +1 -0
- package/dist/chunk-KIDHJQRG.js +14 -0
- package/dist/chunk-KIDHJQRG.js.map +1 -0
- package/dist/{chunk-7ES63LA7.js → chunk-P6X75LO5.js} +11 -1
- package/dist/chunk-P6X75LO5.js.map +1 -0
- package/dist/chunk-P7W3F4UA.js +115 -0
- package/dist/chunk-P7W3F4UA.js.map +1 -0
- package/dist/contextual-ask-panel.cjs +1637 -0
- package/dist/contextual-ask-panel.cjs.map +1 -0
- package/dist/contextual-ask-panel.d.cts +163 -0
- package/dist/contextual-ask-panel.d.ts +163 -0
- package/dist/contextual-ask-panel.js +1593 -0
- package/dist/contextual-ask-panel.js.map +1 -0
- package/dist/data-flow-panel.cjs +9 -0
- package/dist/data-flow-panel.cjs.map +1 -1
- package/dist/data-flow-panel.js +1 -1
- package/dist/external-handoff-panel.cjs +29 -9
- package/dist/external-handoff-panel.cjs.map +1 -1
- package/dist/external-handoff-panel.d.cts +3 -3
- package/dist/external-handoff-panel.d.ts +3 -3
- package/dist/external-handoff-panel.js +22 -11
- package/dist/external-handoff-panel.js.map +1 -1
- package/dist/index.cjs +1285 -279
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1250 -356
- package/dist/index.js.map +1 -1
- package/dist/motion-extension-contract-B3m26CML.d.cts +72 -0
- package/dist/motion-extension-contract-B3m26CML.d.ts +72 -0
- package/dist/motion.cjs +1251 -0
- package/dist/motion.cjs.map +1 -0
- package/dist/motion.d.cts +11 -0
- package/dist/motion.d.ts +11 -0
- package/dist/motion.js +1092 -0
- package/dist/motion.js.map +1 -0
- package/package.json +31 -3
- package/dist/chunk-7ES63LA7.js.map +0 -1
- package/dist/chunk-BXRMVHNT.js.map +0 -1
|
@@ -0,0 +1,1593 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getContextualAskExtension,
|
|
3
|
+
registerContextualAskExtension
|
|
4
|
+
} from "./chunk-KIDHJQRG.js";
|
|
5
|
+
import {
|
|
6
|
+
createButton,
|
|
7
|
+
createMarkedElement
|
|
8
|
+
} from "./chunk-P6X75LO5.js";
|
|
9
|
+
|
|
10
|
+
// src/controller/contextual-ask-workflow.ts
|
|
11
|
+
import {
|
|
12
|
+
CONTEXTUAL_ASK_SCHEMA_VERSION as CONTEXTUAL_ASK_SCHEMA_VERSION2
|
|
13
|
+
} from "@spotpatch/shared/contextual-ask-browser";
|
|
14
|
+
|
|
15
|
+
// src/api/contextual-ask-api.ts
|
|
16
|
+
import {
|
|
17
|
+
SPOTPATCH_ENDPOINTS,
|
|
18
|
+
SPOTPATCH_TOKEN_HEADER,
|
|
19
|
+
getAskJobEndpoint,
|
|
20
|
+
isErrorCode as isErrorCode2
|
|
21
|
+
} from "@spotpatch/shared/contextual-ask-browser";
|
|
22
|
+
|
|
23
|
+
// src/api/contextual-ask-response-validation.ts
|
|
24
|
+
import {
|
|
25
|
+
ASK_ANSWER_WARNING_CODES,
|
|
26
|
+
ASK_FILE_COUNT_BUCKETS,
|
|
27
|
+
ASK_JOB_STATUSES,
|
|
28
|
+
ASK_READ_ACTIVITY_STATES,
|
|
29
|
+
ASK_SOURCE_CONFIDENCES,
|
|
30
|
+
CONTEXTUAL_ASK_EXECUTOR_KINDS,
|
|
31
|
+
CONTEXTUAL_ASK_EXECUTOR_STATES,
|
|
32
|
+
CONTEXTUAL_ASK_SCHEMA_VERSION,
|
|
33
|
+
isErrorCode
|
|
34
|
+
} from "@spotpatch/shared/contextual-ask-browser";
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
function hasOnlyKeys(value, required, optional = []) {
|
|
39
|
+
const keys = Object.keys(value);
|
|
40
|
+
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
41
|
+
return required.every((key) => key in value) && keys.every((key) => allowed.has(key));
|
|
42
|
+
}
|
|
43
|
+
function isEnum(value, values) {
|
|
44
|
+
return typeof value === "string" && values.includes(value);
|
|
45
|
+
}
|
|
46
|
+
function isNonEmptyString(value) {
|
|
47
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
48
|
+
}
|
|
49
|
+
function isOpaqueId(value) {
|
|
50
|
+
return typeof value === "string" && value.length > 0 && value.length <= 128 && /^[A-Za-z0-9_-]+$/u.test(value);
|
|
51
|
+
}
|
|
52
|
+
function isTimestamp(value) {
|
|
53
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
54
|
+
}
|
|
55
|
+
function isPositiveInteger(value) {
|
|
56
|
+
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
57
|
+
}
|
|
58
|
+
function isAskDomainError(value) {
|
|
59
|
+
return isErrorCode(value) && value.startsWith("ASK_");
|
|
60
|
+
}
|
|
61
|
+
function isStringArray(value) {
|
|
62
|
+
return Array.isArray(value) && value.every((item) => isOpaqueId(item));
|
|
63
|
+
}
|
|
64
|
+
function unique(values) {
|
|
65
|
+
return new Set(values).size === values.length;
|
|
66
|
+
}
|
|
67
|
+
function isExecutorCapability(value) {
|
|
68
|
+
if (!isRecord(value) || !hasOnlyKeys(
|
|
69
|
+
value,
|
|
70
|
+
[
|
|
71
|
+
"executorId",
|
|
72
|
+
"kind",
|
|
73
|
+
"label",
|
|
74
|
+
"requestedModelLabel",
|
|
75
|
+
"effectiveModelLabel",
|
|
76
|
+
"state",
|
|
77
|
+
"providerDataConsentRequired",
|
|
78
|
+
"readOnlyProven"
|
|
79
|
+
],
|
|
80
|
+
["errorCode"]
|
|
81
|
+
) || !isOpaqueId(value.executorId) || !isEnum(value.kind, CONTEXTUAL_ASK_EXECUTOR_KINDS) || !isNonEmptyString(value.label) || !isNonEmptyString(value.requestedModelLabel) || !isNonEmptyString(value.effectiveModelLabel) || !isEnum(value.state, CONTEXTUAL_ASK_EXECUTOR_STATES) || typeof value.providerDataConsentRequired !== "boolean" || typeof value.readOnlyProven !== "boolean" || value.errorCode !== void 0 && !isAskDomainError(value.errorCode)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return value.state === "ready" ? value.readOnlyProven && value.errorCode === void 0 : value.errorCode !== void 0;
|
|
85
|
+
}
|
|
86
|
+
function isContextualAskCapability(value) {
|
|
87
|
+
if (!isRecord(value) || !hasOnlyKeys(value, [
|
|
88
|
+
"schemaVersion",
|
|
89
|
+
"enabled",
|
|
90
|
+
"executors",
|
|
91
|
+
"safety",
|
|
92
|
+
"checkedAt"
|
|
93
|
+
]) || value.schemaVersion !== CONTEXTUAL_ASK_SCHEMA_VERSION || typeof value.enabled !== "boolean" || !Array.isArray(value.executors) || !value.executors.every(isExecutorCapability) || !isRecord(value.safety) || !hasOnlyKeys(value.safety, [
|
|
94
|
+
"selectionRequired",
|
|
95
|
+
"singleTurn",
|
|
96
|
+
"writesAllowed",
|
|
97
|
+
"historyStored"
|
|
98
|
+
]) || value.safety.selectionRequired !== true || value.safety.singleTurn !== true || value.safety.writesAllowed !== false || value.safety.historyStored !== false || !isTimestamp(value.checkedAt)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const ids = value.executors.map((executor) => executor.executorId);
|
|
102
|
+
return (value.enabled || value.executors.length === 0) && unique(ids);
|
|
103
|
+
}
|
|
104
|
+
function isExecutorSnapshot(value) {
|
|
105
|
+
return isRecord(value) && hasOnlyKeys(value, ["executorId", "kind", "label", "modelLabel"]) && isOpaqueId(value.executorId) && isEnum(value.kind, CONTEXTUAL_ASK_EXECUTOR_KINDS) && isNonEmptyString(value.label) && isNonEmptyString(value.modelLabel);
|
|
106
|
+
}
|
|
107
|
+
function isAskJobSnapshot(value) {
|
|
108
|
+
if (!isRecord(value) || !hasOnlyKeys(
|
|
109
|
+
value,
|
|
110
|
+
[
|
|
111
|
+
"schemaVersion",
|
|
112
|
+
"jobId",
|
|
113
|
+
"selectionId",
|
|
114
|
+
"status",
|
|
115
|
+
"executor",
|
|
116
|
+
"createdAt",
|
|
117
|
+
"updatedAt",
|
|
118
|
+
"canCancel"
|
|
119
|
+
],
|
|
120
|
+
["phaseMessage", "errorCode"]
|
|
121
|
+
) || value.schemaVersion !== CONTEXTUAL_ASK_SCHEMA_VERSION || !isOpaqueId(value.jobId) || !isOpaqueId(value.selectionId) || !isEnum(value.status, ASK_JOB_STATUSES) || !isExecutorSnapshot(value.executor) || !isTimestamp(value.createdAt) || !isTimestamp(value.updatedAt) || Date.parse(value.updatedAt) < Date.parse(value.createdAt) || typeof value.canCancel !== "boolean" || value.phaseMessage !== void 0 && !isNonEmptyString(value.phaseMessage) || value.errorCode !== void 0 && !isAskDomainError(value.errorCode)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const cancellable = ["queued", "authorizing", "running"].includes(value.status);
|
|
125
|
+
if (value.canCancel !== cancellable) return false;
|
|
126
|
+
if (value.status === "cancelled") return value.errorCode === "ASK_CANCELLED";
|
|
127
|
+
if (value.status === "failed") {
|
|
128
|
+
return value.errorCode !== void 0 && value.errorCode !== "ASK_CANCELLED";
|
|
129
|
+
}
|
|
130
|
+
return value.errorCode === void 0;
|
|
131
|
+
}
|
|
132
|
+
function isSourceReference(value) {
|
|
133
|
+
if (!isRecord(value) || !hasOnlyKeys(
|
|
134
|
+
value,
|
|
135
|
+
[
|
|
136
|
+
"sourceId",
|
|
137
|
+
"label",
|
|
138
|
+
"relativePath",
|
|
139
|
+
"fileId",
|
|
140
|
+
"startLine",
|
|
141
|
+
"endLine",
|
|
142
|
+
"confidence",
|
|
143
|
+
"targetIds",
|
|
144
|
+
"contentHash"
|
|
145
|
+
],
|
|
146
|
+
["sourceVersion"]
|
|
147
|
+
) || !isOpaqueId(value.sourceId) || !isNonEmptyString(value.label) || !isNonEmptyString(value.relativePath) || value.relativePath.startsWith("/") || value.relativePath.includes("\\") || value.relativePath.split("/").some((part) => part === "" || part === "." || part === "..") || !isOpaqueId(value.fileId) || !isPositiveInteger(value.startLine) || !isPositiveInteger(value.endLine) || value.endLine < value.startLine || !isEnum(value.confidence, ASK_SOURCE_CONFIDENCES) || !isStringArray(value.targetIds) || value.targetIds.length === 0 || !unique(value.targetIds) || value.sourceVersion !== void 0 && !isNonEmptyString(value.sourceVersion) || typeof value.contentHash !== "string" || !/^[a-f0-9]{64}$/u.test(value.contentHash)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
function isAnswerWarning(value) {
|
|
153
|
+
return isRecord(value) && hasOnlyKeys(value, ["code"]) && isEnum(value.code, ASK_ANSWER_WARNING_CODES);
|
|
154
|
+
}
|
|
155
|
+
function isAnswerBlock(value) {
|
|
156
|
+
if (!isRecord(value) || typeof value.kind !== "string") return false;
|
|
157
|
+
if (value.kind === "paragraph") {
|
|
158
|
+
return hasOnlyKeys(value, ["kind", "text", "sourceIds"]) && isNonEmptyString(value.text) && isStringArray(value.sourceIds);
|
|
159
|
+
}
|
|
160
|
+
if (value.kind === "code") {
|
|
161
|
+
return hasOnlyKeys(value, ["kind", "code", "sourceIds"], ["language"]) && typeof value.code === "string" && value.code.length > 0 && isStringArray(value.sourceIds) && (value.language === void 0 || isNonEmptyString(value.language));
|
|
162
|
+
}
|
|
163
|
+
if (value.kind !== "list" || !hasOnlyKeys(value, ["kind", "items"])) return false;
|
|
164
|
+
return Array.isArray(value.items) && value.items.length > 0 && value.items.every(
|
|
165
|
+
(item) => isRecord(item) && hasOnlyKeys(item, ["text", "sourceIds"]) && isNonEmptyString(item.text) && isStringArray(item.sourceIds)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
function isAnswerResult(value) {
|
|
169
|
+
if (!isRecord(value) || !hasOnlyKeys(value, [
|
|
170
|
+
"schemaVersion",
|
|
171
|
+
"jobId",
|
|
172
|
+
"selectionId",
|
|
173
|
+
"contextHash",
|
|
174
|
+
"executor",
|
|
175
|
+
"blocks",
|
|
176
|
+
"sources",
|
|
177
|
+
"warnings",
|
|
178
|
+
"createdAt",
|
|
179
|
+
"expiresAt"
|
|
180
|
+
]) || value.schemaVersion !== CONTEXTUAL_ASK_SCHEMA_VERSION || !isOpaqueId(value.jobId) || !isOpaqueId(value.selectionId) || typeof value.contextHash !== "string" || !/^[a-f0-9]{64}$/u.test(value.contextHash) || !isExecutorSnapshot(value.executor) || !Array.isArray(value.blocks) || value.blocks.length === 0 || !value.blocks.every(isAnswerBlock) || !Array.isArray(value.sources) || !value.sources.every(isSourceReference) || !Array.isArray(value.warnings) || !value.warnings.every(isAnswerWarning) || !isTimestamp(value.createdAt) || !isTimestamp(value.expiresAt) || Date.parse(value.expiresAt) <= Date.parse(value.createdAt)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const sources = value.sources.filter(isSourceReference);
|
|
184
|
+
const blocks = value.blocks.filter(isAnswerBlock);
|
|
185
|
+
const answerWarnings = value.warnings.filter(isAnswerWarning);
|
|
186
|
+
const sourceIds = sources.map((source) => source.sourceId);
|
|
187
|
+
if (!unique(sourceIds)) return false;
|
|
188
|
+
const available = new Set(sourceIds);
|
|
189
|
+
const references = blocks.flatMap(
|
|
190
|
+
(block) => block.kind === "list" ? block.items.flatMap((item) => item.sourceIds) : block.sourceIds
|
|
191
|
+
);
|
|
192
|
+
return references.every((sourceId) => available.has(sourceId)) && sourceIds.every((sourceId) => references.includes(sourceId)) && (references.length > 0 || answerWarnings.some((warning) => warning.code === "insufficient-evidence"));
|
|
193
|
+
}
|
|
194
|
+
function isAskJobResultResponse(value) {
|
|
195
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["snapshot"], ["result"]) || !isAskJobSnapshot(value.snapshot) || value.result !== void 0 && !isAnswerResult(value.result)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
const answered = value.snapshot.status === "answered";
|
|
199
|
+
return answered === (value.result !== void 0) && (value.result === void 0 || value.result.jobId === value.snapshot.jobId && value.result.selectionId === value.snapshot.selectionId && value.result.executor.executorId === value.snapshot.executor.executorId);
|
|
200
|
+
}
|
|
201
|
+
function isAskJobEvent(value) {
|
|
202
|
+
if (!isRecord(value) || !hasOnlyKeys(
|
|
203
|
+
value,
|
|
204
|
+
["schemaVersion", "sequence", "jobId", "status", "timestamp", "type"],
|
|
205
|
+
["snapshot", "message", "activity", "state", "errorCode"]
|
|
206
|
+
) || value.schemaVersion !== CONTEXTUAL_ASK_SCHEMA_VERSION || !isPositiveInteger(value.sequence) || !isOpaqueId(value.jobId) || !isEnum(value.status, ASK_JOB_STATUSES) || !isTimestamp(value.timestamp)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (value.type === "snapshot") {
|
|
210
|
+
return hasOnlyKeys(value, [
|
|
211
|
+
"schemaVersion",
|
|
212
|
+
"sequence",
|
|
213
|
+
"jobId",
|
|
214
|
+
"status",
|
|
215
|
+
"timestamp",
|
|
216
|
+
"type",
|
|
217
|
+
"snapshot"
|
|
218
|
+
]) && isAskJobSnapshot(value.snapshot) && value.snapshot.jobId === value.jobId && value.snapshot.status === value.status;
|
|
219
|
+
}
|
|
220
|
+
if (value.type === "phase") {
|
|
221
|
+
return hasOnlyKeys(value, [
|
|
222
|
+
"schemaVersion",
|
|
223
|
+
"sequence",
|
|
224
|
+
"jobId",
|
|
225
|
+
"status",
|
|
226
|
+
"timestamp",
|
|
227
|
+
"type",
|
|
228
|
+
"message"
|
|
229
|
+
]) && isNonEmptyString(value.message) && !["answered", "cancelled", "failed"].includes(value.status);
|
|
230
|
+
}
|
|
231
|
+
if (value.type === "answer-ready")
|
|
232
|
+
return hasOnlyKeys(value, [
|
|
233
|
+
"schemaVersion",
|
|
234
|
+
"sequence",
|
|
235
|
+
"jobId",
|
|
236
|
+
"status",
|
|
237
|
+
"timestamp",
|
|
238
|
+
"type"
|
|
239
|
+
]) && value.status === "answered";
|
|
240
|
+
if (value.type === "error") {
|
|
241
|
+
return hasOnlyKeys(value, [
|
|
242
|
+
"schemaVersion",
|
|
243
|
+
"sequence",
|
|
244
|
+
"jobId",
|
|
245
|
+
"status",
|
|
246
|
+
"timestamp",
|
|
247
|
+
"type",
|
|
248
|
+
"errorCode"
|
|
249
|
+
]) && isAskDomainError(value.errorCode) && (value.status === "cancelled" && value.errorCode === "ASK_CANCELLED" || value.status === "failed" && value.errorCode !== "ASK_CANCELLED");
|
|
250
|
+
}
|
|
251
|
+
if (value.type !== "read-activity" || !hasOnlyKeys(value, [
|
|
252
|
+
"schemaVersion",
|
|
253
|
+
"sequence",
|
|
254
|
+
"jobId",
|
|
255
|
+
"status",
|
|
256
|
+
"timestamp",
|
|
257
|
+
"type",
|
|
258
|
+
"activity",
|
|
259
|
+
"state"
|
|
260
|
+
]) || value.status !== "running" || !isRecord(value.activity) || !isEnum(value.state, ASK_READ_ACTIVITY_STATES))
|
|
261
|
+
return false;
|
|
262
|
+
return value.activity.kind === "source" ? hasOnlyKeys(value.activity, ["kind", "sourceId", "relativePath"]) && isOpaqueId(value.activity.sourceId) && isNonEmptyString(value.activity.relativePath) : value.activity.kind === "file-count" && hasOnlyKeys(value.activity, ["kind", "bucket"]) && isEnum(value.activity.bucket, ASK_FILE_COUNT_BUCKETS);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/api/contextual-ask-api.ts
|
|
266
|
+
var MAX_JSON_RESPONSE_BYTES = 2e6;
|
|
267
|
+
var MAX_EVENT_LINE_BYTES = 1e5;
|
|
268
|
+
var MAX_EVENT_STREAM_BYTES = 4e6;
|
|
269
|
+
var ContextualAskApiError = class extends Error {
|
|
270
|
+
constructor(code) {
|
|
271
|
+
super("SpotPatch Contextual Ask request failed.");
|
|
272
|
+
this.code = code;
|
|
273
|
+
this.name = "ContextualAskApiError";
|
|
274
|
+
}
|
|
275
|
+
code;
|
|
276
|
+
};
|
|
277
|
+
function isRecord2(value) {
|
|
278
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
279
|
+
}
|
|
280
|
+
function deepFreezeUnknown(value) {
|
|
281
|
+
if (!isRecord2(value) && !Array.isArray(value) || Object.isFrozen(value)) return;
|
|
282
|
+
for (const nested of Object.values(value)) deepFreezeUnknown(nested);
|
|
283
|
+
Object.freeze(value);
|
|
284
|
+
}
|
|
285
|
+
function deepFreeze(value) {
|
|
286
|
+
deepFreezeUnknown(value);
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
function parseJson(text) {
|
|
290
|
+
try {
|
|
291
|
+
return JSON.parse(text);
|
|
292
|
+
} catch {
|
|
293
|
+
throw new ContextualAskApiError();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function readBoundedText(response, maximumBytes) {
|
|
297
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
298
|
+
if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
|
|
299
|
+
await response.body?.cancel();
|
|
300
|
+
throw new ContextualAskApiError();
|
|
301
|
+
}
|
|
302
|
+
if (response.body === null) throw new ContextualAskApiError();
|
|
303
|
+
const reader = response.body.getReader();
|
|
304
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
305
|
+
let bytes = 0;
|
|
306
|
+
let output = "";
|
|
307
|
+
try {
|
|
308
|
+
for (; ; ) {
|
|
309
|
+
const chunk = await reader.read();
|
|
310
|
+
if (chunk.done) return output + decoder.decode();
|
|
311
|
+
bytes += chunk.value.byteLength;
|
|
312
|
+
if (bytes > maximumBytes) {
|
|
313
|
+
await reader.cancel();
|
|
314
|
+
throw new ContextualAskApiError();
|
|
315
|
+
}
|
|
316
|
+
output += decoder.decode(chunk.value, { stream: true });
|
|
317
|
+
}
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (error instanceof ContextualAskApiError || error instanceof DOMException && error.name === "AbortError") {
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
throw new ContextualAskApiError();
|
|
323
|
+
} finally {
|
|
324
|
+
reader.releaseLock();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async function readEnvelope(response) {
|
|
328
|
+
const payload = parseJson(await readBoundedText(response, MAX_JSON_RESPONSE_BYTES));
|
|
329
|
+
if (!isRecord2(payload)) throw new ContextualAskApiError();
|
|
330
|
+
if (!response.ok) {
|
|
331
|
+
const code = payload.ok === false && isRecord2(payload.error) && isErrorCode2(payload.error.code) ? payload.error.code : void 0;
|
|
332
|
+
throw new ContextualAskApiError(code);
|
|
333
|
+
}
|
|
334
|
+
if (payload.ok !== true || !("data" in payload)) throw new ContextualAskApiError();
|
|
335
|
+
return payload.data;
|
|
336
|
+
}
|
|
337
|
+
function contextualAskApiErrorCode(error) {
|
|
338
|
+
return error instanceof ContextualAskApiError ? error.code : void 0;
|
|
339
|
+
}
|
|
340
|
+
function createContextualAskApi(options) {
|
|
341
|
+
const pending = /* @__PURE__ */ new Set();
|
|
342
|
+
function cancelPending() {
|
|
343
|
+
for (const controller of pending) controller.abort();
|
|
344
|
+
pending.clear();
|
|
345
|
+
}
|
|
346
|
+
async function request(endpoint, method, body) {
|
|
347
|
+
const controller = new AbortController();
|
|
348
|
+
pending.add(controller);
|
|
349
|
+
try {
|
|
350
|
+
const response = await options.fetch(endpoint, {
|
|
351
|
+
method,
|
|
352
|
+
headers: {
|
|
353
|
+
...body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
354
|
+
[SPOTPATCH_TOKEN_HEADER]: options.sessionToken
|
|
355
|
+
},
|
|
356
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
357
|
+
signal: controller.signal
|
|
358
|
+
});
|
|
359
|
+
return await readEnvelope(response);
|
|
360
|
+
} finally {
|
|
361
|
+
pending.delete(controller);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async function events(jobId, afterSequence, onEvent) {
|
|
365
|
+
const controller = new AbortController();
|
|
366
|
+
pending.add(controller);
|
|
367
|
+
try {
|
|
368
|
+
const response = await options.fetch(
|
|
369
|
+
getAskJobEndpoint(jobId, "events", { afterSequence }),
|
|
370
|
+
{
|
|
371
|
+
method: "GET",
|
|
372
|
+
headers: { [SPOTPATCH_TOKEN_HEADER]: options.sessionToken },
|
|
373
|
+
signal: controller.signal
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
if (!response.ok) {
|
|
377
|
+
await readEnvelope(response);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (response.body === null) throw new ContextualAskApiError();
|
|
381
|
+
const reader = response.body.getReader();
|
|
382
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
383
|
+
let buffer = "";
|
|
384
|
+
let totalBytes = 0;
|
|
385
|
+
let previousSequence = afterSequence;
|
|
386
|
+
const consumeLine = (line) => {
|
|
387
|
+
if (line.trim().length === 0) return;
|
|
388
|
+
if (new TextEncoder().encode(line).byteLength > MAX_EVENT_LINE_BYTES) {
|
|
389
|
+
throw new ContextualAskApiError();
|
|
390
|
+
}
|
|
391
|
+
const event = parseJson(line);
|
|
392
|
+
if (!isAskJobEvent(event) || event.jobId !== jobId || event.sequence <= previousSequence) {
|
|
393
|
+
throw new ContextualAskApiError();
|
|
394
|
+
}
|
|
395
|
+
previousSequence = event.sequence;
|
|
396
|
+
onEvent(deepFreeze(event));
|
|
397
|
+
};
|
|
398
|
+
try {
|
|
399
|
+
for (; ; ) {
|
|
400
|
+
const chunk = await reader.read();
|
|
401
|
+
if (chunk.done) {
|
|
402
|
+
consumeLine(buffer + decoder.decode());
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
totalBytes += chunk.value.byteLength;
|
|
406
|
+
if (totalBytes > MAX_EVENT_STREAM_BYTES) {
|
|
407
|
+
await reader.cancel();
|
|
408
|
+
throw new ContextualAskApiError();
|
|
409
|
+
}
|
|
410
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
411
|
+
const lines = buffer.split("\n");
|
|
412
|
+
buffer = lines.pop() ?? "";
|
|
413
|
+
if (new TextEncoder().encode(buffer).byteLength > MAX_EVENT_LINE_BYTES) {
|
|
414
|
+
await reader.cancel();
|
|
415
|
+
throw new ContextualAskApiError();
|
|
416
|
+
}
|
|
417
|
+
for (const line of lines) consumeLine(line);
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
if (error instanceof ContextualAskApiError || error instanceof DOMException && error.name === "AbortError") {
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
throw new ContextualAskApiError();
|
|
424
|
+
} finally {
|
|
425
|
+
reader.releaseLock();
|
|
426
|
+
}
|
|
427
|
+
} finally {
|
|
428
|
+
pending.delete(controller);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return Object.freeze({
|
|
432
|
+
async capability() {
|
|
433
|
+
const data = await request(SPOTPATCH_ENDPOINTS.askCapability, "GET");
|
|
434
|
+
if (!isContextualAskCapability(data)) throw new ContextualAskApiError();
|
|
435
|
+
return deepFreeze(data);
|
|
436
|
+
},
|
|
437
|
+
async cancelJob(jobId) {
|
|
438
|
+
const data = await request(getAskJobEndpoint(jobId, "cancel"), "POST", {});
|
|
439
|
+
if (!isAskJobSnapshot(data) || data.jobId !== jobId) {
|
|
440
|
+
throw new ContextualAskApiError();
|
|
441
|
+
}
|
|
442
|
+
return deepFreeze(data);
|
|
443
|
+
},
|
|
444
|
+
cancelPending,
|
|
445
|
+
async createJob(requestBody) {
|
|
446
|
+
const data = await request(SPOTPATCH_ENDPOINTS.askJobs, "POST", requestBody);
|
|
447
|
+
if (!isAskJobSnapshot(data) || data.selectionId !== requestBody.envelope.selection.selectionId || data.executor.executorId !== requestBody.executorId) {
|
|
448
|
+
throw new ContextualAskApiError();
|
|
449
|
+
}
|
|
450
|
+
return deepFreeze(data);
|
|
451
|
+
},
|
|
452
|
+
dispose: cancelPending,
|
|
453
|
+
events,
|
|
454
|
+
async result(jobId) {
|
|
455
|
+
const data = await request(getAskJobEndpoint(jobId, "result"), "GET");
|
|
456
|
+
if (!isAskJobResultResponse(data) || data.snapshot.jobId !== jobId) {
|
|
457
|
+
throw new ContextualAskApiError();
|
|
458
|
+
}
|
|
459
|
+
return deepFreeze(data);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/controller/contextual-ask-workflow.ts
|
|
465
|
+
function isAbortError(error) {
|
|
466
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
467
|
+
}
|
|
468
|
+
function createContextualAskWorkflow(input) {
|
|
469
|
+
let mounted = false;
|
|
470
|
+
let revision = 0;
|
|
471
|
+
let busy = false;
|
|
472
|
+
let currentSnapshot;
|
|
473
|
+
let currentResult;
|
|
474
|
+
let currentQuestion = "";
|
|
475
|
+
let capabilityLoaded = false;
|
|
476
|
+
let composing = false;
|
|
477
|
+
const consentByExecutor = /* @__PURE__ */ new Map();
|
|
478
|
+
const api = input.api ?? createContextualAskApi({
|
|
479
|
+
fetch: input.fetch,
|
|
480
|
+
sessionToken: input.sessionToken
|
|
481
|
+
});
|
|
482
|
+
function isCurrent(requestRevision) {
|
|
483
|
+
return mounted && revision === requestRevision;
|
|
484
|
+
}
|
|
485
|
+
function createEnvelope(question) {
|
|
486
|
+
const selection = input.getSelection();
|
|
487
|
+
if (question.length === 0 || question.length > 4e3 || selection === void 0) {
|
|
488
|
+
return void 0;
|
|
489
|
+
}
|
|
490
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
491
|
+
return {
|
|
492
|
+
schemaVersion: CONTEXTUAL_ASK_SCHEMA_VERSION2,
|
|
493
|
+
taskId: input.createId(),
|
|
494
|
+
selection: {
|
|
495
|
+
schemaVersion: CONTEXTUAL_ASK_SCHEMA_VERSION2,
|
|
496
|
+
selectionId: input.createId(),
|
|
497
|
+
locale: selection.locale,
|
|
498
|
+
targets: selection.targets.map((snapshot) => ({
|
|
499
|
+
targetId: snapshot.id,
|
|
500
|
+
page: snapshot.page,
|
|
501
|
+
source: snapshot.source,
|
|
502
|
+
react: {
|
|
503
|
+
...snapshot.react,
|
|
504
|
+
componentStack: [...snapshot.react.componentStack]
|
|
505
|
+
},
|
|
506
|
+
element: snapshot.element,
|
|
507
|
+
styles: {
|
|
508
|
+
...snapshot.styles,
|
|
509
|
+
classNames: [...snapshot.styles.classNames],
|
|
510
|
+
matchedRules: snapshot.styles.matchedRules.map((rule) => ({ ...rule })),
|
|
511
|
+
computed: { ...snapshot.styles.computed },
|
|
512
|
+
warnings: [...snapshot.styles.warnings]
|
|
513
|
+
},
|
|
514
|
+
...snapshot.code === void 0 ? {} : { code: snapshot.code },
|
|
515
|
+
warnings: []
|
|
516
|
+
})),
|
|
517
|
+
createdAt
|
|
518
|
+
},
|
|
519
|
+
task: { kind: "ask", question },
|
|
520
|
+
createdAt
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
async function loadCapability() {
|
|
524
|
+
if (capabilityLoaded) return;
|
|
525
|
+
const requestRevision = revision;
|
|
526
|
+
input.panel.renderCapability(void 0);
|
|
527
|
+
try {
|
|
528
|
+
const capability = await api.capability();
|
|
529
|
+
if (!isCurrent(requestRevision)) return;
|
|
530
|
+
capabilityLoaded = true;
|
|
531
|
+
input.panel.renderCapability(capability);
|
|
532
|
+
const executorId = input.panel.readExecutorId();
|
|
533
|
+
input.panel.setConsent(
|
|
534
|
+
executorId !== void 0 && consentByExecutor.get(executorId) === true
|
|
535
|
+
);
|
|
536
|
+
} catch (error) {
|
|
537
|
+
if (!isCurrent(requestRevision) || isAbortError(error)) return;
|
|
538
|
+
input.panel.renderError(contextualAskApiErrorCode(error));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function handleEvent(event, requestRevision) {
|
|
542
|
+
if (!isCurrent(requestRevision)) return;
|
|
543
|
+
input.panel.renderEvent(event);
|
|
544
|
+
if (event.type === "snapshot") currentSnapshot = event.snapshot;
|
|
545
|
+
}
|
|
546
|
+
async function submit() {
|
|
547
|
+
if (busy) return;
|
|
548
|
+
const question = input.panel.readQuestion();
|
|
549
|
+
const executorId = input.panel.readExecutorId();
|
|
550
|
+
const envelope = createEnvelope(question);
|
|
551
|
+
if (question.length === 0 || executorId === void 0 || !input.panel.readConsent() || envelope === void 0) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const requestRevision = ++revision;
|
|
555
|
+
currentQuestion = question;
|
|
556
|
+
busy = true;
|
|
557
|
+
input.onBusyChange(true);
|
|
558
|
+
currentResult = void 0;
|
|
559
|
+
currentSnapshot = void 0;
|
|
560
|
+
input.panel.setBusy(true);
|
|
561
|
+
try {
|
|
562
|
+
const snapshot = await api.createJob({
|
|
563
|
+
schemaVersion: CONTEXTUAL_ASK_SCHEMA_VERSION2,
|
|
564
|
+
requestId: input.createId(),
|
|
565
|
+
envelope,
|
|
566
|
+
executorId,
|
|
567
|
+
providerDataConsent: true
|
|
568
|
+
});
|
|
569
|
+
if (!isCurrent(requestRevision)) return;
|
|
570
|
+
currentSnapshot = snapshot;
|
|
571
|
+
input.panel.renderJob(snapshot);
|
|
572
|
+
await api.events(snapshot.jobId, 0, (event) => {
|
|
573
|
+
handleEvent(event, requestRevision);
|
|
574
|
+
});
|
|
575
|
+
if (!isCurrent(requestRevision)) return;
|
|
576
|
+
const response = await api.result(snapshot.jobId);
|
|
577
|
+
if (!isCurrent(requestRevision)) return;
|
|
578
|
+
currentSnapshot = response.snapshot;
|
|
579
|
+
busy = false;
|
|
580
|
+
input.onBusyChange(false);
|
|
581
|
+
if (response.result === void 0) {
|
|
582
|
+
input.panel.renderJob(response.snapshot);
|
|
583
|
+
if (response.snapshot.status === "failed") {
|
|
584
|
+
input.panel.renderError(response.snapshot.errorCode);
|
|
585
|
+
}
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
currentResult = response.result;
|
|
589
|
+
input.panel.renderAnswer(response.result, false);
|
|
590
|
+
} catch (error) {
|
|
591
|
+
if (!isCurrent(requestRevision) || isAbortError(error)) return;
|
|
592
|
+
busy = false;
|
|
593
|
+
input.onBusyChange(false);
|
|
594
|
+
input.panel.renderError(contextualAskApiErrorCode(error));
|
|
595
|
+
} finally {
|
|
596
|
+
if (isCurrent(requestRevision)) {
|
|
597
|
+
busy = false;
|
|
598
|
+
input.panel.setBusy(false);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
async function cancel() {
|
|
603
|
+
if (!busy || currentSnapshot?.canCancel !== true) return;
|
|
604
|
+
const requestRevision = revision;
|
|
605
|
+
try {
|
|
606
|
+
const snapshot = await api.cancelJob(currentSnapshot.jobId);
|
|
607
|
+
if (!isCurrent(requestRevision)) return;
|
|
608
|
+
currentSnapshot = snapshot;
|
|
609
|
+
busy = false;
|
|
610
|
+
input.onBusyChange(false);
|
|
611
|
+
input.panel.renderJob(snapshot);
|
|
612
|
+
input.panel.setBusy(false);
|
|
613
|
+
} catch (error) {
|
|
614
|
+
if (!isCurrent(requestRevision) || isAbortError(error)) return;
|
|
615
|
+
busy = false;
|
|
616
|
+
input.onBusyChange(false);
|
|
617
|
+
input.panel.renderError(contextualAskApiErrorCode(error));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function cancelPending() {
|
|
621
|
+
revision += 1;
|
|
622
|
+
const snapshot = currentSnapshot;
|
|
623
|
+
busy = false;
|
|
624
|
+
input.onBusyChange(false);
|
|
625
|
+
currentSnapshot = void 0;
|
|
626
|
+
api.cancelPending();
|
|
627
|
+
if (snapshot?.canCancel === true) {
|
|
628
|
+
void api.cancelJob(snapshot.jobId).catch(() => void 0);
|
|
629
|
+
}
|
|
630
|
+
input.panel.setBusy(false);
|
|
631
|
+
}
|
|
632
|
+
function newQuestion() {
|
|
633
|
+
if (busy) cancelPending();
|
|
634
|
+
revision += 1;
|
|
635
|
+
currentSnapshot = void 0;
|
|
636
|
+
currentResult = void 0;
|
|
637
|
+
currentQuestion = "";
|
|
638
|
+
input.panel.clear();
|
|
639
|
+
input.panel.focusQuestion();
|
|
640
|
+
}
|
|
641
|
+
function copyAnswer() {
|
|
642
|
+
const text = input.panel.answerPlainText();
|
|
643
|
+
if (text.length === 0 || input.clipboard === void 0) return;
|
|
644
|
+
void input.clipboard.writeText(text).then(() => {
|
|
645
|
+
input.panel.notify("copied");
|
|
646
|
+
}).catch(() => {
|
|
647
|
+
input.panel.notify("copy-failed");
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
function convert() {
|
|
651
|
+
if (currentResult === void 0 || busy) return;
|
|
652
|
+
const answerDigest = input.panel.answerPlainText().trim().slice(0, 4e3);
|
|
653
|
+
if (answerDigest.length === 0) return;
|
|
654
|
+
const origin = Object.freeze({
|
|
655
|
+
kind: "contextual-ask",
|
|
656
|
+
askJobId: currentResult.jobId,
|
|
657
|
+
question: currentQuestion,
|
|
658
|
+
answerDigest,
|
|
659
|
+
sourceIds: currentResult.sources.map((source) => source.sourceId)
|
|
660
|
+
});
|
|
661
|
+
input.onConvert(origin);
|
|
662
|
+
input.panel.setOrigin(origin);
|
|
663
|
+
input.panel.setMode("change");
|
|
664
|
+
input.panel.notify("converted");
|
|
665
|
+
}
|
|
666
|
+
function openSource(event) {
|
|
667
|
+
const sourceId = input.panel.selectedSourceId(event.target);
|
|
668
|
+
if (sourceId === void 0) return;
|
|
669
|
+
const source = input.panel.sourceById(sourceId);
|
|
670
|
+
if (source !== void 0) {
|
|
671
|
+
void input.onOpenSource(source).catch(() => {
|
|
672
|
+
input.panel.notify("source-open-failed");
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function keydown(event) {
|
|
677
|
+
if (event.target !== input.panel.questionInput || event.key !== "Enter" || composing || event.isComposing || !event.metaKey && !event.ctrlKey) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
event.preventDefault();
|
|
681
|
+
void submit();
|
|
682
|
+
}
|
|
683
|
+
function executorChanged() {
|
|
684
|
+
const executorId = input.panel.readExecutorId();
|
|
685
|
+
input.panel.setConsent(
|
|
686
|
+
executorId !== void 0 && consentByExecutor.get(executorId) === true
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
function compositionStart() {
|
|
690
|
+
composing = true;
|
|
691
|
+
}
|
|
692
|
+
function compositionEnd() {
|
|
693
|
+
composing = false;
|
|
694
|
+
}
|
|
695
|
+
function consentChanged() {
|
|
696
|
+
const executorId = input.panel.readExecutorId();
|
|
697
|
+
if (executorId !== void 0) {
|
|
698
|
+
consentByExecutor.set(executorId, input.panel.readConsent());
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function submitClick() {
|
|
702
|
+
void submit();
|
|
703
|
+
}
|
|
704
|
+
function cancelClick() {
|
|
705
|
+
void cancel();
|
|
706
|
+
}
|
|
707
|
+
function mount() {
|
|
708
|
+
if (mounted) return;
|
|
709
|
+
mounted = true;
|
|
710
|
+
input.panel.submitButton.addEventListener("click", submitClick);
|
|
711
|
+
input.panel.cancelButton.addEventListener("click", cancelClick);
|
|
712
|
+
input.panel.newQuestionButton.addEventListener("click", newQuestion);
|
|
713
|
+
input.panel.copyButton.addEventListener("click", copyAnswer);
|
|
714
|
+
input.panel.convertButton.addEventListener("click", convert);
|
|
715
|
+
input.panel.executorSelect.addEventListener("change", executorChanged);
|
|
716
|
+
input.panel.consentCheckbox.addEventListener("change", consentChanged);
|
|
717
|
+
input.panel.questionInput.addEventListener("keydown", keydown);
|
|
718
|
+
input.panel.questionInput.addEventListener("compositionstart", compositionStart);
|
|
719
|
+
input.panel.questionInput.addEventListener("compositionend", compositionEnd);
|
|
720
|
+
input.panel.root.addEventListener("click", openSource);
|
|
721
|
+
void loadCapability();
|
|
722
|
+
}
|
|
723
|
+
function dispose() {
|
|
724
|
+
if (!mounted) return;
|
|
725
|
+
cancelPending();
|
|
726
|
+
mounted = false;
|
|
727
|
+
input.panel.submitButton.removeEventListener("click", submitClick);
|
|
728
|
+
input.panel.cancelButton.removeEventListener("click", cancelClick);
|
|
729
|
+
input.panel.newQuestionButton.removeEventListener("click", newQuestion);
|
|
730
|
+
input.panel.copyButton.removeEventListener("click", copyAnswer);
|
|
731
|
+
input.panel.convertButton.removeEventListener("click", convert);
|
|
732
|
+
input.panel.executorSelect.removeEventListener("change", executorChanged);
|
|
733
|
+
input.panel.consentCheckbox.removeEventListener("change", consentChanged);
|
|
734
|
+
input.panel.questionInput.removeEventListener("keydown", keydown);
|
|
735
|
+
input.panel.questionInput.removeEventListener("compositionstart", compositionStart);
|
|
736
|
+
input.panel.questionInput.removeEventListener("compositionend", compositionEnd);
|
|
737
|
+
input.panel.root.removeEventListener("click", openSource);
|
|
738
|
+
api.dispose();
|
|
739
|
+
}
|
|
740
|
+
return Object.freeze({
|
|
741
|
+
beginSelection() {
|
|
742
|
+
revision += 1;
|
|
743
|
+
busy = false;
|
|
744
|
+
input.onBusyChange(false);
|
|
745
|
+
currentSnapshot = void 0;
|
|
746
|
+
currentResult = void 0;
|
|
747
|
+
currentQuestion = "";
|
|
748
|
+
input.panel.clear();
|
|
749
|
+
void loadCapability();
|
|
750
|
+
},
|
|
751
|
+
cancelPending,
|
|
752
|
+
dispose,
|
|
753
|
+
isBusy: () => busy,
|
|
754
|
+
mount,
|
|
755
|
+
selectionChanged() {
|
|
756
|
+
if (currentResult !== void 0) input.panel.renderAnswer(currentResult, true);
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/ui/contextual-ask-localization.ts
|
|
762
|
+
var EN = Object.freeze({
|
|
763
|
+
mode: Object.freeze({ ask: "Ask", change: "Change", label: "Task mode" }),
|
|
764
|
+
title: "Ask about this selection",
|
|
765
|
+
subtitle: "One question, one sourced answer. SpotPatch will not write code.",
|
|
766
|
+
questionLabel: "Question",
|
|
767
|
+
questionPlaceholder: "What does this component do?",
|
|
768
|
+
suggestionsLabel: "Suggested questions",
|
|
769
|
+
suggestions: Object.freeze([
|
|
770
|
+
"What does this component do?",
|
|
771
|
+
"Where does its data come from?",
|
|
772
|
+
"Why is it implemented this way?"
|
|
773
|
+
]),
|
|
774
|
+
executorLabel: "Read-only executor",
|
|
775
|
+
loadingExecutors: "Checking available executors\u2026",
|
|
776
|
+
noExecutor: "No verified read-only executor is available.",
|
|
777
|
+
unavailable: "unavailable",
|
|
778
|
+
consent: "Allow the selected source snapshot to be sent to this provider for this answer.",
|
|
779
|
+
dataSummary: (targets, sources) => `${String(targets)} selected element${targets === 1 ? "" : "s"} \xB7 ${String(sources)} source-backed`,
|
|
780
|
+
safety: "Single turn \xB7 read-only \xB7 no history",
|
|
781
|
+
submit: "Ask",
|
|
782
|
+
cancel: "Cancel",
|
|
783
|
+
newQuestion: "Start over",
|
|
784
|
+
copy: "Copy answer",
|
|
785
|
+
convert: "Turn into change request",
|
|
786
|
+
answerTitle: "Answer",
|
|
787
|
+
sourcesTitle: "Sources",
|
|
788
|
+
stale: "The selection changed after this answer. Ask again before relying on it.",
|
|
789
|
+
convertedTitle: "From contextual answer",
|
|
790
|
+
convertedBody: "The answer and source references remain local and have not started a change.",
|
|
791
|
+
status: Object.freeze({
|
|
792
|
+
queued: "Queued",
|
|
793
|
+
authorizing: "Verifying read-only access",
|
|
794
|
+
running: "Analyzing selected source",
|
|
795
|
+
cancelling: "Cancelling",
|
|
796
|
+
answered: "Answer ready",
|
|
797
|
+
cancelled: "Cancelled",
|
|
798
|
+
failed: "Could not answer"
|
|
799
|
+
}),
|
|
800
|
+
sourceLabel: (path, start, end) => `${path}:${String(start)}${end === start ? "" : `\u2013${String(end)}`}`,
|
|
801
|
+
readSource: (path) => `Reading ${path}`,
|
|
802
|
+
readFiles: (bucket) => `Reading ${bucket} files`,
|
|
803
|
+
copied: "Answer copied.",
|
|
804
|
+
copyFailed: "The answer could not be copied.",
|
|
805
|
+
sourceOpenFailed: "The source could not be opened.",
|
|
806
|
+
converted: "Switched to Change with the answer attached as local context.",
|
|
807
|
+
error: (code) => {
|
|
808
|
+
const errors = {
|
|
809
|
+
INVALID_TOKEN: "The local SpotPatch session expired. Refresh the page.",
|
|
810
|
+
ORIGIN_NOT_ALLOWED: "The local request failed its same-origin security check. Refresh or restart the dev server.",
|
|
811
|
+
ASK_DISABLED: "Contextual Ask is disabled.",
|
|
812
|
+
ASK_SELECTION_REQUIRED: "Select at least one element first.",
|
|
813
|
+
ASK_SELECTION_STALE: "The selected source changed. Refresh the selection and ask again.",
|
|
814
|
+
ASK_QUESTION_INVALID: "Enter one valid question.",
|
|
815
|
+
ASK_EXECUTOR_UNAVAILABLE: "The selected read-only executor is unavailable.",
|
|
816
|
+
ASK_TIMEOUT: "The read-only executor took too long to answer. Try the question again.",
|
|
817
|
+
ASK_CONSENT_REQUIRED: "Confirm provider data access for this answer.",
|
|
818
|
+
ASK_BUSY: "Another workspace task is active. Try again when it finishes.",
|
|
819
|
+
ASK_CANCELLED: "The question was cancelled.",
|
|
820
|
+
ASK_RESULT_EXPIRED: "This answer has expired. Ask again.",
|
|
821
|
+
ASK_WRITE_ATTEMPTED: "The executor attempted a write and was stopped.",
|
|
822
|
+
ASK_ANSWER_INVALID: "The executor returned an invalid sourced answer.",
|
|
823
|
+
ASK_PROTOCOL_INCOMPATIBLE: "The executor protocol is incompatible.",
|
|
824
|
+
ASK_LIMIT_EXCEEDED: "The selected context is too large.",
|
|
825
|
+
ASK_SOURCE_SCOPE_DENIED: "A requested source was outside the granted snapshot."
|
|
826
|
+
};
|
|
827
|
+
return code === void 0 ? "SpotPatch could not complete this question." : errors[code] ?? "SpotPatch could not complete this question.";
|
|
828
|
+
},
|
|
829
|
+
warning: (code) => code === "insufficient-evidence" ? "The selected source did not contain enough evidence for a complete answer." : "Some source references were omitted because they could not be verified."
|
|
830
|
+
});
|
|
831
|
+
var ZH = Object.freeze({
|
|
832
|
+
mode: Object.freeze({ ask: "\u95EE\u7B54", change: "\u4FEE\u6539", label: "\u4EFB\u52A1\u6A21\u5F0F" }),
|
|
833
|
+
title: "\u8BE2\u95EE\u6240\u9009\u5143\u7D20",
|
|
834
|
+
subtitle: "\u4E00\u6B21\u63D0\u95EE\uFF0C\u4E00\u6B21\u5E26\u6E90\u7801\u5F15\u7528\u7684\u56DE\u7B54\uFF1BSpotPatch \u4E0D\u4F1A\u5199\u5165\u4EE3\u7801\u3002",
|
|
835
|
+
questionLabel: "\u95EE\u9898",
|
|
836
|
+
questionPlaceholder: "\u8FD9\u4E2A\u7EC4\u4EF6\u662F\u505A\u4EC0\u4E48\u7684\uFF1F",
|
|
837
|
+
suggestionsLabel: "\u63A8\u8350\u95EE\u9898",
|
|
838
|
+
suggestions: Object.freeze([
|
|
839
|
+
"\u8FD9\u4E2A\u7EC4\u4EF6\u662F\u505A\u4EC0\u4E48\u7684\uFF1F",
|
|
840
|
+
"\u5B83\u7684\u6570\u636E\u4ECE\u54EA\u91CC\u6765\uFF1F",
|
|
841
|
+
"\u4E3A\u4EC0\u4E48\u8981\u8FD9\u6837\u5B9E\u73B0\uFF1F"
|
|
842
|
+
]),
|
|
843
|
+
executorLabel: "\u53EA\u8BFB\u6267\u884C\u5668",
|
|
844
|
+
loadingExecutors: "\u6B63\u5728\u68C0\u67E5\u53EF\u7528\u6267\u884C\u5668\u2026",
|
|
845
|
+
noExecutor: "\u76EE\u524D\u6CA1\u6709\u901A\u8FC7\u53EA\u8BFB\u9A8C\u8BC1\u7684\u6267\u884C\u5668\u3002",
|
|
846
|
+
unavailable: "\u4E0D\u53EF\u7528",
|
|
847
|
+
consent: "\u5141\u8BB8\u672C\u6B21\u56DE\u7B54\u5C06\u6240\u9009\u6E90\u7801\u5FEB\u7167\u53D1\u9001\u7ED9\u8BE5\u63D0\u4F9B\u5546\u3002",
|
|
848
|
+
dataSummary: (targets, sources) => `${String(targets)} \u4E2A\u5DF2\u9009\u5143\u7D20 \xB7 ${String(sources)} \u4E2A\u5177\u5907\u6E90\u7801\u4E0A\u4E0B\u6587`,
|
|
849
|
+
safety: "\u5355\u8F6E \xB7 \u53EA\u8BFB \xB7 \u4E0D\u4FDD\u5B58\u5386\u53F2",
|
|
850
|
+
submit: "\u63D0\u95EE",
|
|
851
|
+
cancel: "\u53D6\u6D88",
|
|
852
|
+
newQuestion: "\u91CD\u65B0\u5F00\u59CB",
|
|
853
|
+
copy: "\u590D\u5236\u56DE\u7B54",
|
|
854
|
+
convert: "\u8F6C\u6210\u4FEE\u6539\u8BF7\u6C42",
|
|
855
|
+
answerTitle: "\u56DE\u7B54",
|
|
856
|
+
sourcesTitle: "\u6E90\u7801\u5F15\u7528",
|
|
857
|
+
stale: "\u56DE\u7B54\u540E\u9009\u62E9\u5DF2\u53D1\u751F\u53D8\u5316\u3002\u8BF7\u91CD\u65B0\u63D0\u95EE\u540E\u518D\u91C7\u4FE1\u6B64\u56DE\u7B54\u3002",
|
|
858
|
+
convertedTitle: "\u6765\u81EA\u4E0A\u4E0B\u6587\u95EE\u7B54",
|
|
859
|
+
convertedBody: "\u56DE\u7B54\u4E0E\u6E90\u7801\u5F15\u7528\u4EC5\u4F5C\u4E3A\u672C\u5730\u4E0A\u4E0B\u6587\u4FDD\u7559\uFF0C\u5C1A\u672A\u53D1\u8D77\u4EFB\u4F55\u4FEE\u6539\u3002",
|
|
860
|
+
status: Object.freeze({
|
|
861
|
+
queued: "\u5DF2\u6392\u961F",
|
|
862
|
+
authorizing: "\u6B63\u5728\u9A8C\u8BC1\u53EA\u8BFB\u6743\u9650",
|
|
863
|
+
running: "\u6B63\u5728\u5206\u6790\u6240\u9009\u6E90\u7801",
|
|
864
|
+
cancelling: "\u6B63\u5728\u53D6\u6D88",
|
|
865
|
+
answered: "\u56DE\u7B54\u5DF2\u5C31\u7EEA",
|
|
866
|
+
cancelled: "\u5DF2\u53D6\u6D88",
|
|
867
|
+
failed: "\u65E0\u6CD5\u56DE\u7B54"
|
|
868
|
+
}),
|
|
869
|
+
sourceLabel: (path, start, end) => `${path}:${String(start)}${end === start ? "" : `\u2013${String(end)}`}`,
|
|
870
|
+
readSource: (path) => `\u6B63\u5728\u8BFB\u53D6 ${path}`,
|
|
871
|
+
readFiles: (bucket) => `\u6B63\u5728\u8BFB\u53D6 ${bucket} \u4E2A\u6587\u4EF6`,
|
|
872
|
+
copied: "\u56DE\u7B54\u5DF2\u590D\u5236\u3002",
|
|
873
|
+
copyFailed: "\u65E0\u6CD5\u590D\u5236\u56DE\u7B54\u3002",
|
|
874
|
+
sourceOpenFailed: "\u65E0\u6CD5\u6253\u5F00\u6E90\u7801\u3002",
|
|
875
|
+
converted: "\u5DF2\u5207\u6362\u5230\u4FEE\u6539\u6A21\u5F0F\uFF0C\u5E76\u628A\u56DE\u7B54\u4F5C\u4E3A\u672C\u5730\u4E0A\u4E0B\u6587\u9644\u5E26\u3002",
|
|
876
|
+
error: (code) => {
|
|
877
|
+
const errors = {
|
|
878
|
+
INVALID_TOKEN: "\u672C\u5730 SpotPatch \u4F1A\u8BDD\u5DF2\u5931\u6548\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u3002",
|
|
879
|
+
ORIGIN_NOT_ALLOWED: "\u672C\u5730\u8BF7\u6C42\u672A\u901A\u8FC7\u540C\u6E90\u5B89\u5168\u6821\u9A8C\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u6216\u91CD\u542F\u5F00\u53D1\u670D\u52A1\u3002",
|
|
880
|
+
ASK_DISABLED: "\u4E0A\u4E0B\u6587\u95EE\u7B54\u672A\u542F\u7528\u3002",
|
|
881
|
+
ASK_SELECTION_REQUIRED: "\u8BF7\u5148\u9009\u62E9\u81F3\u5C11\u4E00\u4E2A\u5143\u7D20\u3002",
|
|
882
|
+
ASK_SELECTION_STALE: "\u6240\u9009\u6E90\u7801\u5DF2\u7ECF\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u9009\u62E9\u540E\u91CD\u65B0\u63D0\u95EE\u3002",
|
|
883
|
+
ASK_QUESTION_INVALID: "\u8BF7\u8F93\u5165\u4E00\u4E2A\u6709\u6548\u7684\u95EE\u9898\u3002",
|
|
884
|
+
ASK_EXECUTOR_UNAVAILABLE: "\u6240\u9009\u53EA\u8BFB\u6267\u884C\u5668\u5F53\u524D\u4E0D\u53EF\u7528\u3002",
|
|
885
|
+
ASK_TIMEOUT: "\u53EA\u8BFB\u6267\u884C\u5668\u672A\u80FD\u5728\u9650\u5B9A\u65F6\u95F4\u5185\u8FD4\u56DE\u56DE\u7B54\uFF0C\u8BF7\u91CD\u65B0\u63D0\u95EE\u3002",
|
|
886
|
+
ASK_CONSENT_REQUIRED: "\u8BF7\u786E\u8BA4\u672C\u6B21\u56DE\u7B54\u7684\u63D0\u4F9B\u5546\u6570\u636E\u8BBF\u95EE\u3002",
|
|
887
|
+
ASK_BUSY: "\u5DE5\u4F5C\u533A\u6B63\u5728\u6267\u884C\u5176\u4ED6\u4EFB\u52A1\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
|
|
888
|
+
ASK_CANCELLED: "\u672C\u6B21\u63D0\u95EE\u5DF2\u53D6\u6D88\u3002",
|
|
889
|
+
ASK_RESULT_EXPIRED: "\u56DE\u7B54\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u63D0\u95EE\u3002",
|
|
890
|
+
ASK_WRITE_ATTEMPTED: "\u6267\u884C\u5668\u5C1D\u8BD5\u5199\u5165\uFF0C\u5DF2\u88AB\u963B\u6B62\u3002",
|
|
891
|
+
ASK_ANSWER_INVALID: "\u6267\u884C\u5668\u8FD4\u56DE\u7684\u5E26\u5F15\u7528\u56DE\u7B54\u65E0\u6548\u3002",
|
|
892
|
+
ASK_PROTOCOL_INCOMPATIBLE: "\u6267\u884C\u5668\u534F\u8BAE\u4E0D\u517C\u5BB9\u3002",
|
|
893
|
+
ASK_LIMIT_EXCEEDED: "\u6240\u9009\u4E0A\u4E0B\u6587\u8FC7\u5927\u3002",
|
|
894
|
+
ASK_SOURCE_SCOPE_DENIED: "\u8BF7\u6C42\u7684\u6E90\u7801\u8D85\u51FA\u4E86\u6388\u6743\u5FEB\u7167\u8303\u56F4\u3002"
|
|
895
|
+
};
|
|
896
|
+
return code === void 0 ? "SpotPatch \u672A\u80FD\u5B8C\u6210\u672C\u6B21\u63D0\u95EE\u3002" : errors[code] ?? "SpotPatch \u672A\u80FD\u5B8C\u6210\u672C\u6B21\u63D0\u95EE\u3002";
|
|
897
|
+
},
|
|
898
|
+
warning: (code) => code === "insufficient-evidence" ? "\u6240\u9009\u6E90\u7801\u4E0D\u8DB3\u4EE5\u652F\u6301\u5B8C\u6574\u56DE\u7B54\u3002" : "\u90E8\u5206\u6E90\u7801\u5F15\u7528\u56E0\u65E0\u6CD5\u9A8C\u8BC1\u800C\u88AB\u7701\u7565\u3002"
|
|
899
|
+
});
|
|
900
|
+
function contextualAskMessages(locale) {
|
|
901
|
+
return locale === "zh-CN" ? ZH : EN;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// src/ui/contextual-ask-panel.ts
|
|
905
|
+
function createStyles(document) {
|
|
906
|
+
const style = document.createElement("style");
|
|
907
|
+
style.textContent = `
|
|
908
|
+
.spotpatch-ask-mode { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; margin-bottom: 14px; padding: 4px; border: 1px solid var(--spotpatch-border-subtle); border-radius: 10px; background: #0a0a0e; }
|
|
909
|
+
.spotpatch-ask-mode button { min-height: 34px; border: 0; border-radius: 7px; color: var(--spotpatch-text-secondary); background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; }
|
|
910
|
+
.spotpatch-ask-mode button[aria-selected="true"] { color: #f7f7ff; background: linear-gradient(135deg, rgb(139 123 255 / 25%), rgb(82 168 255 / 14%)); box-shadow: inset 0 0 0 1px rgb(139 123 255 / 24%); }
|
|
911
|
+
.spotpatch-ask-panel { display: grid; gap: 14px; }
|
|
912
|
+
.spotpatch-ask-field { display: grid; gap: 7px; }
|
|
913
|
+
.spotpatch-ask-field > label, .spotpatch-ask-label { color: #c9cad2; font-size: 11px; font-weight: 680; }
|
|
914
|
+
.spotpatch-ask-question { box-sizing: border-box; width: 100%; min-height: 92px; resize: vertical; border: 1px solid var(--spotpatch-border); border-radius: 10px; padding: 11px 12px; outline: none; color: var(--spotpatch-text); background: var(--spotpatch-bg-input); line-height: 1.55; }
|
|
915
|
+
.spotpatch-ask-question:focus { border-color: rgb(139 123 255 / 68%); box-shadow: 0 0 0 3px rgb(139 123 255 / 12%); }
|
|
916
|
+
.spotpatch-ask-suggestions { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
917
|
+
.spotpatch-ask-suggestions button { border: 1px solid var(--spotpatch-border-subtle); border-radius: 999px; padding: 5px 9px; color: var(--spotpatch-text-secondary); background: rgb(255 255 255 / 2%); cursor: pointer; font-size: 10.5px; }
|
|
918
|
+
.spotpatch-ask-suggestions button:hover { border-color: rgb(139 123 255 / 42%); color: #fff; }
|
|
919
|
+
.spotpatch-ask-executor-picker { display: grid; gap: 5px; min-width: 0; }
|
|
920
|
+
.spotpatch-ask-executor { box-sizing: border-box; display: flex; width: 100%; min-height: 38px; align-items: center; justify-content: space-between; gap: 10px; border: 1px solid var(--spotpatch-border); border-radius: 9px; padding: 0 11px; overflow: hidden; color: var(--spotpatch-text); background: var(--spotpatch-bg-input); cursor: pointer; font: inherit; outline: none; text-align: left; }
|
|
921
|
+
.spotpatch-ask-executor > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
922
|
+
.spotpatch-ask-executor[data-expandable="true"]::after { width: 7px; height: 7px; flex: 0 0 auto; border-right: 1.5px solid #8f96a3; border-bottom: 1.5px solid #8f96a3; content: ""; transform: translateY(-2px) rotate(45deg); }
|
|
923
|
+
.spotpatch-ask-executor[aria-expanded="true"]::after { transform: translateY(2px) rotate(225deg); }
|
|
924
|
+
.spotpatch-ask-executor:focus-visible { border-color: rgb(139 123 255 / 68%); box-shadow: 0 0 0 3px rgb(139 123 255 / 12%); }
|
|
925
|
+
.spotpatch-ask-executor:disabled { color: var(--spotpatch-text); background: rgb(255 255 255 / 3%); cursor: default; opacity: 1; }
|
|
926
|
+
.spotpatch-ask-executor[data-empty="true"]:disabled { color: var(--spotpatch-text-muted); }
|
|
927
|
+
.spotpatch-ask-executor-menu { display: grid; gap: 3px; border: 1px solid var(--spotpatch-border); border-radius: 9px; padding: 4px; background: #101116; box-shadow: 0 10px 28px rgb(0 0 0 / 28%); }
|
|
928
|
+
.spotpatch-ask-executor-menu[hidden] { display: none; }
|
|
929
|
+
.spotpatch-ask-executor-option { box-sizing: border-box; min-height: 34px; border: 0; border-radius: 6px; padding: 7px 9px; color: var(--spotpatch-text-secondary); background: transparent; cursor: pointer; font: inherit; text-align: left; }
|
|
930
|
+
.spotpatch-ask-executor-option:hover, .spotpatch-ask-executor-option:focus-visible { color: #fff; background: rgb(139 123 255 / 13%); outline: none; }
|
|
931
|
+
.spotpatch-ask-executor-option[aria-selected="true"] { color: #f4f1ff; background: rgb(139 123 255 / 20%); }
|
|
932
|
+
.spotpatch-ask-executor-native { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; border: 0; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
|
|
933
|
+
.spotpatch-ask-executor-status { margin: 0; border-left: 2px solid var(--spotpatch-warning); padding-left: 8px; color: #d8b66c; font-size: 10.5px; line-height: 1.45; }
|
|
934
|
+
.spotpatch-ask-safety { display: grid; gap: 8px; border: 1px solid rgb(82 168 255 / 16%); border-radius: 10px; padding: 10px 11px; background: rgb(82 168 255 / 4%); }
|
|
935
|
+
.spotpatch-ask-data { display: flex; justify-content: space-between; gap: 10px; color: #aab3c2; font-size: 10.5px; }
|
|
936
|
+
.spotpatch-ask-data strong { color: #9fe3c4; font-weight: 650; white-space: nowrap; }
|
|
937
|
+
.spotpatch-ask-consent { display: grid; grid-template-columns: 16px 1fr; gap: 8px; align-items: start; color: var(--spotpatch-text-secondary); cursor: pointer; font-size: 10.5px; line-height: 1.45; }
|
|
938
|
+
.spotpatch-ask-consent input { margin: 2px 0 0; accent-color: var(--spotpatch-accent); }
|
|
939
|
+
.spotpatch-ask-actions, .spotpatch-ask-answer-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 7px; }
|
|
940
|
+
.spotpatch-ask-actions button, .spotpatch-ask-answer-actions button { min-height: 34px; border: 1px solid var(--spotpatch-border); border-radius: 8px; padding: 0 12px; color: #d6d6de; background: var(--spotpatch-bg-raised); cursor: pointer; font-size: 11px; font-weight: 680; }
|
|
941
|
+
.spotpatch-ask-actions .spotpatch-primary, .spotpatch-ask-answer-actions .spotpatch-primary { border-color: transparent; color: var(--spotpatch-text-on-accent); background: linear-gradient(135deg, #a99cff, #65b7ff); }
|
|
942
|
+
.spotpatch-ask-actions button:disabled, .spotpatch-ask-answer-actions button:disabled { opacity: .45; cursor: not-allowed; }
|
|
943
|
+
.spotpatch-ask-status { display: grid; gap: 7px; border-left: 2px solid var(--spotpatch-accent); padding: 2px 0 2px 10px; color: #c8c9d2; font-size: 11px; }
|
|
944
|
+
.spotpatch-ask-activity { display: grid; gap: 4px; color: var(--spotpatch-text-muted); font: 500 10px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
945
|
+
.spotpatch-ask-error, .spotpatch-ask-stale, .spotpatch-ask-warning { border-radius: 8px; padding: 9px 10px; font-size: 10.5px; }
|
|
946
|
+
.spotpatch-ask-error { color: #fecaca; background: rgb(251 113 133 / 9%); }
|
|
947
|
+
.spotpatch-ask-stale, .spotpatch-ask-warning { color: #f9d68a; background: rgb(245 158 11 / 8%); }
|
|
948
|
+
.spotpatch-ask-answer { display: grid; gap: 12px; border: 1px solid var(--spotpatch-border-subtle); border-radius: 11px; padding: 12px; background: var(--spotpatch-bg); box-shadow: 0 12px 30px rgb(0 0 0 / 18%); }
|
|
949
|
+
.spotpatch-ask-answer h3, .spotpatch-ask-sources h4 { margin: 0; color: var(--spotpatch-text); font-size: 13px; }
|
|
950
|
+
.spotpatch-ask-blocks { display: grid; gap: 10px; color: #d9dae2; font-size: 12px; line-height: 1.65; overflow-wrap: anywhere; }
|
|
951
|
+
.spotpatch-ask-blocks p, .spotpatch-ask-blocks ul { margin: 0; }
|
|
952
|
+
.spotpatch-ask-blocks ul { padding-left: 18px; }
|
|
953
|
+
.spotpatch-ask-blocks pre { max-width: 100%; overflow: auto; margin: 0; border: 1px solid var(--spotpatch-border-subtle); border-radius: 9px; padding: 10px; color: #dbeafe; background: #08090d; font: 500 10.5px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
954
|
+
.spotpatch-ask-citations, .spotpatch-ask-source-list { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
955
|
+
.spotpatch-ask-source { max-width: 100%; overflow: hidden; border: 1px solid rgb(82 168 255 / 22%); border-radius: 999px; padding: 4px 8px; color: #b8dcff; background: rgb(82 168 255 / 7%); cursor: pointer; font: 550 9.5px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
|
956
|
+
.spotpatch-ask-sources { display: grid; gap: 7px; }
|
|
957
|
+
.spotpatch-ask-origin { margin-bottom: 12px; border: 1px solid rgb(139 123 255 / 22%); border-radius: 10px; padding: 10px 11px; color: var(--spotpatch-text-secondary); background: rgb(139 123 255 / 6%); font-size: 10.5px; }
|
|
958
|
+
.spotpatch-ask-origin strong { display: block; margin-bottom: 2px; color: #d9d4ff; font-size: 11px; }
|
|
959
|
+
@media (max-width: 420px) { .spotpatch-ask-data { display: grid; } .spotpatch-ask-actions, .spotpatch-ask-answer-actions { justify-content: stretch; } .spotpatch-ask-actions button, .spotpatch-ask-answer-actions button { flex: 1 1 auto; } }
|
|
960
|
+
@media (prefers-reduced-motion: reduce) { .spotpatch-ask-panel *, .spotpatch-ask-mode * { scroll-behavior: auto !important; transition: none !important; } }
|
|
961
|
+
`;
|
|
962
|
+
return style;
|
|
963
|
+
}
|
|
964
|
+
function appendSourceChips(document, root, sourceIds, sources, messages) {
|
|
965
|
+
if (sourceIds.length === 0) return;
|
|
966
|
+
const chips = createMarkedElement(document, "div");
|
|
967
|
+
chips.className = "spotpatch-ask-citations";
|
|
968
|
+
for (const sourceId of sourceIds) {
|
|
969
|
+
const source = sources.get(sourceId);
|
|
970
|
+
if (source === void 0) continue;
|
|
971
|
+
const chip = createButton(
|
|
972
|
+
document,
|
|
973
|
+
messages.sourceLabel(source.relativePath, source.startLine, source.endLine),
|
|
974
|
+
"spotpatch-ask-source"
|
|
975
|
+
);
|
|
976
|
+
chip.dataset.askSourceId = source.sourceId;
|
|
977
|
+
chip.title = source.label;
|
|
978
|
+
chips.append(chip);
|
|
979
|
+
}
|
|
980
|
+
root.append(chips);
|
|
981
|
+
}
|
|
982
|
+
function createContextualAskPanel(input) {
|
|
983
|
+
const { document } = input;
|
|
984
|
+
let messages = contextualAskMessages(input.locale());
|
|
985
|
+
let currentMode = "change";
|
|
986
|
+
let currentCapability;
|
|
987
|
+
let currentResult;
|
|
988
|
+
let currentJob;
|
|
989
|
+
let reportedMode;
|
|
990
|
+
let reportedTitle = "";
|
|
991
|
+
let reportedSubtitle = "";
|
|
992
|
+
let currentPreview = Object.freeze({
|
|
993
|
+
contextReady: false,
|
|
994
|
+
targetCount: 0,
|
|
995
|
+
sourceCount: 0
|
|
996
|
+
});
|
|
997
|
+
let busy = false;
|
|
998
|
+
const root = createMarkedElement(document, "section");
|
|
999
|
+
const modeSwitch = createMarkedElement(document, "div");
|
|
1000
|
+
modeSwitch.className = "spotpatch-ask-mode";
|
|
1001
|
+
modeSwitch.setAttribute("role", "tablist");
|
|
1002
|
+
const askTab = createButton(document, "");
|
|
1003
|
+
const changeTab = createButton(document, "");
|
|
1004
|
+
askTab.setAttribute("role", "tab");
|
|
1005
|
+
changeTab.setAttribute("role", "tab");
|
|
1006
|
+
modeSwitch.append(askTab, changeTab);
|
|
1007
|
+
const origin = createMarkedElement(document, "aside");
|
|
1008
|
+
origin.className = "spotpatch-ask-origin";
|
|
1009
|
+
origin.hidden = true;
|
|
1010
|
+
const originTitle = createMarkedElement(document, "strong");
|
|
1011
|
+
const originBody = createMarkedElement(document, "span");
|
|
1012
|
+
origin.append(originTitle, originBody);
|
|
1013
|
+
const askPanel = createMarkedElement(document, "div");
|
|
1014
|
+
askPanel.className = "spotpatch-ask-panel";
|
|
1015
|
+
const questionField = createMarkedElement(document, "div");
|
|
1016
|
+
questionField.className = "spotpatch-ask-field";
|
|
1017
|
+
const questionLabel = createMarkedElement(document, "label");
|
|
1018
|
+
const questionId = `spotpatch-ask-question-${Math.random().toString(36).slice(2)}`;
|
|
1019
|
+
questionLabel.htmlFor = questionId;
|
|
1020
|
+
const questionInput = createMarkedElement(document, "textarea");
|
|
1021
|
+
questionInput.id = questionId;
|
|
1022
|
+
questionInput.className = "spotpatch-ask-question";
|
|
1023
|
+
questionInput.maxLength = 4e3;
|
|
1024
|
+
const suggestions = createMarkedElement(document, "div");
|
|
1025
|
+
suggestions.className = "spotpatch-ask-suggestions";
|
|
1026
|
+
suggestions.setAttribute("aria-label", "");
|
|
1027
|
+
questionField.append(questionLabel, questionInput, suggestions);
|
|
1028
|
+
const executorField = createMarkedElement(document, "div");
|
|
1029
|
+
executorField.className = "spotpatch-ask-field";
|
|
1030
|
+
const executorLabel = createMarkedElement(document, "label");
|
|
1031
|
+
const executorPicker = createMarkedElement(document, "div");
|
|
1032
|
+
executorPicker.className = "spotpatch-ask-executor-picker";
|
|
1033
|
+
const executorTrigger = createButton(document, "", "spotpatch-ask-executor");
|
|
1034
|
+
const executorSelect = createMarkedElement(document, "select");
|
|
1035
|
+
const executorId = `spotpatch-ask-executor-${Math.random().toString(36).slice(2)}`;
|
|
1036
|
+
const executorMenuId = `${executorId}-menu`;
|
|
1037
|
+
executorTrigger.id = executorId;
|
|
1038
|
+
executorTrigger.setAttribute("role", "combobox");
|
|
1039
|
+
executorTrigger.setAttribute("aria-controls", executorMenuId);
|
|
1040
|
+
executorTrigger.setAttribute("aria-expanded", "false");
|
|
1041
|
+
const executorText = createMarkedElement(document, "span");
|
|
1042
|
+
executorTrigger.append(executorText);
|
|
1043
|
+
const executorMenu = createMarkedElement(document, "div");
|
|
1044
|
+
executorMenu.id = executorMenuId;
|
|
1045
|
+
executorMenu.className = "spotpatch-ask-executor-menu";
|
|
1046
|
+
executorMenu.setAttribute("role", "listbox");
|
|
1047
|
+
executorMenu.hidden = true;
|
|
1048
|
+
executorSelect.className = "spotpatch-ask-executor-native";
|
|
1049
|
+
executorSelect.tabIndex = -1;
|
|
1050
|
+
executorSelect.setAttribute("aria-hidden", "true");
|
|
1051
|
+
executorLabel.htmlFor = executorId;
|
|
1052
|
+
const executorStatus = createMarkedElement(document, "p");
|
|
1053
|
+
executorStatus.className = "spotpatch-ask-executor-status";
|
|
1054
|
+
executorStatus.setAttribute("role", "status");
|
|
1055
|
+
executorStatus.hidden = true;
|
|
1056
|
+
executorPicker.append(executorTrigger, executorMenu, executorSelect);
|
|
1057
|
+
executorField.append(executorLabel, executorPicker, executorStatus);
|
|
1058
|
+
const safety = createMarkedElement(document, "div");
|
|
1059
|
+
safety.className = "spotpatch-ask-safety";
|
|
1060
|
+
const dataSummary = createMarkedElement(document, "div");
|
|
1061
|
+
dataSummary.className = "spotpatch-ask-data";
|
|
1062
|
+
const dataText = createMarkedElement(document, "span");
|
|
1063
|
+
const safetyText = createMarkedElement(document, "strong");
|
|
1064
|
+
dataSummary.append(dataText, safetyText);
|
|
1065
|
+
const consentLabel = createMarkedElement(document, "label");
|
|
1066
|
+
consentLabel.className = "spotpatch-ask-consent";
|
|
1067
|
+
const consentCheckbox = createMarkedElement(document, "input");
|
|
1068
|
+
consentCheckbox.type = "checkbox";
|
|
1069
|
+
const consentText = createMarkedElement(document, "span");
|
|
1070
|
+
consentLabel.append(consentCheckbox, consentText);
|
|
1071
|
+
safety.append(dataSummary, consentLabel);
|
|
1072
|
+
const status = createMarkedElement(document, "div");
|
|
1073
|
+
status.className = "spotpatch-ask-status";
|
|
1074
|
+
status.setAttribute("role", "status");
|
|
1075
|
+
status.setAttribute("aria-live", "polite");
|
|
1076
|
+
status.hidden = true;
|
|
1077
|
+
const statusText = createMarkedElement(document, "strong");
|
|
1078
|
+
const activities = createMarkedElement(document, "div");
|
|
1079
|
+
activities.className = "spotpatch-ask-activity";
|
|
1080
|
+
status.append(statusText, activities);
|
|
1081
|
+
const error = createMarkedElement(document, "div");
|
|
1082
|
+
error.className = "spotpatch-ask-error";
|
|
1083
|
+
error.setAttribute("role", "alert");
|
|
1084
|
+
error.hidden = true;
|
|
1085
|
+
const actions = createMarkedElement(document, "div");
|
|
1086
|
+
actions.className = "spotpatch-ask-actions";
|
|
1087
|
+
const newQuestionButton = createButton(document, "");
|
|
1088
|
+
const cancelButton = createButton(document, "");
|
|
1089
|
+
const submitButton = createButton(document, "", "spotpatch-primary");
|
|
1090
|
+
cancelButton.hidden = true;
|
|
1091
|
+
actions.append(newQuestionButton, cancelButton, submitButton);
|
|
1092
|
+
const answer = createMarkedElement(document, "article");
|
|
1093
|
+
answer.className = "spotpatch-ask-answer";
|
|
1094
|
+
answer.hidden = true;
|
|
1095
|
+
const stale = createMarkedElement(document, "div");
|
|
1096
|
+
stale.className = "spotpatch-ask-stale";
|
|
1097
|
+
stale.hidden = true;
|
|
1098
|
+
const answerTitle = createMarkedElement(document, "h3");
|
|
1099
|
+
const warnings = createMarkedElement(document, "div");
|
|
1100
|
+
const blocks = createMarkedElement(document, "div");
|
|
1101
|
+
blocks.className = "spotpatch-ask-blocks";
|
|
1102
|
+
const sourcesSection = createMarkedElement(document, "section");
|
|
1103
|
+
sourcesSection.className = "spotpatch-ask-sources";
|
|
1104
|
+
const sourcesTitle = createMarkedElement(document, "h4");
|
|
1105
|
+
const sourceList = createMarkedElement(document, "div");
|
|
1106
|
+
sourceList.className = "spotpatch-ask-source-list";
|
|
1107
|
+
sourcesSection.append(sourcesTitle, sourceList);
|
|
1108
|
+
const answerActions = createMarkedElement(document, "div");
|
|
1109
|
+
answerActions.className = "spotpatch-ask-answer-actions";
|
|
1110
|
+
const copyButton = createButton(document, "");
|
|
1111
|
+
const convertButton = createButton(document, "", "spotpatch-primary");
|
|
1112
|
+
answerActions.append(copyButton, convertButton);
|
|
1113
|
+
answer.append(stale, answerTitle, warnings, blocks, sourcesSection, answerActions);
|
|
1114
|
+
askPanel.append(questionField, executorField, safety, status, error, actions, answer);
|
|
1115
|
+
root.append(modeSwitch, origin, askPanel);
|
|
1116
|
+
function hasReadyExecutor() {
|
|
1117
|
+
const selected = currentCapability?.executors.find(
|
|
1118
|
+
(candidate) => candidate.executorId === executorSelect.value
|
|
1119
|
+
);
|
|
1120
|
+
return selected?.state === "ready" && selected.readOnlyProven;
|
|
1121
|
+
}
|
|
1122
|
+
function hasAnyReadyExecutor() {
|
|
1123
|
+
return currentCapability?.executors.some(
|
|
1124
|
+
(candidate) => candidate.state === "ready" && candidate.readOnlyProven
|
|
1125
|
+
) === true;
|
|
1126
|
+
}
|
|
1127
|
+
function readyExecutorCount() {
|
|
1128
|
+
return currentCapability?.executors.filter(
|
|
1129
|
+
(candidate) => candidate.state === "ready" && candidate.readOnlyProven
|
|
1130
|
+
).length ?? 0;
|
|
1131
|
+
}
|
|
1132
|
+
function closeExecutorMenu(focusTrigger = false) {
|
|
1133
|
+
if (executorMenu.hidden) return;
|
|
1134
|
+
executorMenu.hidden = true;
|
|
1135
|
+
executorTrigger.setAttribute("aria-expanded", "false");
|
|
1136
|
+
if (focusTrigger) executorTrigger.focus({ preventScroll: true });
|
|
1137
|
+
input.onViewChange();
|
|
1138
|
+
}
|
|
1139
|
+
function openExecutorMenu(focusSelected = false) {
|
|
1140
|
+
if (executorTrigger.disabled || !executorMenu.hidden) return;
|
|
1141
|
+
executorMenu.hidden = false;
|
|
1142
|
+
executorTrigger.setAttribute("aria-expanded", "true");
|
|
1143
|
+
if (focusSelected) {
|
|
1144
|
+
const selected = executorMenu.querySelector(
|
|
1145
|
+
'.spotpatch-ask-executor-option[aria-selected="true"]'
|
|
1146
|
+
);
|
|
1147
|
+
const first = executorMenu.firstElementChild;
|
|
1148
|
+
const focusTarget = selected ?? (first instanceof HTMLElement ? first : null);
|
|
1149
|
+
focusTarget?.focus({ preventScroll: true });
|
|
1150
|
+
}
|
|
1151
|
+
input.onViewChange();
|
|
1152
|
+
}
|
|
1153
|
+
function syncExecutorControl() {
|
|
1154
|
+
const selectedOption = [...executorSelect.options].find(
|
|
1155
|
+
(option) => option.value === executorSelect.value
|
|
1156
|
+
);
|
|
1157
|
+
executorText.textContent = selectedOption?.textContent ?? messages.noExecutor;
|
|
1158
|
+
executorTrigger.dataset.empty = String(executorSelect.value.length === 0);
|
|
1159
|
+
for (const option of executorMenu.querySelectorAll(
|
|
1160
|
+
".spotpatch-ask-executor-option"
|
|
1161
|
+
)) {
|
|
1162
|
+
option.setAttribute(
|
|
1163
|
+
"aria-selected",
|
|
1164
|
+
String(option.dataset.executorId === executorSelect.value)
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function refreshSubmitState() {
|
|
1169
|
+
submitButton.disabled = busy || questionInput.value.trim().length === 0 || !hasReadyExecutor() || !consentCheckbox.checked || currentPreview.targetCount === 0;
|
|
1170
|
+
if (!currentPreview.contextReady) submitButton.disabled = true;
|
|
1171
|
+
questionInput.disabled = busy;
|
|
1172
|
+
const readyCount = readyExecutorCount();
|
|
1173
|
+
const executorUnavailable = busy || currentCapability === void 0 || !hasAnyReadyExecutor();
|
|
1174
|
+
executorSelect.disabled = executorUnavailable;
|
|
1175
|
+
executorTrigger.disabled = executorUnavailable || readyCount < 2;
|
|
1176
|
+
executorTrigger.dataset.expandable = String(!executorUnavailable && readyCount > 1);
|
|
1177
|
+
if (executorTrigger.disabled) closeExecutorMenu();
|
|
1178
|
+
consentCheckbox.disabled = busy || !hasReadyExecutor();
|
|
1179
|
+
newQuestionButton.hidden = busy || currentResult === void 0;
|
|
1180
|
+
cancelButton.hidden = !busy;
|
|
1181
|
+
}
|
|
1182
|
+
function applyMode() {
|
|
1183
|
+
const asking = currentMode === "ask";
|
|
1184
|
+
askTab.setAttribute("aria-selected", String(asking));
|
|
1185
|
+
changeTab.setAttribute("aria-selected", String(!asking));
|
|
1186
|
+
askPanel.hidden = !asking;
|
|
1187
|
+
input.changeRoot.hidden = asking;
|
|
1188
|
+
input.changeActions.hidden = asking;
|
|
1189
|
+
origin.hidden = asking || origin.dataset.active !== "true";
|
|
1190
|
+
if (reportedMode !== currentMode || reportedTitle !== messages.title || reportedSubtitle !== messages.subtitle) {
|
|
1191
|
+
reportedMode = currentMode;
|
|
1192
|
+
reportedTitle = messages.title;
|
|
1193
|
+
reportedSubtitle = messages.subtitle;
|
|
1194
|
+
input.onModeChange(currentMode, messages.title, messages.subtitle);
|
|
1195
|
+
}
|
|
1196
|
+
input.onViewChange();
|
|
1197
|
+
}
|
|
1198
|
+
function renderSuggestions() {
|
|
1199
|
+
suggestions.replaceChildren();
|
|
1200
|
+
for (const suggestion of messages.suggestions) {
|
|
1201
|
+
const button = createButton(document, suggestion);
|
|
1202
|
+
button.type = "button";
|
|
1203
|
+
button.addEventListener("click", () => {
|
|
1204
|
+
questionInput.value = suggestion;
|
|
1205
|
+
refreshSubmitState();
|
|
1206
|
+
questionInput.focus();
|
|
1207
|
+
});
|
|
1208
|
+
suggestions.append(button);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
function applyMessages() {
|
|
1212
|
+
messages = contextualAskMessages(input.locale());
|
|
1213
|
+
modeSwitch.setAttribute("aria-label", messages.mode.label);
|
|
1214
|
+
askTab.textContent = messages.mode.ask;
|
|
1215
|
+
changeTab.textContent = messages.mode.change;
|
|
1216
|
+
questionLabel.textContent = messages.questionLabel;
|
|
1217
|
+
questionInput.placeholder = messages.questionPlaceholder;
|
|
1218
|
+
suggestions.setAttribute("aria-label", messages.suggestionsLabel);
|
|
1219
|
+
executorLabel.textContent = messages.executorLabel;
|
|
1220
|
+
consentText.textContent = messages.consent;
|
|
1221
|
+
safetyText.textContent = messages.safety;
|
|
1222
|
+
submitButton.textContent = messages.submit;
|
|
1223
|
+
cancelButton.textContent = messages.cancel;
|
|
1224
|
+
newQuestionButton.textContent = messages.newQuestion;
|
|
1225
|
+
copyButton.textContent = messages.copy;
|
|
1226
|
+
convertButton.textContent = messages.convert;
|
|
1227
|
+
answerTitle.textContent = messages.answerTitle;
|
|
1228
|
+
sourcesTitle.textContent = messages.sourcesTitle;
|
|
1229
|
+
stale.textContent = messages.stale;
|
|
1230
|
+
originTitle.textContent = messages.convertedTitle;
|
|
1231
|
+
originBody.textContent = messages.convertedBody;
|
|
1232
|
+
dataText.textContent = messages.dataSummary(
|
|
1233
|
+
currentPreview.targetCount,
|
|
1234
|
+
currentPreview.sourceCount
|
|
1235
|
+
);
|
|
1236
|
+
renderSuggestions();
|
|
1237
|
+
renderCapability(currentCapability);
|
|
1238
|
+
if (currentResult !== void 0) renderAnswer(currentResult, !stale.hidden);
|
|
1239
|
+
}
|
|
1240
|
+
function renderCapability(capability) {
|
|
1241
|
+
currentCapability = capability;
|
|
1242
|
+
const previous = executorSelect.value;
|
|
1243
|
+
executorSelect.replaceChildren();
|
|
1244
|
+
executorMenu.replaceChildren();
|
|
1245
|
+
closeExecutorMenu();
|
|
1246
|
+
executorStatus.hidden = true;
|
|
1247
|
+
executorStatus.textContent = "";
|
|
1248
|
+
if (capability === void 0) {
|
|
1249
|
+
const option = document.createElement("option");
|
|
1250
|
+
option.textContent = messages.loadingExecutors;
|
|
1251
|
+
option.value = "";
|
|
1252
|
+
executorSelect.append(option);
|
|
1253
|
+
} else if (!capability.enabled || capability.executors.length === 0) {
|
|
1254
|
+
const option = document.createElement("option");
|
|
1255
|
+
option.textContent = messages.noExecutor;
|
|
1256
|
+
option.value = "";
|
|
1257
|
+
executorSelect.append(option);
|
|
1258
|
+
} else {
|
|
1259
|
+
const readyExecutors = capability.executors.filter(
|
|
1260
|
+
(executor) => executor.state === "ready" && executor.readOnlyProven
|
|
1261
|
+
);
|
|
1262
|
+
const unavailableExecutors = capability.executors.filter(
|
|
1263
|
+
(executor) => executor.state !== "ready" || !executor.readOnlyProven
|
|
1264
|
+
);
|
|
1265
|
+
for (const executor of readyExecutors) {
|
|
1266
|
+
const option = document.createElement("option");
|
|
1267
|
+
option.value = executor.executorId;
|
|
1268
|
+
option.textContent = `${executor.label} \xB7 ${executor.effectiveModelLabel}`;
|
|
1269
|
+
executorSelect.append(option);
|
|
1270
|
+
const menuOption = createButton(
|
|
1271
|
+
document,
|
|
1272
|
+
option.textContent,
|
|
1273
|
+
"spotpatch-ask-executor-option"
|
|
1274
|
+
);
|
|
1275
|
+
menuOption.dataset.executorId = executor.executorId;
|
|
1276
|
+
menuOption.setAttribute("role", "option");
|
|
1277
|
+
menuOption.addEventListener("click", () => {
|
|
1278
|
+
executorSelect.value = executor.executorId;
|
|
1279
|
+
syncExecutorControl();
|
|
1280
|
+
closeExecutorMenu(true);
|
|
1281
|
+
executorSelect.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1282
|
+
});
|
|
1283
|
+
executorMenu.append(menuOption);
|
|
1284
|
+
}
|
|
1285
|
+
if (readyExecutors.length === 0) {
|
|
1286
|
+
const option = document.createElement("option");
|
|
1287
|
+
option.textContent = messages.noExecutor;
|
|
1288
|
+
option.value = "";
|
|
1289
|
+
executorSelect.append(option);
|
|
1290
|
+
}
|
|
1291
|
+
executorSelect.value = readyExecutors.some(
|
|
1292
|
+
(executor) => executor.executorId === previous
|
|
1293
|
+
) ? previous : readyExecutors[0]?.executorId ?? "";
|
|
1294
|
+
if (unavailableExecutors.length > 0) {
|
|
1295
|
+
executorStatus.textContent = unavailableExecutors.map((executor) => `${executor.label}: ${messages.error(executor.errorCode)}`).join(" ");
|
|
1296
|
+
executorStatus.hidden = false;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
syncExecutorControl();
|
|
1300
|
+
refreshSubmitState();
|
|
1301
|
+
}
|
|
1302
|
+
function renderJob(snapshot) {
|
|
1303
|
+
currentJob = snapshot;
|
|
1304
|
+
status.hidden = false;
|
|
1305
|
+
error.hidden = true;
|
|
1306
|
+
statusText.textContent = messages.status[snapshot.status];
|
|
1307
|
+
if (["answered", "cancelled", "failed"].includes(snapshot.status)) {
|
|
1308
|
+
busy = false;
|
|
1309
|
+
input.onExecutionChange();
|
|
1310
|
+
} else {
|
|
1311
|
+
input.onExecutionChange({
|
|
1312
|
+
scene: "running",
|
|
1313
|
+
tone: "running",
|
|
1314
|
+
headline: messages.status[snapshot.status],
|
|
1315
|
+
expandedHeadline: messages.status[snapshot.status],
|
|
1316
|
+
action: messages.safety,
|
|
1317
|
+
expandedAction: messages.safety,
|
|
1318
|
+
meta: `${snapshot.executor.label} \xB7 ${snapshot.executor.modelLabel}`,
|
|
1319
|
+
recentActivities: Object.freeze([]),
|
|
1320
|
+
startedAt: snapshot.createdAt
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
refreshSubmitState();
|
|
1324
|
+
}
|
|
1325
|
+
function renderEvent(event) {
|
|
1326
|
+
if (event.type === "snapshot") {
|
|
1327
|
+
renderJob(event.snapshot);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
if (event.type === "phase") statusText.textContent = messages.status[event.status];
|
|
1331
|
+
if (event.type === "read-activity" && event.state === "started") {
|
|
1332
|
+
const line = createMarkedElement(document, "span");
|
|
1333
|
+
line.textContent = event.activity.kind === "source" ? messages.readSource(event.activity.relativePath) : messages.readFiles(event.activity.bucket);
|
|
1334
|
+
activities.append(line);
|
|
1335
|
+
while (activities.childElementCount > 5) activities.firstElementChild?.remove();
|
|
1336
|
+
if (currentJob !== void 0) {
|
|
1337
|
+
const activityLabel = line.textContent;
|
|
1338
|
+
input.onExecutionChange({
|
|
1339
|
+
scene: "running",
|
|
1340
|
+
tone: "running",
|
|
1341
|
+
headline: messages.status.running,
|
|
1342
|
+
expandedHeadline: messages.status.running,
|
|
1343
|
+
action: activityLabel,
|
|
1344
|
+
expandedAction: activityLabel,
|
|
1345
|
+
meta: `${currentJob.executor.label} \xB7 ${currentJob.executor.modelLabel}`,
|
|
1346
|
+
activity: {
|
|
1347
|
+
key: `ask:${String(event.sequence)}`,
|
|
1348
|
+
kind: "read",
|
|
1349
|
+
label: activityLabel,
|
|
1350
|
+
state: "active"
|
|
1351
|
+
},
|
|
1352
|
+
recentActivities: Object.freeze([]),
|
|
1353
|
+
startedAt: currentJob.createdAt
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function renderAnswer(result, isStale) {
|
|
1359
|
+
currentResult = result;
|
|
1360
|
+
currentJob = void 0;
|
|
1361
|
+
input.onExecutionChange();
|
|
1362
|
+
const sourceMap = new Map(
|
|
1363
|
+
result.sources.map((source) => [source.sourceId, source])
|
|
1364
|
+
);
|
|
1365
|
+
blocks.replaceChildren();
|
|
1366
|
+
warnings.replaceChildren();
|
|
1367
|
+
sourceList.replaceChildren();
|
|
1368
|
+
stale.hidden = !isStale;
|
|
1369
|
+
for (const warning of result.warnings) {
|
|
1370
|
+
const warningNode = createMarkedElement(document, "div");
|
|
1371
|
+
warningNode.className = "spotpatch-ask-warning";
|
|
1372
|
+
warningNode.textContent = messages.warning(warning.code);
|
|
1373
|
+
warnings.append(warningNode);
|
|
1374
|
+
}
|
|
1375
|
+
for (const block of result.blocks) {
|
|
1376
|
+
if (block.kind === "paragraph") {
|
|
1377
|
+
const paragraph = createMarkedElement(document, "p");
|
|
1378
|
+
paragraph.textContent = block.text;
|
|
1379
|
+
blocks.append(paragraph);
|
|
1380
|
+
appendSourceChips(document, blocks, block.sourceIds, sourceMap, messages);
|
|
1381
|
+
} else if (block.kind === "code") {
|
|
1382
|
+
const pre = createMarkedElement(document, "pre");
|
|
1383
|
+
const code = createMarkedElement(document, "code");
|
|
1384
|
+
if (block.language !== void 0) code.dataset.language = block.language;
|
|
1385
|
+
code.textContent = block.code;
|
|
1386
|
+
pre.append(code);
|
|
1387
|
+
blocks.append(pre);
|
|
1388
|
+
appendSourceChips(document, blocks, block.sourceIds, sourceMap, messages);
|
|
1389
|
+
} else {
|
|
1390
|
+
const list = createMarkedElement(document, "ul");
|
|
1391
|
+
for (const item of block.items) {
|
|
1392
|
+
const listItem = createMarkedElement(document, "li");
|
|
1393
|
+
listItem.textContent = item.text;
|
|
1394
|
+
appendSourceChips(document, listItem, item.sourceIds, sourceMap, messages);
|
|
1395
|
+
list.append(listItem);
|
|
1396
|
+
}
|
|
1397
|
+
blocks.append(list);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
for (const source of result.sources) {
|
|
1401
|
+
const button = createButton(
|
|
1402
|
+
document,
|
|
1403
|
+
messages.sourceLabel(source.relativePath, source.startLine, source.endLine),
|
|
1404
|
+
"spotpatch-ask-source"
|
|
1405
|
+
);
|
|
1406
|
+
button.dataset.askSourceId = source.sourceId;
|
|
1407
|
+
button.title = source.label;
|
|
1408
|
+
sourceList.append(button);
|
|
1409
|
+
}
|
|
1410
|
+
sourcesSection.hidden = result.sources.length === 0;
|
|
1411
|
+
answer.hidden = false;
|
|
1412
|
+
status.hidden = true;
|
|
1413
|
+
busy = false;
|
|
1414
|
+
refreshSubmitState();
|
|
1415
|
+
input.onViewChange();
|
|
1416
|
+
}
|
|
1417
|
+
function answerPlainText() {
|
|
1418
|
+
if (currentResult === void 0) return "";
|
|
1419
|
+
const body = currentResult.blocks.flatMap(
|
|
1420
|
+
(block) => block.kind === "paragraph" ? [block.text] : block.kind === "code" ? [block.code] : block.items.map((item) => `- ${item.text}`)
|
|
1421
|
+
).join("\n\n");
|
|
1422
|
+
const sourceText = currentResult.sources.map(
|
|
1423
|
+
(source) => messages.sourceLabel(source.relativePath, source.startLine, source.endLine)
|
|
1424
|
+
).join("\n");
|
|
1425
|
+
return sourceText.length === 0 ? body : `${body}
|
|
1426
|
+
|
|
1427
|
+
${messages.sourcesTitle}
|
|
1428
|
+
${sourceText}`;
|
|
1429
|
+
}
|
|
1430
|
+
function clear() {
|
|
1431
|
+
currentResult = void 0;
|
|
1432
|
+
currentJob = void 0;
|
|
1433
|
+
input.onExecutionChange();
|
|
1434
|
+
questionInput.value = "";
|
|
1435
|
+
answer.hidden = true;
|
|
1436
|
+
status.hidden = true;
|
|
1437
|
+
error.hidden = true;
|
|
1438
|
+
activities.replaceChildren();
|
|
1439
|
+
stale.hidden = true;
|
|
1440
|
+
busy = false;
|
|
1441
|
+
refreshSubmitState();
|
|
1442
|
+
input.onViewChange();
|
|
1443
|
+
}
|
|
1444
|
+
askTab.addEventListener("click", () => {
|
|
1445
|
+
currentMode = "ask";
|
|
1446
|
+
applyMode();
|
|
1447
|
+
questionInput.focus({ preventScroll: true });
|
|
1448
|
+
});
|
|
1449
|
+
changeTab.addEventListener("click", () => {
|
|
1450
|
+
currentMode = "change";
|
|
1451
|
+
applyMode();
|
|
1452
|
+
});
|
|
1453
|
+
questionInput.addEventListener("input", refreshSubmitState);
|
|
1454
|
+
executorSelect.addEventListener("change", refreshSubmitState);
|
|
1455
|
+
executorTrigger.addEventListener("click", () => {
|
|
1456
|
+
if (executorMenu.hidden) openExecutorMenu();
|
|
1457
|
+
else closeExecutorMenu();
|
|
1458
|
+
});
|
|
1459
|
+
executorTrigger.addEventListener("keydown", (event) => {
|
|
1460
|
+
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
|
|
1461
|
+
event.preventDefault();
|
|
1462
|
+
openExecutorMenu(true);
|
|
1463
|
+
});
|
|
1464
|
+
executorMenu.addEventListener("keydown", (event) => {
|
|
1465
|
+
const options = [
|
|
1466
|
+
...executorMenu.querySelectorAll(
|
|
1467
|
+
".spotpatch-ask-executor-option"
|
|
1468
|
+
)
|
|
1469
|
+
];
|
|
1470
|
+
if (event.key === "Escape") {
|
|
1471
|
+
event.preventDefault();
|
|
1472
|
+
closeExecutorMenu(true);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
1476
|
+
event.preventDefault();
|
|
1477
|
+
const currentIndex = options.indexOf(document.activeElement);
|
|
1478
|
+
const nextIndex = event.key === "Home" ? 0 : event.key === "End" ? options.length - 1 : event.key === "ArrowDown" ? (currentIndex + 1 + options.length) % options.length : (currentIndex - 1 + options.length) % options.length;
|
|
1479
|
+
options[nextIndex]?.focus({ preventScroll: true });
|
|
1480
|
+
});
|
|
1481
|
+
executorPicker.addEventListener("focusout", (event) => {
|
|
1482
|
+
if (!(event.relatedTarget instanceof Node) || !executorPicker.contains(event.relatedTarget)) {
|
|
1483
|
+
closeExecutorMenu();
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
const closeExecutorMenuOutside = (event) => {
|
|
1487
|
+
if (!event.composedPath().includes(executorPicker)) closeExecutorMenu();
|
|
1488
|
+
};
|
|
1489
|
+
document.addEventListener("pointerdown", closeExecutorMenuOutside);
|
|
1490
|
+
consentCheckbox.addEventListener("change", refreshSubmitState);
|
|
1491
|
+
const unsubscribeLocale = input.subscribeLocale(applyMessages);
|
|
1492
|
+
applyMessages();
|
|
1493
|
+
applyMode();
|
|
1494
|
+
return Object.freeze({
|
|
1495
|
+
root,
|
|
1496
|
+
styles: createStyles(document),
|
|
1497
|
+
questionInput,
|
|
1498
|
+
executorSelect,
|
|
1499
|
+
consentCheckbox,
|
|
1500
|
+
submitButton,
|
|
1501
|
+
cancelButton,
|
|
1502
|
+
newQuestionButton,
|
|
1503
|
+
copyButton,
|
|
1504
|
+
convertButton,
|
|
1505
|
+
clear,
|
|
1506
|
+
dispose: () => {
|
|
1507
|
+
unsubscribeLocale();
|
|
1508
|
+
document.removeEventListener("pointerdown", closeExecutorMenuOutside);
|
|
1509
|
+
},
|
|
1510
|
+
focusQuestion: () => {
|
|
1511
|
+
questionInput.focus({ preventScroll: true });
|
|
1512
|
+
},
|
|
1513
|
+
mode: () => currentMode,
|
|
1514
|
+
notify(event) {
|
|
1515
|
+
const message = event === "copied" ? messages.copied : event === "copy-failed" ? messages.copyFailed : event === "converted" ? messages.converted : messages.sourceOpenFailed;
|
|
1516
|
+
input.announce(message);
|
|
1517
|
+
},
|
|
1518
|
+
readConsent: () => consentCheckbox.checked,
|
|
1519
|
+
readExecutorId: () => executorSelect.value || void 0,
|
|
1520
|
+
readQuestion: () => questionInput.value.trim(),
|
|
1521
|
+
renderAnswer,
|
|
1522
|
+
renderCapability,
|
|
1523
|
+
renderError(code) {
|
|
1524
|
+
currentJob = void 0;
|
|
1525
|
+
input.onExecutionChange();
|
|
1526
|
+
error.textContent = messages.error(code);
|
|
1527
|
+
error.hidden = false;
|
|
1528
|
+
status.hidden = true;
|
|
1529
|
+
busy = false;
|
|
1530
|
+
refreshSubmitState();
|
|
1531
|
+
input.onViewChange();
|
|
1532
|
+
},
|
|
1533
|
+
renderEvent,
|
|
1534
|
+
renderJob,
|
|
1535
|
+
selectedSourceId(target) {
|
|
1536
|
+
return target instanceof Element ? target.closest("[data-ask-source-id]")?.dataset.askSourceId : void 0;
|
|
1537
|
+
},
|
|
1538
|
+
setBusy(nextBusy) {
|
|
1539
|
+
busy = nextBusy;
|
|
1540
|
+
if (nextBusy) {
|
|
1541
|
+
status.hidden = false;
|
|
1542
|
+
error.hidden = true;
|
|
1543
|
+
answer.hidden = true;
|
|
1544
|
+
activities.replaceChildren();
|
|
1545
|
+
}
|
|
1546
|
+
refreshSubmitState();
|
|
1547
|
+
},
|
|
1548
|
+
setConsent(granted) {
|
|
1549
|
+
consentCheckbox.checked = granted;
|
|
1550
|
+
refreshSubmitState();
|
|
1551
|
+
},
|
|
1552
|
+
setMode(mode) {
|
|
1553
|
+
currentMode = mode;
|
|
1554
|
+
applyMode();
|
|
1555
|
+
},
|
|
1556
|
+
setOrigin(nextOrigin) {
|
|
1557
|
+
origin.dataset.active = String(nextOrigin !== void 0);
|
|
1558
|
+
origin.hidden = currentMode === "ask" || nextOrigin === void 0;
|
|
1559
|
+
input.onViewChange();
|
|
1560
|
+
},
|
|
1561
|
+
setSelectionPreview(preview) {
|
|
1562
|
+
currentPreview = preview;
|
|
1563
|
+
dataText.textContent = messages.dataSummary(
|
|
1564
|
+
preview.targetCount,
|
|
1565
|
+
preview.sourceCount
|
|
1566
|
+
);
|
|
1567
|
+
refreshSubmitState();
|
|
1568
|
+
},
|
|
1569
|
+
setSelectionVisible(visible) {
|
|
1570
|
+
root.hidden = !visible;
|
|
1571
|
+
if (visible) applyMode();
|
|
1572
|
+
},
|
|
1573
|
+
sourceById: (sourceId) => currentResult?.sources.find((source) => source.sourceId === sourceId),
|
|
1574
|
+
answerPlainText
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/contextual-ask-panel-entry.ts
|
|
1579
|
+
var EXTENSION = Object.freeze({
|
|
1580
|
+
createPanel: createContextualAskPanel,
|
|
1581
|
+
createWorkflow: createContextualAskWorkflow
|
|
1582
|
+
});
|
|
1583
|
+
function installContextualAskExtension() {
|
|
1584
|
+
registerContextualAskExtension(EXTENSION);
|
|
1585
|
+
}
|
|
1586
|
+
export {
|
|
1587
|
+
createContextualAskPanel,
|
|
1588
|
+
createContextualAskWorkflow,
|
|
1589
|
+
getContextualAskExtension,
|
|
1590
|
+
installContextualAskExtension,
|
|
1591
|
+
registerContextualAskExtension
|
|
1592
|
+
};
|
|
1593
|
+
//# sourceMappingURL=contextual-ask-panel.js.map
|