dsh-codex-subscription 2.1.0-beta.4 → 2.1.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +987 -898
- package/lib/index.js +49 -16
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -31,937 +31,968 @@ window.__ModuleLoader__.load({
|
|
|
31
31
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
32
32
|
let react_dom = require("react-dom");
|
|
33
33
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
34
|
-
//#region src/
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
throw error;
|
|
34
|
+
//#region src/sketch-curves.js
|
|
35
|
+
const cached = /* @__PURE__ */ new WeakMap();
|
|
36
|
+
const midpoint = (a, b) => ({
|
|
37
|
+
x: (a.x + b.x) / 2,
|
|
38
|
+
y: (a.y + b.y) / 2
|
|
39
|
+
});
|
|
40
|
+
function flattenSketchCurve(stroke, width, height) {
|
|
41
|
+
const previous = cached.get(stroke);
|
|
42
|
+
if (previous?.width === width && previous.height === height) return previous.points;
|
|
43
|
+
const controls = stroke.points.map((p) => ({
|
|
44
|
+
x: p.x * width,
|
|
45
|
+
y: p.y * height
|
|
46
|
+
})), points = [controls[0]];
|
|
47
|
+
const distance = (p, a, b) => {
|
|
48
|
+
const dx = b.x - a.x, dy = b.y - a.y, d = dx * dx + dy * dy;
|
|
49
|
+
const t = d ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / d)) : 0;
|
|
50
|
+
return Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy);
|
|
51
|
+
};
|
|
52
|
+
const split = (a, b, c, d, depth) => {
|
|
53
|
+
if (depth === 10 || Math.max(distance(b, a, d), distance(c, a, d)) <= .5) {
|
|
54
|
+
points.push(d);
|
|
55
|
+
return;
|
|
57
56
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const rounded = Number(value.toFixed(2));
|
|
72
|
-
return String(rounded);
|
|
73
|
-
};
|
|
74
|
-
const withNames = (value, sourceName, referenceName) => String(value).replaceAll("{sourceName}", sourceName).replaceAll("{referenceName}", referenceName);
|
|
75
|
-
const positiveImageDimension = (value) => Number.isSafeInteger(value) && value > 0;
|
|
76
|
-
function buildImageEditDraft({ prompt = "", annotations = [], translate, width, height, sourceName = "source.png", referenceName = "annotated-reference.png" }) {
|
|
77
|
-
if (typeof translate !== "function") throw new TypeError("translate must be a function");
|
|
78
|
-
const base = typeof prompt === "string" && prompt.trim() !== "" ? prompt.trim() : translate("imageEditDefault");
|
|
79
|
-
if (!Array.isArray(annotations)) throw new TypeError("annotations must be an array");
|
|
80
|
-
if (annotations.length === 0) return base;
|
|
81
|
-
const hasWidth = width !== void 0;
|
|
82
|
-
if (hasWidth !== (height !== void 0) || hasWidth && (!positiveImageDimension(width) || !positiveImageDimension(height))) throw new Error("width and height must be positive integers when provided");
|
|
83
|
-
const normalized = normalizeImageEditAnnotations(annotations, { requireNotes: true });
|
|
84
|
-
const source = typeof sourceName === "string" && sourceName.trim() !== "" ? sourceName.trim() : "source.png";
|
|
85
|
-
const reference = typeof referenceName === "string" && referenceName.trim() !== "" ? referenceName.trim() : "annotated-reference.png";
|
|
86
|
-
const guide = withNames(translate("imageEditReferenceGuide"), source, reference);
|
|
87
|
-
const location = translate("imageEditLocation");
|
|
88
|
-
const notes = normalized.map(({ number, x, y, note }) => {
|
|
89
|
-
const pixels = hasWidth ? ` (pixel x=${formatPixel(x * Math.max(0, width - 1))} of ${width}, y=${formatPixel(y * Math.max(0, height - 1))} of ${height})` : "";
|
|
90
|
-
return `${number}. ${location}: x=${formatPercent(x)} (normalized ${x}), y=${formatPercent(y)} (normalized ${y})${pixels}; ${note}`;
|
|
57
|
+
const ab = midpoint(a, b), bc = midpoint(b, c), cd = midpoint(c, d), abc = midpoint(ab, bc), bcd = midpoint(bc, cd), m = midpoint(abc, bcd);
|
|
58
|
+
split(a, ab, abc, m, depth + 1);
|
|
59
|
+
split(m, bcd, cd, d, depth + 1);
|
|
60
|
+
};
|
|
61
|
+
for (let i = 1; i < controls.length; i += 3) split(controls[i - 1], controls[i], controls[i + 1], controls[i + 2], 0);
|
|
62
|
+
const normalized = points.map((p) => ({
|
|
63
|
+
x: p.x / width,
|
|
64
|
+
y: p.y / height
|
|
65
|
+
}));
|
|
66
|
+
cached.set(stroke, {
|
|
67
|
+
width,
|
|
68
|
+
height,
|
|
69
|
+
points: normalized
|
|
91
70
|
});
|
|
92
|
-
return
|
|
93
|
-
base,
|
|
94
|
-
"",
|
|
95
|
-
guide,
|
|
96
|
-
"",
|
|
97
|
-
translate("imageRegionNotes"),
|
|
98
|
-
...notes
|
|
99
|
-
].join("\n");
|
|
71
|
+
return normalized;
|
|
100
72
|
}
|
|
101
73
|
//#endregion
|
|
102
|
-
//#region src/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
label: t("imageEdit"),
|
|
113
|
-
pendingLabel: t("imageEditPreparing"),
|
|
114
|
-
errorLabel: t("imageEditFailed"),
|
|
115
|
-
closeOnSuccess: true,
|
|
116
|
-
onInvoke: ({ annotations }) => attachForEdit(item.src, item.name, buildImageEditDraft({
|
|
117
|
-
annotations,
|
|
118
|
-
translate: t,
|
|
119
|
-
sourceName: item.name,
|
|
120
|
-
referenceName
|
|
121
|
-
}), annotations, referenceName, sourceInDraft)
|
|
122
|
-
}, ...settings.imageSketch && props.openSketchImage ? [{
|
|
123
|
-
id: "sketch",
|
|
124
|
-
label: t("imageToSketch"),
|
|
125
|
-
pendingLabel: t("imageEditPreparing"),
|
|
126
|
-
errorLabel: t("imageEditFailed"),
|
|
127
|
-
onInvoke: () => props.openSketchImage(item.src, item.name)
|
|
128
|
-
}] : []] : []
|
|
129
|
-
}],
|
|
130
|
-
opener,
|
|
131
|
-
source: sourceInDraft ? "codex-draft" : "codex-message",
|
|
132
|
-
annotations: settings.imageAnnotations
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
function ComposerImagePreviews(props) {
|
|
136
|
-
const { attachments, service, nativeAttachments, watchNativeAttachments, nativeTranslate } = props;
|
|
137
|
-
const entry = (0, react.useSyncExternalStore)(watchNativeAttachments, nativeAttachments);
|
|
138
|
-
(0, react.useEffect)(() => {
|
|
139
|
-
const current = service.getSnapshot();
|
|
140
|
-
if (current?.source === "codex-draft" && !attachments.some((item) => item.id === current.items[0]?.id)) service.close();
|
|
141
|
-
}, [attachments, service]);
|
|
142
|
-
(0, react.useEffect)(() => () => {
|
|
143
|
-
if (service.getSnapshot()?.source === "codex-draft") service.close();
|
|
144
|
-
}, [service]);
|
|
145
|
-
if (!entry) return null;
|
|
146
|
-
const NativeAttachments = entry.component;
|
|
147
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
148
|
-
style: { display: "contents" },
|
|
149
|
-
onClickCapture: (event) => {
|
|
150
|
-
const button = event.target.closest("button"), image = button?.querySelector("img");
|
|
151
|
-
const item = image && attachments.find((item) => item.previewUrl === image.src);
|
|
152
|
-
if (!item || event.button !== 0 || event.ctrlKey || event.metaKey || event.altKey) return;
|
|
153
|
-
event.preventDefault();
|
|
154
|
-
event.stopPropagation();
|
|
155
|
-
openPreview(props, {
|
|
156
|
-
id: item.id,
|
|
157
|
-
src: item.previewUrl,
|
|
158
|
-
name: item.file.name,
|
|
159
|
-
width: item.width,
|
|
160
|
-
height: item.height,
|
|
161
|
-
bytes: item.file.size
|
|
162
|
-
}, button, true);
|
|
163
|
-
},
|
|
164
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NativeAttachments, {
|
|
165
|
-
...props,
|
|
166
|
-
t: nativeTranslate
|
|
167
|
-
})
|
|
168
|
-
});
|
|
74
|
+
//#region src/sketch-document.js
|
|
75
|
+
const SKETCH_SIZE = 1024;
|
|
76
|
+
const MAX_SKETCH_STROKES = 2e3;
|
|
77
|
+
const MAX_STROKE_POINTS = 2e3;
|
|
78
|
+
function sketchPoint(clientX, clientY, rect) {
|
|
79
|
+
if (!(rect.width > 0 && rect.height > 0)) return void 0;
|
|
80
|
+
return {
|
|
81
|
+
x: Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)),
|
|
82
|
+
y: Math.max(0, Math.min(1, (clientY - rect.top) / rect.height))
|
|
83
|
+
};
|
|
169
84
|
}
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
85
|
+
function paintSketch(context, strokes, size = SKETCH_SIZE, transparent = false, height = size, start = 0, end = strokes.length) {
|
|
86
|
+
context.globalCompositeOperation = "source-over";
|
|
87
|
+
context.globalAlpha = 1;
|
|
88
|
+
if (!transparent) {
|
|
89
|
+
context.fillStyle = "#ffffff";
|
|
90
|
+
context.fillRect(0, 0, size, height);
|
|
91
|
+
}
|
|
92
|
+
context.lineCap = "round";
|
|
93
|
+
context.lineJoin = "round";
|
|
94
|
+
for (let index = start; index < end; index++) {
|
|
95
|
+
const stroke = strokes[index];
|
|
96
|
+
const first = stroke.points[0];
|
|
97
|
+
if (!first) continue;
|
|
98
|
+
context.globalCompositeOperation = stroke.shape === "eraser" ? "destination-out" : "source-over";
|
|
99
|
+
context.globalAlpha = (stroke.opacity ?? 1) * (stroke.brush === "marker" ? .28 : stroke.brush === "pencil" ? .65 : 1);
|
|
100
|
+
context.strokeStyle = stroke.color;
|
|
101
|
+
context.fillStyle = stroke.color;
|
|
102
|
+
context.lineWidth = stroke.width * (stroke.brush === "pencil" ? .55 : 1) * (stroke.pressure ?? 1);
|
|
103
|
+
context.beginPath();
|
|
104
|
+
const last = stroke.points.at(-1);
|
|
105
|
+
if (stroke.shape === "text") {
|
|
106
|
+
const x = Math.min(first.x, last.x) * size, y = Math.min(first.y, last.y) * height, w = Math.abs(last.x - first.x) * size, h = Math.abs(last.y - first.y) * height;
|
|
107
|
+
const lines = stroke.text.split("\n"), fontSize = Math.min(stroke.width, h / Math.max(1, lines.length) / 1.2);
|
|
108
|
+
context.font = `${fontSize}px system-ui, sans-serif`;
|
|
109
|
+
context.textBaseline = "top";
|
|
110
|
+
lines.forEach((line, i) => context.fillText(line, x, y + i * fontSize * 1.2, w));
|
|
111
|
+
} else if (stroke.shape === "arrow") {
|
|
112
|
+
const x = last.x * size, y = last.y * height, a = Math.atan2(y - first.y * height, x - first.x * size), head = Math.min(Math.hypot(x - first.x * size, y - first.y * height) * .4, Math.max(12, stroke.width * 3));
|
|
113
|
+
context.moveTo(first.x * size, first.y * height);
|
|
114
|
+
context.lineTo(x, y);
|
|
115
|
+
context.stroke();
|
|
116
|
+
context.beginPath();
|
|
117
|
+
context.moveTo(x, y);
|
|
118
|
+
context.lineTo(x - head * Math.cos(a - .5), y - head * Math.sin(a - .5));
|
|
119
|
+
context.lineTo(x - head * Math.cos(a + .5), y - head * Math.sin(a + .5));
|
|
120
|
+
context.closePath();
|
|
121
|
+
context.fill();
|
|
122
|
+
} else if (stroke.shape === "bezier") {
|
|
123
|
+
context.moveTo(first.x * size, first.y * height);
|
|
124
|
+
for (let i = 1; i < stroke.points.length; i += 3) {
|
|
125
|
+
const [a, b, c] = stroke.points.slice(i, i + 3);
|
|
126
|
+
context.bezierCurveTo(a.x * size, a.y * height, b.x * size, b.y * height, c.x * size, c.y * height);
|
|
206
127
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
128
|
+
if (stroke.fill) {
|
|
129
|
+
context.closePath();
|
|
130
|
+
context.fill();
|
|
131
|
+
} else context.stroke();
|
|
132
|
+
} else if (stroke.shape === "line") {
|
|
133
|
+
context.moveTo(first.x * size, first.y * height);
|
|
134
|
+
context.lineTo(last.x * size, last.y * height);
|
|
135
|
+
context.stroke();
|
|
136
|
+
} else if (stroke.shape === "rectangle") {
|
|
137
|
+
context.rect(first.x * size, first.y * height, (last.x - first.x) * size, (last.y - first.y) * height);
|
|
138
|
+
if (stroke.fill) context.fill();
|
|
139
|
+
else context.stroke();
|
|
140
|
+
} else if (stroke.shape === "circle") {
|
|
141
|
+
context.ellipse((first.x + last.x) * size / 2, (first.y + last.y) * height / 2, Math.abs(last.x - first.x) * size / 2, Math.abs(last.y - first.y) * height / 2, 0, 0, Math.PI * 2);
|
|
142
|
+
if (stroke.fill) context.fill();
|
|
143
|
+
else context.stroke();
|
|
144
|
+
} else if (stroke.shape === "polygon") {
|
|
145
|
+
context.moveTo(first.x * size, first.y * height);
|
|
146
|
+
for (const point of stroke.points.slice(1)) context.lineTo(point.x * size, point.y * height);
|
|
147
|
+
context.closePath();
|
|
148
|
+
if (stroke.fill) context.fill();
|
|
149
|
+
else context.stroke();
|
|
150
|
+
} else if (stroke.points.length === 1) {
|
|
151
|
+
context.arc(first.x * size, first.y * height, context.lineWidth / 2, 0, Math.PI * 2);
|
|
152
|
+
context.fill();
|
|
153
|
+
} else {
|
|
154
|
+
context.moveTo(first.x * size, first.y * height);
|
|
155
|
+
for (let i = 1; i < stroke.points.length - 1; i++) {
|
|
156
|
+
const point = stroke.points[i], next = stroke.points[i + 1];
|
|
157
|
+
if (context.quadraticCurveTo) context.quadraticCurveTo(point.x * size, point.y * height, (point.x + next.x) * size / 2, (point.y + next.y) * height / 2);
|
|
158
|
+
else context.lineTo(point.x * size, point.y * height);
|
|
159
|
+
}
|
|
160
|
+
context.lineTo(last.x * size, last.y * height);
|
|
161
|
+
context.stroke();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
context.globalCompositeOperation = "source-over";
|
|
165
|
+
context.globalAlpha = 1;
|
|
231
166
|
}
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
167
|
+
const createSketchLayers = () => ({
|
|
168
|
+
active: 1,
|
|
169
|
+
nextId: 2,
|
|
170
|
+
layers: [{
|
|
171
|
+
id: 1,
|
|
172
|
+
name: "",
|
|
173
|
+
visible: true,
|
|
174
|
+
strokes: []
|
|
175
|
+
}]
|
|
176
|
+
});
|
|
177
|
+
const strokeCount = (doc) => doc.layers.reduce((n, layer) => n + layer.strokes.length, 0);
|
|
178
|
+
function changeSketchLayer(doc, action, id = doc.active, value) {
|
|
179
|
+
const index = doc.layers.findIndex((layer) => layer.id === id);
|
|
180
|
+
if (index < 0) return doc;
|
|
181
|
+
const layers = doc.layers.slice(), layer = layers[index];
|
|
182
|
+
if (action === "select") return {
|
|
183
|
+
...doc,
|
|
184
|
+
active: id
|
|
185
|
+
};
|
|
186
|
+
if (action === "add" || action === "duplicate") {
|
|
187
|
+
if (layers.length >= 8 || action === "duplicate" && strokeCount(doc) + layer.strokes.length > 2e3) return doc;
|
|
188
|
+
const next = action === "add" ? {
|
|
189
|
+
id: doc.nextId,
|
|
190
|
+
name: "",
|
|
191
|
+
visible: true,
|
|
192
|
+
strokes: []
|
|
193
|
+
} : {
|
|
194
|
+
...layer,
|
|
195
|
+
id: doc.nextId,
|
|
196
|
+
strokes: layer.strokes.slice()
|
|
197
|
+
};
|
|
198
|
+
layers.splice(index + 1, 0, next);
|
|
250
199
|
return {
|
|
251
|
-
|
|
252
|
-
|
|
200
|
+
...doc,
|
|
201
|
+
layers,
|
|
202
|
+
active: next.id,
|
|
203
|
+
nextId: doc.nextId + 1
|
|
253
204
|
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const end = context.matches.find((match) => match.event.type === "turn/end");
|
|
259
|
-
if (!end) return null;
|
|
260
|
-
const blocks = context.matches.flatMap(({ event }) => resultsOf(event).map((block) => ({
|
|
261
|
-
...block,
|
|
262
|
-
kind: "tool-result",
|
|
263
|
-
meta: event.data.meta
|
|
264
|
-
})));
|
|
265
|
-
if (!blocks.length) return null;
|
|
266
|
-
const answer = end.location.turn?.steps?.at(-1)?.data.get("assistant-step");
|
|
267
|
-
const lastResultSeq = Math.max(...context.matches.filter((match) => resultsOf(match.event).length).map((match) => match.event.seq));
|
|
268
|
-
const answerSeq = answer?.finalNode?.seq;
|
|
269
|
-
const anchorSeq = answerSeq > lastResultSeq ? answerSeq + .025 : end.event.seq - .025;
|
|
205
|
+
}
|
|
206
|
+
if (action === "delete") {
|
|
207
|
+
if (layers.length === 1) return doc;
|
|
208
|
+
layers.splice(index, 1);
|
|
270
209
|
return {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
target: "chat",
|
|
275
|
-
location: end.location,
|
|
276
|
-
anchorSeq,
|
|
277
|
-
visibility: "visible",
|
|
278
|
-
data: { blocks }
|
|
210
|
+
...doc,
|
|
211
|
+
layers,
|
|
212
|
+
active: doc.active === id ? layers[Math.min(index, layers.length - 1)].id : doc.active
|
|
279
213
|
};
|
|
280
214
|
}
|
|
215
|
+
if (action === "up" || action === "down") {
|
|
216
|
+
const target = index + (action === "up" ? 1 : -1);
|
|
217
|
+
if (!layers[target]) return doc;
|
|
218
|
+
[layers[index], layers[target]] = [layers[target], layer];
|
|
219
|
+
} else if (action === "visible") layers[index] = {
|
|
220
|
+
...layer,
|
|
221
|
+
visible: !layer.visible
|
|
222
|
+
};
|
|
223
|
+
else if (action === "rename") layers[index] = {
|
|
224
|
+
...layer,
|
|
225
|
+
name: String(value).trim().slice(0, 40)
|
|
226
|
+
};
|
|
227
|
+
else if (action === "clear") layers[index] = {
|
|
228
|
+
...layer,
|
|
229
|
+
strokes: [],
|
|
230
|
+
image: void 0
|
|
231
|
+
};
|
|
232
|
+
else return doc;
|
|
233
|
+
return {
|
|
234
|
+
...doc,
|
|
235
|
+
layers
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const distanceToSegment = (p, a, b) => {
|
|
239
|
+
const dx = b.x - a.x, dy = b.y - a.y, length = dx * dx + dy * dy;
|
|
240
|
+
const k = length ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / length)) : 0;
|
|
241
|
+
return Math.hypot(p.x - a.x - k * dx, p.y - a.y - k * dy);
|
|
281
242
|
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
243
|
+
function strokeHit(stroke, point, radius, width = SKETCH_SIZE, height = width) {
|
|
244
|
+
let points = stroke.shape === "bezier" ? flattenSketchCurve(stroke, width, height) : stroke.points;
|
|
245
|
+
if (!points.length) return false;
|
|
246
|
+
const a = points[0], b = points.at(-1);
|
|
247
|
+
if ((stroke.shape === "text" || stroke.fill && stroke.shape === "rectangle") && point.x >= Math.min(a.x, b.x) && point.x <= Math.max(a.x, b.x) && point.y >= Math.min(a.y, b.y) && point.y <= Math.max(a.y, b.y)) return true;
|
|
248
|
+
if (stroke.fill && stroke.shape === "circle") {
|
|
249
|
+
const rx = Math.abs(b.x - a.x) / 2, ry = Math.abs(b.y - a.y) / 2;
|
|
250
|
+
if (rx && ry && ((point.x - (a.x + b.x) / 2) / rx) ** 2 + ((point.y - (a.y + b.y) / 2) / ry) ** 2 <= 1) return true;
|
|
251
|
+
}
|
|
252
|
+
if (stroke.shape === "polygon" || stroke.shape === "bezier" && stroke.fill) {
|
|
253
|
+
if (stroke.fill) {
|
|
254
|
+
let inside = false;
|
|
255
|
+
for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
|
|
256
|
+
const p = points[i], q = points[j];
|
|
257
|
+
if (p.y > point.y !== q.y > point.y && point.x < (q.x - p.x) * (point.y - p.y) / (q.y - p.y) + p.x) inside = !inside;
|
|
258
|
+
}
|
|
259
|
+
if (inside) return true;
|
|
260
|
+
}
|
|
261
|
+
points = [...points, points[0]];
|
|
262
|
+
}
|
|
263
|
+
if (stroke.shape === "rectangle") points = [
|
|
264
|
+
a,
|
|
265
|
+
{
|
|
266
|
+
x: b.x,
|
|
267
|
+
y: a.y
|
|
268
|
+
},
|
|
269
|
+
b,
|
|
270
|
+
{
|
|
271
|
+
x: a.x,
|
|
272
|
+
y: b.y
|
|
273
|
+
},
|
|
274
|
+
a
|
|
275
|
+
];
|
|
276
|
+
if (stroke.shape === "circle") points = Array.from({ length: 65 }, (_, i) => ({
|
|
277
|
+
x: (a.x + b.x) / 2 + Math.abs(b.x - a.x) / 2 * Math.cos(i * Math.PI / 32),
|
|
278
|
+
y: (a.y + b.y) / 2 + Math.abs(b.y - a.y) / 2 * Math.sin(i * Math.PI / 32)
|
|
299
279
|
}));
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
//#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ExclamationCircleIcon.js
|
|
304
|
-
function ExclamationCircleIcon({ title, titleId, ...props }, svgRef) {
|
|
305
|
-
return /*#__PURE__*/ react.createElement("svg", Object.assign({
|
|
306
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
307
|
-
fill: "none",
|
|
308
|
-
viewBox: "0 0 24 24",
|
|
309
|
-
strokeWidth: 1.5,
|
|
310
|
-
stroke: "currentColor",
|
|
311
|
-
"aria-hidden": "true",
|
|
312
|
-
"data-slot": "icon",
|
|
313
|
-
ref: svgRef,
|
|
314
|
-
"aria-labelledby": titleId
|
|
315
|
-
}, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
|
|
316
|
-
strokeLinecap: "round",
|
|
317
|
-
strokeLinejoin: "round",
|
|
318
|
-
d: "M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
|
|
280
|
+
points = points.map((p) => ({
|
|
281
|
+
x: p.x * width,
|
|
282
|
+
y: p.y * height
|
|
319
283
|
}));
|
|
284
|
+
point = {
|
|
285
|
+
x: point.x * width,
|
|
286
|
+
y: point.y * height
|
|
287
|
+
};
|
|
288
|
+
const tolerance = radius + stroke.width / 2;
|
|
289
|
+
return points.some((p, i) => distanceToSegment(point, i ? points[i - 1] : p, p) <= tolerance);
|
|
320
290
|
}
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
|
|
336
|
-
strokeLinecap: "round",
|
|
337
|
-
strokeLinejoin: "round",
|
|
338
|
-
d: "m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
|
|
339
|
-
}));
|
|
340
|
-
}
|
|
341
|
-
const ForwardRef$1 = /*#__PURE__*/ react.forwardRef(PhotoIcon);
|
|
342
|
-
const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
|
|
343
|
-
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
|
|
344
|
-
function decodeOriginalImageRef(value) {
|
|
345
|
-
if (value === null || typeof value !== "object" || Array.isArray(value) || typeof value.assetId !== "string" || !ORIGINAL_IMAGE_ID_PATTERN.test(value.assetId) || value.mediaType !== "image/png" || !positiveInteger(value.bytes) || value.bytes > 48 * 1024 * 1024 || !positiveInteger(value.width) || !positiveInteger(value.height) || typeof value.name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value.name) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(value.sha256)) return void 0;
|
|
291
|
+
const SKETCH_RATIOS = Object.freeze({
|
|
292
|
+
"1:1": [1024, 1024],
|
|
293
|
+
"4:3": [1024, 768],
|
|
294
|
+
"3:4": [768, 1024],
|
|
295
|
+
"16:9": [1024, 576],
|
|
296
|
+
"9:16": [576, 1024]
|
|
297
|
+
});
|
|
298
|
+
function resizeSketch(doc, ratio) {
|
|
299
|
+
if (!Object.hasOwn(SKETCH_RATIOS, ratio)) throw new Error("Invalid sketch ratio");
|
|
300
|
+
const [width, height] = SKETCH_RATIOS[ratio];
|
|
301
|
+
const oldWidth = doc.width ?? 1024, oldHeight = doc.height ?? 1024;
|
|
302
|
+
if (width === oldWidth && height === oldHeight) return doc;
|
|
303
|
+
const scale = Math.min(width / oldWidth, height / oldHeight);
|
|
304
|
+
const dx = (width - oldWidth * scale) / 2, dy = (height - oldHeight * scale) / 2;
|
|
346
305
|
return {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
306
|
+
...doc,
|
|
307
|
+
width,
|
|
308
|
+
height,
|
|
309
|
+
ratio,
|
|
310
|
+
layers: doc.layers.map((layer) => ({
|
|
311
|
+
...layer,
|
|
312
|
+
...layer.image ? { image: {
|
|
313
|
+
...layer.image,
|
|
314
|
+
x: (layer.image.x * oldWidth * scale + dx) / width,
|
|
315
|
+
y: (layer.image.y * oldHeight * scale + dy) / height,
|
|
316
|
+
width: layer.image.width * oldWidth * scale / width,
|
|
317
|
+
height: layer.image.height * oldHeight * scale / height
|
|
318
|
+
} } : {},
|
|
319
|
+
strokes: layer.strokes.map((stroke) => ({
|
|
320
|
+
...stroke,
|
|
321
|
+
width: stroke.width * scale,
|
|
322
|
+
points: stroke.points.map((p) => ({
|
|
323
|
+
x: (p.x * oldWidth * scale + dx) / width,
|
|
324
|
+
y: (p.y * oldHeight * scale + dy) / height
|
|
325
|
+
}))
|
|
326
|
+
}))
|
|
327
|
+
}))
|
|
354
328
|
};
|
|
355
329
|
}
|
|
356
|
-
function decodeImagePresentation(value) {
|
|
357
|
-
if (value === null || typeof value !== "object" || Array.isArray(value) || value.kind !== "codex-subscription-image" || value.schemaVersion !== 1) return void 0;
|
|
358
|
-
const original = decodeOriginalImageRef(value.original);
|
|
359
|
-
return original === void 0 ? void 0 : { original };
|
|
360
|
-
}
|
|
361
|
-
function originalImageRefsEqual(left, right) {
|
|
362
|
-
const a = decodeOriginalImageRef(left);
|
|
363
|
-
const b = decodeOriginalImageRef(right);
|
|
364
|
-
return a !== void 0 && b !== void 0 && a.assetId === b.assetId && a.mediaType === b.mediaType && a.bytes === b.bytes && a.width === b.width && a.height === b.height && a.name === b.name && a.sha256 === b.sha256;
|
|
365
|
-
}
|
|
366
330
|
//#endregion
|
|
367
|
-
//#region src/
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
"reset-credit/inspect",
|
|
384
|
-
"reset-credit/prepare",
|
|
385
|
-
"reset-credit/consume",
|
|
386
|
-
"image/original/chunk",
|
|
387
|
-
"sketch/connect",
|
|
388
|
-
"sketch/poll",
|
|
389
|
-
"sketch/claim",
|
|
390
|
-
"sketch/result",
|
|
391
|
-
"sketch/disconnect"
|
|
392
|
-
]);
|
|
393
|
-
function createSubscriptionRpcClient(transport) {
|
|
394
|
-
return Object.freeze({ call(channel, endpoint, payload, signal) {
|
|
395
|
-
if (channel !== "/codex-subscription" || !RPC_ENDPOINTS.includes(endpoint)) throw new Error("Invalid subscription RPC target");
|
|
396
|
-
return transport.call("/api", `codex-subscription/${endpoint}`, payload, signal);
|
|
397
|
-
} });
|
|
331
|
+
//#region src/sketch-session-state.js
|
|
332
|
+
function createSketchSessionState() {
|
|
333
|
+
const ref = (current) => ({ current });
|
|
334
|
+
return {
|
|
335
|
+
doc: ref(createSketchLayers()),
|
|
336
|
+
undo: ref([]),
|
|
337
|
+
redo: ref([]),
|
|
338
|
+
images: ref(/* @__PURE__ */ new Map()),
|
|
339
|
+
saved: ref(null),
|
|
340
|
+
dirty: ref(false),
|
|
341
|
+
documentId: ref(crypto.randomUUID()),
|
|
342
|
+
documentRevision: ref(0),
|
|
343
|
+
agentAdapter: ref({}),
|
|
344
|
+
agentSession: ref(null),
|
|
345
|
+
agentRun: ref(null)
|
|
346
|
+
};
|
|
398
347
|
}
|
|
399
|
-
function
|
|
400
|
-
|
|
401
|
-
return
|
|
348
|
+
function createSketchSessionRegistry() {
|
|
349
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
350
|
+
return {
|
|
351
|
+
get(id) {
|
|
352
|
+
if (!sessions.has(id)) sessions.set(id, createSketchSessionState());
|
|
353
|
+
return sessions.get(id);
|
|
354
|
+
},
|
|
355
|
+
dispose() {
|
|
356
|
+
for (const value of sessions.values()) value.agentRun.current?.dispose();
|
|
357
|
+
sessions.clear();
|
|
358
|
+
}
|
|
359
|
+
};
|
|
402
360
|
}
|
|
403
361
|
//#endregion
|
|
404
|
-
//#region src/
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const bytes = decodeBase64Chunk(chunk.encoded);
|
|
437
|
-
if (bytes.byteLength === 0 || total + bytes.byteLength > original.bytes) throw new Error("Original image download is incomplete");
|
|
438
|
-
data.set(bytes, total);
|
|
439
|
-
total += bytes.byteLength;
|
|
440
|
-
done = chunk.done;
|
|
441
|
-
onProgress?.({
|
|
442
|
-
loaded: total,
|
|
443
|
-
total: original.bytes
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
if (total !== original.bytes) throw new Error("Original image download is incomplete");
|
|
447
|
-
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
448
|
-
signal?.throwIfAborted();
|
|
449
|
-
if ([...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("") !== original.sha256) throw new Error("Original image integrity check failed");
|
|
450
|
-
return data;
|
|
362
|
+
//#region src/image-edit.js
|
|
363
|
+
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
364
|
+
const validCoordinate = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
365
|
+
const cleanNote = (value) => typeof value === "string" ? value.trim() : "";
|
|
366
|
+
const coordinateError = (number) => {
|
|
367
|
+
const error = /* @__PURE__ */ new Error(`Annotation ${number} must have finite x and y coordinates between 0 and 1`);
|
|
368
|
+
error.code = "ANNOTATION_INVALID";
|
|
369
|
+
return error;
|
|
370
|
+
};
|
|
371
|
+
/**
|
|
372
|
+
* Validate the annotation contract shared by the draft builder and the
|
|
373
|
+
* reference-image renderer. Annotation numbers are their array positions so
|
|
374
|
+
* that they stay aligned with the pins shown to the user.
|
|
375
|
+
*/
|
|
376
|
+
function normalizeImageEditAnnotations(annotations, { requireNotes = false } = {}) {
|
|
377
|
+
if (!Array.isArray(annotations)) throw new TypeError("annotations must be an array");
|
|
378
|
+
return annotations.map((annotation, index) => {
|
|
379
|
+
const number = index + 1;
|
|
380
|
+
const note = cleanNote(annotation?.note);
|
|
381
|
+
if (requireNotes && note === "") {
|
|
382
|
+
const error = /* @__PURE__ */ new Error(`Annotation ${number} is missing a note; describe what should change`);
|
|
383
|
+
error.code = "ANNOTATION_INVALID";
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
if (!isRecord(annotation) || !validCoordinate(annotation.x) || !validCoordinate(annotation.y)) throw coordinateError(number);
|
|
387
|
+
return {
|
|
388
|
+
number,
|
|
389
|
+
x: annotation.x,
|
|
390
|
+
y: annotation.y,
|
|
391
|
+
note
|
|
392
|
+
};
|
|
393
|
+
});
|
|
451
394
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const imageDownloadName = (attachment) => {
|
|
455
|
-
const fallback = "codex-generated-image.png";
|
|
456
|
-
if (typeof attachment?.name !== "string") return fallback;
|
|
457
|
-
const cleaned = attachment.name.replace(/[<>:"/\\|?*\u0000-\u001f]/gu, "-").replace(/[. ]+$/u, "").trim();
|
|
458
|
-
if (cleaned === "") return fallback;
|
|
459
|
-
return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
|
|
395
|
+
const formatPercent = (value) => {
|
|
396
|
+
return `${Number((value * 100).toFixed(2))}%`;
|
|
460
397
|
};
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
398
|
+
const formatPixel = (value) => {
|
|
399
|
+
const rounded = Number(value.toFixed(2));
|
|
400
|
+
return String(rounded);
|
|
401
|
+
};
|
|
402
|
+
const withNames = (value, sourceName, referenceName) => String(value).replaceAll("{sourceName}", sourceName).replaceAll("{referenceName}", referenceName);
|
|
403
|
+
const positiveImageDimension = (value) => Number.isSafeInteger(value) && value > 0;
|
|
404
|
+
function buildImageEditDraft({ prompt = "", annotations = [], translate, width, height, sourceName = "source.png", referenceName = "annotated-reference.png" }) {
|
|
405
|
+
if (typeof translate !== "function") throw new TypeError("translate must be a function");
|
|
406
|
+
const base = typeof prompt === "string" && prompt.trim() !== "" ? prompt.trim() : translate("imageEditDefault");
|
|
407
|
+
if (!Array.isArray(annotations)) throw new TypeError("annotations must be an array");
|
|
408
|
+
if (annotations.length === 0) return base;
|
|
409
|
+
const hasWidth = width !== void 0;
|
|
410
|
+
if (hasWidth !== (height !== void 0) || hasWidth && (!positiveImageDimension(width) || !positiveImageDimension(height))) throw new Error("width and height must be positive integers when provided");
|
|
411
|
+
const normalized = normalizeImageEditAnnotations(annotations, { requireNotes: true });
|
|
412
|
+
const source = typeof sourceName === "string" && sourceName.trim() !== "" ? sourceName.trim() : "source.png";
|
|
413
|
+
const reference = typeof referenceName === "string" && referenceName.trim() !== "" ? referenceName.trim() : "annotated-reference.png";
|
|
414
|
+
const guide = withNames(translate("imageEditReferenceGuide"), source, reference);
|
|
415
|
+
const location = translate("imageEditLocation");
|
|
416
|
+
const notes = normalized.map(({ number, x, y, note }) => {
|
|
417
|
+
const pixels = hasWidth ? ` (pixel x=${formatPixel(x * Math.max(0, width - 1))} of ${width}, y=${formatPixel(y * Math.max(0, height - 1))} of ${height})` : "";
|
|
418
|
+
return `${number}. ${location}: x=${formatPercent(x)} (normalized ${x}), y=${formatPercent(y)} (normalized ${y})${pixels}; ${note}`;
|
|
419
|
+
});
|
|
420
|
+
return [
|
|
421
|
+
base,
|
|
422
|
+
"",
|
|
423
|
+
guide,
|
|
424
|
+
"",
|
|
425
|
+
translate("imageRegionNotes"),
|
|
426
|
+
...notes
|
|
427
|
+
].join("\n");
|
|
474
428
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
const
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/client-image-previews.jsx
|
|
431
|
+
function openPreview(props, item, opener, sourceInDraft = false) {
|
|
432
|
+
const { service, preference, t, attachForEdit } = props;
|
|
433
|
+
const settings = preference.getSnapshot();
|
|
434
|
+
const referenceName = `annotated-${item.name}.png`;
|
|
435
|
+
service.open({
|
|
436
|
+
items: [{
|
|
437
|
+
...item,
|
|
438
|
+
actions: settings.imageEditing ? [{
|
|
439
|
+
id: "edit",
|
|
440
|
+
label: t("imageEdit"),
|
|
441
|
+
pendingLabel: t("imageEditPreparing"),
|
|
442
|
+
errorLabel: t("imageEditFailed"),
|
|
443
|
+
closeOnSuccess: true,
|
|
444
|
+
onInvoke: ({ annotations }) => attachForEdit(item.src, item.name, buildImageEditDraft({
|
|
445
|
+
annotations,
|
|
446
|
+
translate: t,
|
|
447
|
+
sourceName: item.name,
|
|
448
|
+
referenceName
|
|
449
|
+
}), annotations, referenceName, sourceInDraft)
|
|
450
|
+
}, ...settings.imageSketch && props.openSketchImage ? [{
|
|
451
|
+
id: "sketch",
|
|
452
|
+
label: t("imageToSketch"),
|
|
453
|
+
pendingLabel: t("imageEditPreparing"),
|
|
454
|
+
errorLabel: t("imageEditFailed"),
|
|
455
|
+
onInvoke: () => props.openSketchImage(item.src, item.name)
|
|
456
|
+
}] : []] : []
|
|
457
|
+
}],
|
|
458
|
+
opener,
|
|
459
|
+
source: sourceInDraft ? "codex-draft" : "codex-message",
|
|
460
|
+
annotations: settings.imageAnnotations
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function ComposerImagePreviews(props) {
|
|
464
|
+
const { attachments, service, nativeAttachments, watchNativeAttachments, nativeTranslate } = props;
|
|
465
|
+
const entry = (0, react.useSyncExternalStore)(watchNativeAttachments, nativeAttachments);
|
|
466
|
+
(0, react.useEffect)(() => {
|
|
467
|
+
const current = service.getSnapshot();
|
|
468
|
+
if (current?.source === "codex-draft" && !attachments.some((item) => item.id === current.items[0]?.id)) service.close();
|
|
469
|
+
}, [attachments, service]);
|
|
470
|
+
(0, react.useEffect)(() => () => {
|
|
471
|
+
if (service.getSnapshot()?.source === "codex-draft") service.close();
|
|
472
|
+
}, [service]);
|
|
473
|
+
if (!entry) return null;
|
|
474
|
+
const NativeAttachments = entry.component;
|
|
475
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
476
|
+
style: { display: "contents" },
|
|
477
|
+
onClickCapture: (event) => {
|
|
478
|
+
const button = event.target.closest("button"), image = button?.querySelector("img");
|
|
479
|
+
const item = image && attachments.find((item) => item.previewUrl === image.src);
|
|
480
|
+
if (!item || event.button !== 0 || event.ctrlKey || event.metaKey || event.altKey) return;
|
|
481
|
+
event.preventDefault();
|
|
482
|
+
event.stopPropagation();
|
|
483
|
+
openPreview(props, {
|
|
484
|
+
id: item.id,
|
|
485
|
+
src: item.previewUrl,
|
|
486
|
+
name: item.file.name,
|
|
487
|
+
width: item.width,
|
|
488
|
+
height: item.height,
|
|
489
|
+
bytes: item.file.size
|
|
490
|
+
}, button, true);
|
|
491
|
+
},
|
|
492
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NativeAttachments, {
|
|
493
|
+
...props,
|
|
494
|
+
t: nativeTranslate
|
|
495
|
+
})
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
function MessageImagePreview({ image, ...props }) {
|
|
499
|
+
const { loadImage, t } = props;
|
|
500
|
+
const [src, setSrc] = (0, react.useState)(image.preview?.url);
|
|
501
|
+
const [failed, setFailed] = (0, react.useState)(false);
|
|
502
|
+
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
480
503
|
(0, react.useEffect)(() => {
|
|
504
|
+
if (image.preview) {
|
|
505
|
+
setSrc(image.preview.url);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
481
508
|
let live = true;
|
|
482
|
-
setError(false);
|
|
483
509
|
setSrc(void 0);
|
|
484
|
-
|
|
510
|
+
setFailed(false);
|
|
511
|
+
Promise.resolve().then(() => loadImage(image.attachment)).then((value) => {
|
|
485
512
|
if (live) setSrc(value);
|
|
486
|
-
}
|
|
487
|
-
if (live)
|
|
513
|
+
}, () => {
|
|
514
|
+
if (live) setFailed(true);
|
|
488
515
|
});
|
|
489
516
|
return () => {
|
|
490
517
|
live = false;
|
|
491
518
|
};
|
|
492
519
|
}, [
|
|
493
|
-
|
|
520
|
+
image,
|
|
494
521
|
loadImage,
|
|
495
522
|
attempt
|
|
496
523
|
]);
|
|
497
|
-
const
|
|
498
|
-
const downloadName = imageDownloadName(attachment);
|
|
499
|
-
const downloadOriginal = async ({ signal, onProgress } = {}) => {
|
|
500
|
-
if (original === void 0) return;
|
|
501
|
-
triggerBlobDownload(await readOriginalImage(rpc, sessionId, original, {
|
|
502
|
-
signal,
|
|
503
|
-
onProgress
|
|
504
|
-
}), original.mediaType, original.name);
|
|
505
|
-
};
|
|
506
|
-
const openImage = () => {
|
|
507
|
-
if (src === void 0) return;
|
|
508
|
-
const request = {
|
|
509
|
-
items: [{
|
|
510
|
-
id: attachment.attachmentId ?? downloadName,
|
|
511
|
-
src,
|
|
512
|
-
name: label,
|
|
513
|
-
width: attachment.width,
|
|
514
|
-
height: attachment.height,
|
|
515
|
-
bytes: attachment.bytes,
|
|
516
|
-
download: original === void 0 ? void 0 : {
|
|
517
|
-
pendingLabel: t("imageDownloadPreparing"),
|
|
518
|
-
errorLabel: t("imageDownloadFailed"),
|
|
519
|
-
onInvoke: downloadOriginal
|
|
520
|
-
},
|
|
521
|
-
actions: !features.imageEditing ? [] : [{
|
|
522
|
-
id: "continue-editing",
|
|
523
|
-
label: t("imageEdit"),
|
|
524
|
-
pendingLabel: t("imageEditPreparing"),
|
|
525
|
-
errorLabel: t("imageEditFailed"),
|
|
526
|
-
closeOnSuccess: true,
|
|
527
|
-
onInvoke: ({ annotations = [] }) => {
|
|
528
|
-
const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
529
|
-
const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
|
|
530
|
-
const referenceName = `codex-edit-${imageKey}-annotations.png`;
|
|
531
|
-
return attachForEdit(src, sourceName, buildImageEditDraft({
|
|
532
|
-
annotations,
|
|
533
|
-
translate: t,
|
|
534
|
-
width: attachment.width,
|
|
535
|
-
height: attachment.height,
|
|
536
|
-
sourceName,
|
|
537
|
-
referenceName
|
|
538
|
-
}), annotations, referenceName);
|
|
539
|
-
}
|
|
540
|
-
}, ...features.imageSketch && openSketchImage ? [{
|
|
541
|
-
id: "sketch",
|
|
542
|
-
label: t("imageToSketch"),
|
|
543
|
-
pendingLabel: t("imageEditPreparing"),
|
|
544
|
-
errorLabel: t("imageEditFailed"),
|
|
545
|
-
onInvoke: () => openSketchImage(src, downloadName)
|
|
546
|
-
}] : []]
|
|
547
|
-
}],
|
|
548
|
-
opener: triggerRef.current,
|
|
549
|
-
source: "codex-generated",
|
|
550
|
-
annotations: features.imageViewer && features.imageAnnotations
|
|
551
|
-
};
|
|
552
|
-
if (features.imageViewer && getInternalImageViewer?.()?.open?.(request) === true) return;
|
|
553
|
-
if ((getImageViewer?.())?.open?.(request) === true) return;
|
|
554
|
-
if (features.imageViewer) getInternalImageViewer?.()?.open?.({
|
|
555
|
-
...request,
|
|
556
|
-
annotations: false
|
|
557
|
-
});
|
|
558
|
-
else window.open(src, "_blank", "noopener,noreferrer");
|
|
559
|
-
};
|
|
560
|
-
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
561
|
-
type: "button",
|
|
562
|
-
className: "codexGeneratedImageRetry",
|
|
563
|
-
onClick: () => setAttempt((value) => value + 1),
|
|
564
|
-
children: t("imageLoadFailed")
|
|
565
|
-
});
|
|
524
|
+
const item = image.attachment ?? image.preview;
|
|
566
525
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
567
|
-
ref: triggerRef,
|
|
568
526
|
type: "button",
|
|
569
|
-
className: "
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
onClick:
|
|
573
|
-
|
|
527
|
+
className: "codexImageThumb",
|
|
528
|
+
"aria-label": `${t("imagePreview")} ${item.name ?? ""}`,
|
|
529
|
+
disabled: !src && !failed,
|
|
530
|
+
onClick: (event) => {
|
|
531
|
+
if (failed) {
|
|
532
|
+
setAttempt((value) => value + 1);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
openPreview(props, {
|
|
536
|
+
id: item.attachmentId ?? src,
|
|
537
|
+
src,
|
|
538
|
+
name: item.name ?? "image.png",
|
|
539
|
+
width: item.width,
|
|
540
|
+
height: item.height
|
|
541
|
+
}, event.currentTarget);
|
|
542
|
+
},
|
|
543
|
+
children: src ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
574
544
|
src,
|
|
575
|
-
alt:
|
|
576
|
-
})
|
|
545
|
+
alt: item.name ?? "image"
|
|
546
|
+
}) : failed ? t("accountRetry") : "…"
|
|
577
547
|
});
|
|
578
548
|
}
|
|
579
|
-
function
|
|
580
|
-
const features = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
|
|
581
|
-
const settled = block?.kind === "tool-result";
|
|
582
|
-
const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
|
|
583
|
-
const failed = settled && block.isError === true;
|
|
584
|
-
const state = !settled ? "running" : failed ? "error" : "done";
|
|
585
|
-
const status = !settled ? t("imageGenerating") : failed ? t("imageFailed") : t("imageGenerated");
|
|
586
|
-
const error = failed ? block.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text : void 0;
|
|
587
|
-
const original = decodeImagePresentation(block?.meta)?.original;
|
|
588
|
-
const showOutput = presentation === "output";
|
|
589
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
590
|
-
className: "codexImageTool",
|
|
591
|
-
"data-state": state,
|
|
592
|
-
children: [
|
|
593
|
-
showOutput ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
594
|
-
className: "codexImageToolRow",
|
|
595
|
-
children: [
|
|
596
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(!settled ? ForwardRef$3 : failed ? ForwardRef$2 : ForwardRef$1, {
|
|
597
|
-
className: "codexImageToolIcon",
|
|
598
|
-
"aria-hidden": "true"
|
|
599
|
-
}),
|
|
600
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
601
|
-
className: "codexImageToolTitle",
|
|
602
|
-
children: t("imageGenerate")
|
|
603
|
-
}),
|
|
604
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
605
|
-
className: "codexImageBeta",
|
|
606
|
-
children: t("imageBeta")
|
|
607
|
-
}),
|
|
608
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
609
|
-
className: "codexImageToolState",
|
|
610
|
-
children: status
|
|
611
|
-
})
|
|
612
|
-
]
|
|
613
|
-
}),
|
|
614
|
-
!showOutput || image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
615
|
-
className: "codexImageToolGallery",
|
|
616
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexGeneratedImage, {
|
|
617
|
-
features,
|
|
618
|
-
attachment: image.attachment,
|
|
619
|
-
original,
|
|
620
|
-
rpc,
|
|
621
|
-
sessionId,
|
|
622
|
-
loadImage,
|
|
623
|
-
openSketchImage,
|
|
624
|
-
attachForEdit,
|
|
625
|
-
getImageViewer,
|
|
626
|
-
getInternalImageViewer,
|
|
627
|
-
t
|
|
628
|
-
})
|
|
629
|
-
}),
|
|
630
|
-
!showOutput && typeof block?.meta?.requestedModel === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
631
|
-
className: "codexImageDetails",
|
|
632
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("imageDetails") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
633
|
-
t("imageRequestedModel"),
|
|
634
|
-
": ",
|
|
635
|
-
block.meta.requestedModel.slice(0, 100),
|
|
636
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
|
|
637
|
-
t("imageReportedModel"),
|
|
638
|
-
": ",
|
|
639
|
-
typeof block.meta.reportedModel === "string" ? block.meta.reportedModel.slice(0, 100) : t("imageModelUnreported"),
|
|
640
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
|
|
641
|
-
t("imageRequestedSize"),
|
|
642
|
-
": ",
|
|
643
|
-
String(block.meta.requestedSize ?? "auto").slice(0, 40),
|
|
644
|
-
" · ",
|
|
645
|
-
t("imageActualSize"),
|
|
646
|
-
": ",
|
|
647
|
-
original?.width,
|
|
648
|
-
" × ",
|
|
649
|
-
original?.height
|
|
650
|
-
] })]
|
|
651
|
-
}) : null,
|
|
652
|
-
error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
653
|
-
className: "codexImageToolError",
|
|
654
|
-
children: error
|
|
655
|
-
})
|
|
656
|
-
]
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
function CodexImageOutput({ node, ...props }) {
|
|
549
|
+
function MessageImagePreviews({ images, align, ...props }) {
|
|
660
550
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
661
|
-
className: "
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
551
|
+
className: "codexMessageImages",
|
|
552
|
+
"data-align": align,
|
|
553
|
+
"data-single": images.length === 1,
|
|
554
|
+
children: images.map((image, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageImagePreview, {
|
|
555
|
+
image,
|
|
556
|
+
...props
|
|
557
|
+
}, image.attachment?.attachmentId ?? image.preview?.url ?? index))
|
|
667
558
|
});
|
|
668
559
|
}
|
|
560
|
+
const IMAGE_PREVIEWS_CSS = `
|
|
561
|
+
.codexMessageImages{display:flex;gap:10px;max-width:100%;padding:8px 0;overflow-x:auto}
|
|
562
|
+
.codexImageThumb{display:grid;place-items:center;width:64px;height:64px;padding:0;border:1px solid var(--dsw-alias-border-l2-darkmode-thin);border-radius:14px;background:var(--dsw-alias-interactive-bg-hover);color:inherit;overflow:hidden;cursor:zoom-in;flex:none}
|
|
563
|
+
.codexImageThumb img{width:100%;height:100%;object-fit:cover}
|
|
564
|
+
.codexImageThumb:focus-visible{outline:2px solid #4598ed;outline-offset:2px}
|
|
565
|
+
.codexMessageImages{flex-wrap:wrap}.codexMessageImages[data-align=end]{justify-content:flex-end}
|
|
566
|
+
.codexMessageImages[data-single=true] .codexImageThumb{width:240px;height:auto;max-width:100%}
|
|
567
|
+
.codexMessageImages[data-single=true] img{height:auto;max-height:320px;object-fit:contain}
|
|
568
|
+
`;
|
|
669
569
|
//#endregion
|
|
670
|
-
//#region src/
|
|
671
|
-
const
|
|
672
|
-
const
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
570
|
+
//#region src/image-conversation-node.js
|
|
571
|
+
const KIND = "codex-image-output";
|
|
572
|
+
const resultsOf = (event) => event?.type === "tool/result" && event.data.meta?.kind === "codex-subscription-image" ? (event.data.message?.content ?? []).filter((block) => block.type === "tool-result" && !block.isError && block.content?.some((part) => part.type === "image" && part.attachment)) : [];
|
|
573
|
+
const imageConversationNode = {
|
|
574
|
+
kind: KIND,
|
|
575
|
+
target: "chat",
|
|
576
|
+
match(event) {
|
|
577
|
+
if (event.type !== "turn/end" && resultsOf(event).length === 0) return null;
|
|
578
|
+
return {
|
|
579
|
+
id: String(event.data.turn),
|
|
580
|
+
role: "update"
|
|
581
|
+
};
|
|
582
|
+
},
|
|
583
|
+
start: () => void 0,
|
|
584
|
+
update: (context) => context.state,
|
|
585
|
+
buildViewNode(context) {
|
|
586
|
+
const end = context.matches.find((match) => match.event.type === "turn/end");
|
|
587
|
+
if (!end) return null;
|
|
588
|
+
const blocks = context.matches.flatMap(({ event }) => resultsOf(event).map((block) => ({
|
|
589
|
+
...block,
|
|
590
|
+
kind: "tool-result",
|
|
591
|
+
meta: event.data.meta
|
|
592
|
+
})));
|
|
593
|
+
if (!blocks.length) return null;
|
|
594
|
+
const answer = end.location.turn?.steps?.at(-1)?.data.get("assistant-step");
|
|
595
|
+
const lastResultSeq = Math.max(...context.matches.filter((match) => resultsOf(match.event).length).map((match) => match.event.seq));
|
|
596
|
+
const answerSeq = answer?.finalNode?.seq;
|
|
597
|
+
const anchorSeq = answerSeq > lastResultSeq ? answerSeq + .025 : end.event.seq - .025;
|
|
598
|
+
return {
|
|
599
|
+
key: context.key,
|
|
600
|
+
id: context.id,
|
|
601
|
+
kind: KIND,
|
|
602
|
+
target: "chat",
|
|
603
|
+
location: end.location,
|
|
604
|
+
anchorSeq,
|
|
605
|
+
visibility: "visible",
|
|
606
|
+
data: { blocks }
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
//#endregion
|
|
611
|
+
//#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ArrowPathIcon.js
|
|
612
|
+
function ArrowPathIcon({ title, titleId, ...props }, svgRef) {
|
|
613
|
+
return /*#__PURE__*/ react.createElement("svg", Object.assign({
|
|
614
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
615
|
+
fill: "none",
|
|
616
|
+
viewBox: "0 0 24 24",
|
|
617
|
+
strokeWidth: 1.5,
|
|
618
|
+
stroke: "currentColor",
|
|
619
|
+
"aria-hidden": "true",
|
|
620
|
+
"data-slot": "icon",
|
|
621
|
+
ref: svgRef,
|
|
622
|
+
"aria-labelledby": titleId
|
|
623
|
+
}, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
|
|
624
|
+
strokeLinecap: "round",
|
|
625
|
+
strokeLinejoin: "round",
|
|
626
|
+
d: "M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
const ForwardRef$3 = /*#__PURE__*/ react.forwardRef(ArrowPathIcon);
|
|
630
|
+
//#endregion
|
|
631
|
+
//#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ExclamationCircleIcon.js
|
|
632
|
+
function ExclamationCircleIcon({ title, titleId, ...props }, svgRef) {
|
|
633
|
+
return /*#__PURE__*/ react.createElement("svg", Object.assign({
|
|
634
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
635
|
+
fill: "none",
|
|
636
|
+
viewBox: "0 0 24 24",
|
|
637
|
+
strokeWidth: 1.5,
|
|
638
|
+
stroke: "currentColor",
|
|
639
|
+
"aria-hidden": "true",
|
|
640
|
+
"data-slot": "icon",
|
|
641
|
+
ref: svgRef,
|
|
642
|
+
"aria-labelledby": titleId
|
|
643
|
+
}, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
|
|
644
|
+
strokeLinecap: "round",
|
|
645
|
+
strokeLinejoin: "round",
|
|
646
|
+
d: "M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
|
|
647
|
+
}));
|
|
648
|
+
}
|
|
649
|
+
const ForwardRef$2 = /*#__PURE__*/ react.forwardRef(ExclamationCircleIcon);
|
|
650
|
+
//#endregion
|
|
651
|
+
//#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/PhotoIcon.js
|
|
652
|
+
function PhotoIcon({ title, titleId, ...props }, svgRef) {
|
|
653
|
+
return /*#__PURE__*/ react.createElement("svg", Object.assign({
|
|
654
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
655
|
+
fill: "none",
|
|
656
|
+
viewBox: "0 0 24 24",
|
|
657
|
+
strokeWidth: 1.5,
|
|
658
|
+
stroke: "currentColor",
|
|
659
|
+
"aria-hidden": "true",
|
|
660
|
+
"data-slot": "icon",
|
|
661
|
+
ref: svgRef,
|
|
662
|
+
"aria-labelledby": titleId
|
|
663
|
+
}, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
|
|
664
|
+
strokeLinecap: "round",
|
|
665
|
+
strokeLinejoin: "round",
|
|
666
|
+
d: "m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
|
|
667
|
+
}));
|
|
668
|
+
}
|
|
669
|
+
const ForwardRef$1 = /*#__PURE__*/ react.forwardRef(PhotoIcon);
|
|
670
|
+
const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
|
|
671
|
+
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
|
|
672
|
+
function decodeOriginalImageRef(value) {
|
|
673
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || typeof value.assetId !== "string" || !ORIGINAL_IMAGE_ID_PATTERN.test(value.assetId) || value.mediaType !== "image/png" || !positiveInteger(value.bytes) || value.bytes > 48 * 1024 * 1024 || !positiveInteger(value.width) || !positiveInteger(value.height) || typeof value.name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value.name) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(value.sha256)) return void 0;
|
|
676
674
|
return {
|
|
677
|
-
|
|
678
|
-
|
|
675
|
+
assetId: value.assetId,
|
|
676
|
+
mediaType: value.mediaType,
|
|
677
|
+
bytes: value.bytes,
|
|
678
|
+
width: value.width,
|
|
679
|
+
height: value.height,
|
|
680
|
+
name: value.name,
|
|
681
|
+
sha256: value.sha256
|
|
679
682
|
};
|
|
680
683
|
}
|
|
681
|
-
function
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
684
|
+
function decodeImagePresentation(value) {
|
|
685
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || value.kind !== "codex-subscription-image" || value.schemaVersion !== 1) return void 0;
|
|
686
|
+
const original = decodeOriginalImageRef(value.original);
|
|
687
|
+
return original === void 0 ? void 0 : { original };
|
|
688
|
+
}
|
|
689
|
+
function originalImageRefsEqual(left, right) {
|
|
690
|
+
const a = decodeOriginalImageRef(left);
|
|
691
|
+
const b = decodeOriginalImageRef(right);
|
|
692
|
+
return a !== void 0 && b !== void 0 && a.assetId === b.assetId && a.mediaType === b.mediaType && a.bytes === b.bytes && a.width === b.width && a.height === b.height && a.name === b.name && a.sha256 === b.sha256;
|
|
693
|
+
}
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/rpc-contract.js
|
|
696
|
+
const CHANNEL = "/codex-subscription";
|
|
697
|
+
const RPC_ENDPOINTS = Object.freeze([
|
|
698
|
+
"status",
|
|
699
|
+
"login/start",
|
|
700
|
+
"login/status",
|
|
701
|
+
"login/submit",
|
|
702
|
+
"login/cancel",
|
|
703
|
+
"logout",
|
|
704
|
+
"account/select",
|
|
705
|
+
"account/remove",
|
|
706
|
+
"usage",
|
|
707
|
+
"diagnostics",
|
|
708
|
+
"preferences/status",
|
|
709
|
+
"preferences/models",
|
|
710
|
+
"preferences/update",
|
|
711
|
+
"reset-credit/inspect",
|
|
712
|
+
"reset-credit/prepare",
|
|
713
|
+
"reset-credit/consume",
|
|
714
|
+
"image/original/chunk",
|
|
715
|
+
"sketch/connect",
|
|
716
|
+
"sketch/poll",
|
|
717
|
+
"sketch/claim",
|
|
718
|
+
"sketch/result",
|
|
719
|
+
"sketch/disconnect"
|
|
720
|
+
]);
|
|
721
|
+
function createSubscriptionRpcClient(transport) {
|
|
722
|
+
return Object.freeze({ call(channel, endpoint, payload, signal) {
|
|
723
|
+
if (channel !== "/codex-subscription" || !RPC_ENDPOINTS.includes(endpoint)) throw new Error("Invalid subscription RPC target");
|
|
724
|
+
return transport.call("/api", `codex-subscription/${endpoint}`, payload, signal);
|
|
725
|
+
} });
|
|
726
|
+
}
|
|
727
|
+
function unwrap(response) {
|
|
728
|
+
if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
|
|
729
|
+
return response.value;
|
|
730
|
+
}
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region src/original-image-download.js
|
|
733
|
+
function decodeBase64Chunk(value) {
|
|
734
|
+
if (typeof value !== "string" || value.length === 0 || value.length > Math.ceil(4194304 / 3) * 4 + 8) throw new Error("Invalid original image chunk");
|
|
735
|
+
let decoded;
|
|
736
|
+
try {
|
|
737
|
+
decoded = atob(value);
|
|
738
|
+
} catch {
|
|
739
|
+
throw new Error("Invalid original image chunk");
|
|
687
740
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
for (let i = 1; i < stroke.points.length; i += 3) {
|
|
721
|
-
const [a, b, c] = stroke.points.slice(i, i + 3);
|
|
722
|
-
context.bezierCurveTo(a.x * size, a.y * height, b.x * size, b.y * height, c.x * size, c.y * height);
|
|
723
|
-
}
|
|
724
|
-
if (stroke.fill) {
|
|
725
|
-
context.closePath();
|
|
726
|
-
context.fill();
|
|
727
|
-
} else context.stroke();
|
|
728
|
-
} else if (stroke.shape === "line") {
|
|
729
|
-
context.moveTo(first.x * size, first.y * height);
|
|
730
|
-
context.lineTo(last.x * size, last.y * height);
|
|
731
|
-
context.stroke();
|
|
732
|
-
} else if (stroke.shape === "rectangle") {
|
|
733
|
-
context.rect(first.x * size, first.y * height, (last.x - first.x) * size, (last.y - first.y) * height);
|
|
734
|
-
if (stroke.fill) context.fill();
|
|
735
|
-
else context.stroke();
|
|
736
|
-
} else if (stroke.shape === "circle") {
|
|
737
|
-
context.ellipse((first.x + last.x) * size / 2, (first.y + last.y) * height / 2, Math.abs(last.x - first.x) * size / 2, Math.abs(last.y - first.y) * height / 2, 0, 0, Math.PI * 2);
|
|
738
|
-
if (stroke.fill) context.fill();
|
|
739
|
-
else context.stroke();
|
|
740
|
-
} else if (stroke.shape === "polygon") {
|
|
741
|
-
context.moveTo(first.x * size, first.y * height);
|
|
742
|
-
for (const point of stroke.points.slice(1)) context.lineTo(point.x * size, point.y * height);
|
|
743
|
-
context.closePath();
|
|
744
|
-
if (stroke.fill) context.fill();
|
|
745
|
-
else context.stroke();
|
|
746
|
-
} else if (stroke.points.length === 1) {
|
|
747
|
-
context.arc(first.x * size, first.y * height, context.lineWidth / 2, 0, Math.PI * 2);
|
|
748
|
-
context.fill();
|
|
749
|
-
} else {
|
|
750
|
-
context.moveTo(first.x * size, first.y * height);
|
|
751
|
-
for (let i = 1; i < stroke.points.length - 1; i++) {
|
|
752
|
-
const point = stroke.points[i], next = stroke.points[i + 1];
|
|
753
|
-
if (context.quadraticCurveTo) context.quadraticCurveTo(point.x * size, point.y * height, (point.x + next.x) * size / 2, (point.y + next.y) * height / 2);
|
|
754
|
-
else context.lineTo(point.x * size, point.y * height);
|
|
755
|
-
}
|
|
756
|
-
context.lineTo(last.x * size, last.y * height);
|
|
757
|
-
context.stroke();
|
|
758
|
-
}
|
|
741
|
+
const bytes = new Uint8Array(decoded.length);
|
|
742
|
+
for (let index = 0; index < decoded.length; index += 1) bytes[index] = decoded.charCodeAt(index);
|
|
743
|
+
return bytes;
|
|
744
|
+
}
|
|
745
|
+
/** Keep only the destination and current chunk, while verifying every reply. */
|
|
746
|
+
async function readOriginalImage(rpc, sessionId, original, { signal, onProgress } = {}) {
|
|
747
|
+
signal?.throwIfAborted();
|
|
748
|
+
original = decodeOriginalImageRef(original);
|
|
749
|
+
if (original === void 0) throw new Error("Invalid original image reference");
|
|
750
|
+
const data = new Uint8Array(original.bytes);
|
|
751
|
+
let total = 0;
|
|
752
|
+
let done = false;
|
|
753
|
+
while (!done) {
|
|
754
|
+
signal?.throwIfAborted();
|
|
755
|
+
const response = await rpc.call(CHANNEL, "image/original/chunk", {
|
|
756
|
+
sessionId,
|
|
757
|
+
assetId: original.assetId,
|
|
758
|
+
offset: total
|
|
759
|
+
}, signal);
|
|
760
|
+
signal?.throwIfAborted();
|
|
761
|
+
if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
|
|
762
|
+
const chunk = response.value;
|
|
763
|
+
if (!originalImageRefsEqual(chunk?.ref, original) || chunk.offset !== total || typeof chunk.done !== "boolean") throw new Error("Original image metadata changed");
|
|
764
|
+
const bytes = decodeBase64Chunk(chunk.encoded);
|
|
765
|
+
if (bytes.byteLength === 0 || total + bytes.byteLength > original.bytes) throw new Error("Original image download is incomplete");
|
|
766
|
+
data.set(bytes, total);
|
|
767
|
+
total += bytes.byteLength;
|
|
768
|
+
done = chunk.done;
|
|
769
|
+
onProgress?.({
|
|
770
|
+
loaded: total,
|
|
771
|
+
total: original.bytes
|
|
772
|
+
});
|
|
759
773
|
}
|
|
760
|
-
|
|
761
|
-
|
|
774
|
+
if (total !== original.bytes) throw new Error("Original image download is incomplete");
|
|
775
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
776
|
+
signal?.throwIfAborted();
|
|
777
|
+
if ([...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("") !== original.sha256) throw new Error("Original image integrity check failed");
|
|
778
|
+
return data;
|
|
762
779
|
}
|
|
763
780
|
//#endregion
|
|
764
|
-
//#region src/
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
const ab = midpoint(a, b), bc = midpoint(b, c), cd = midpoint(c, d), abc = midpoint(ab, bc), bcd = midpoint(bc, cd), m = midpoint(abc, bcd);
|
|
788
|
-
split(a, ab, abc, m, depth + 1);
|
|
789
|
-
split(m, bcd, cd, d, depth + 1);
|
|
790
|
-
};
|
|
791
|
-
for (let i = 1; i < controls.length; i += 3) split(controls[i - 1], controls[i], controls[i + 1], controls[i + 2], 0);
|
|
792
|
-
const normalized = points.map((p) => ({
|
|
793
|
-
x: p.x / width,
|
|
794
|
-
y: p.y / height
|
|
795
|
-
}));
|
|
796
|
-
cached.set(stroke, {
|
|
797
|
-
width,
|
|
798
|
-
height,
|
|
799
|
-
points: normalized
|
|
800
|
-
});
|
|
801
|
-
return normalized;
|
|
781
|
+
//#region src/client-images.jsx
|
|
782
|
+
const imageDownloadName = (attachment) => {
|
|
783
|
+
const fallback = "codex-generated-image.png";
|
|
784
|
+
if (typeof attachment?.name !== "string") return fallback;
|
|
785
|
+
const cleaned = attachment.name.replace(/[<>:"/\\|?*\u0000-\u001f]/gu, "-").replace(/[. ]+$/u, "").trim();
|
|
786
|
+
if (cleaned === "") return fallback;
|
|
787
|
+
return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
|
|
788
|
+
};
|
|
789
|
+
function triggerBlobDownload(data, mediaType, filename) {
|
|
790
|
+
const url = URL.createObjectURL(new Blob([data], { type: mediaType }));
|
|
791
|
+
const anchor = document.createElement("a");
|
|
792
|
+
anchor.href = url;
|
|
793
|
+
anchor.download = filename;
|
|
794
|
+
anchor.rel = "noopener";
|
|
795
|
+
document.body.append(anchor);
|
|
796
|
+
try {
|
|
797
|
+
anchor.click();
|
|
798
|
+
} finally {
|
|
799
|
+
anchor.remove();
|
|
800
|
+
URL.revokeObjectURL(url);
|
|
801
|
+
}
|
|
802
802
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
...doc,
|
|
820
|
-
active: id
|
|
821
|
-
};
|
|
822
|
-
if (action === "add" || action === "duplicate") {
|
|
823
|
-
if (layers.length >= 8 || action === "duplicate" && strokeCount(doc) + layer.strokes.length > 2e3) return doc;
|
|
824
|
-
const next = action === "add" ? {
|
|
825
|
-
id: doc.nextId,
|
|
826
|
-
name: "",
|
|
827
|
-
visible: true,
|
|
828
|
-
strokes: []
|
|
829
|
-
} : {
|
|
830
|
-
...layer,
|
|
831
|
-
id: doc.nextId,
|
|
832
|
-
strokes: layer.strokes.slice()
|
|
833
|
-
};
|
|
834
|
-
layers.splice(index + 1, 0, next);
|
|
835
|
-
return {
|
|
836
|
-
...doc,
|
|
837
|
-
layers,
|
|
838
|
-
active: next.id,
|
|
839
|
-
nextId: doc.nextId + 1
|
|
803
|
+
function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, openSketchImage, attachForEdit, getImageViewer, getInternalImageViewer, t, features }) {
|
|
804
|
+
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
805
|
+
const [error, setError] = (0, react.useState)(false);
|
|
806
|
+
const [src, setSrc] = (0, react.useState)();
|
|
807
|
+
const triggerRef = (0, react.useRef)(null);
|
|
808
|
+
(0, react.useEffect)(() => {
|
|
809
|
+
let live = true;
|
|
810
|
+
setError(false);
|
|
811
|
+
setSrc(void 0);
|
|
812
|
+
Promise.resolve().then(() => loadImage(attachment)).then((value) => {
|
|
813
|
+
if (live) setSrc(value);
|
|
814
|
+
}).catch(() => {
|
|
815
|
+
if (live) setError(true);
|
|
816
|
+
});
|
|
817
|
+
return () => {
|
|
818
|
+
live = false;
|
|
840
819
|
};
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
820
|
+
}, [
|
|
821
|
+
attachment,
|
|
822
|
+
loadImage,
|
|
823
|
+
attempt
|
|
824
|
+
]);
|
|
825
|
+
const label = attachment.name ?? t("imageLabel");
|
|
826
|
+
const downloadName = imageDownloadName(attachment);
|
|
827
|
+
const downloadOriginal = async ({ signal, onProgress } = {}) => {
|
|
828
|
+
if (original === void 0) return;
|
|
829
|
+
triggerBlobDownload(await readOriginalImage(rpc, sessionId, original, {
|
|
830
|
+
signal,
|
|
831
|
+
onProgress
|
|
832
|
+
}), original.mediaType, original.name);
|
|
833
|
+
};
|
|
834
|
+
const openImage = () => {
|
|
835
|
+
if (src === void 0) return;
|
|
836
|
+
const request = {
|
|
837
|
+
items: [{
|
|
838
|
+
id: attachment.attachmentId ?? downloadName,
|
|
839
|
+
src,
|
|
840
|
+
name: label,
|
|
841
|
+
width: attachment.width,
|
|
842
|
+
height: attachment.height,
|
|
843
|
+
bytes: attachment.bytes,
|
|
844
|
+
download: original === void 0 ? void 0 : {
|
|
845
|
+
pendingLabel: t("imageDownloadPreparing"),
|
|
846
|
+
errorLabel: t("imageDownloadFailed"),
|
|
847
|
+
onInvoke: downloadOriginal
|
|
848
|
+
},
|
|
849
|
+
actions: !features.imageEditing ? [] : [{
|
|
850
|
+
id: "continue-editing",
|
|
851
|
+
label: t("imageEdit"),
|
|
852
|
+
pendingLabel: t("imageEditPreparing"),
|
|
853
|
+
errorLabel: t("imageEditFailed"),
|
|
854
|
+
closeOnSuccess: true,
|
|
855
|
+
onInvoke: ({ annotations = [] }) => {
|
|
856
|
+
const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
857
|
+
const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
|
|
858
|
+
const referenceName = `codex-edit-${imageKey}-annotations.png`;
|
|
859
|
+
return attachForEdit(src, sourceName, buildImageEditDraft({
|
|
860
|
+
annotations,
|
|
861
|
+
translate: t,
|
|
862
|
+
width: attachment.width,
|
|
863
|
+
height: attachment.height,
|
|
864
|
+
sourceName,
|
|
865
|
+
referenceName
|
|
866
|
+
}), annotations, referenceName);
|
|
867
|
+
}
|
|
868
|
+
}, ...features.imageSketch && openSketchImage ? [{
|
|
869
|
+
id: "sketch",
|
|
870
|
+
label: t("imageToSketch"),
|
|
871
|
+
pendingLabel: t("imageEditPreparing"),
|
|
872
|
+
errorLabel: t("imageEditFailed"),
|
|
873
|
+
onInvoke: () => openSketchImage(src, downloadName)
|
|
874
|
+
}] : []]
|
|
875
|
+
}],
|
|
876
|
+
opener: triggerRef.current,
|
|
877
|
+
source: "codex-generated",
|
|
878
|
+
annotations: features.imageViewer && features.imageAnnotations
|
|
849
879
|
};
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
visible: !layer.visible
|
|
858
|
-
};
|
|
859
|
-
else if (action === "rename") layers[index] = {
|
|
860
|
-
...layer,
|
|
861
|
-
name: String(value).trim().slice(0, 40)
|
|
862
|
-
};
|
|
863
|
-
else if (action === "clear") layers[index] = {
|
|
864
|
-
...layer,
|
|
865
|
-
strokes: [],
|
|
866
|
-
image: void 0
|
|
867
|
-
};
|
|
868
|
-
else return doc;
|
|
869
|
-
return {
|
|
870
|
-
...doc,
|
|
871
|
-
layers
|
|
880
|
+
if (features.imageViewer && getInternalImageViewer?.()?.open?.(request) === true) return;
|
|
881
|
+
if ((getImageViewer?.())?.open?.(request) === true) return;
|
|
882
|
+
if (features.imageViewer) getInternalImageViewer?.()?.open?.({
|
|
883
|
+
...request,
|
|
884
|
+
annotations: false
|
|
885
|
+
});
|
|
886
|
+
else window.open(src, "_blank", "noopener,noreferrer");
|
|
872
887
|
};
|
|
888
|
+
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
889
|
+
type: "button",
|
|
890
|
+
className: "codexGeneratedImageRetry",
|
|
891
|
+
onClick: () => setAttempt((value) => value + 1),
|
|
892
|
+
children: t("imageLoadFailed")
|
|
893
|
+
});
|
|
894
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
895
|
+
ref: triggerRef,
|
|
896
|
+
type: "button",
|
|
897
|
+
className: "codexGeneratedImageFrame",
|
|
898
|
+
title: t("imageOpen"),
|
|
899
|
+
"aria-label": t("imageOpenNamed").replace("{value}", String(label)),
|
|
900
|
+
onClick: openImage,
|
|
901
|
+
children: src === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageLoading") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
902
|
+
src,
|
|
903
|
+
alt: label
|
|
904
|
+
})
|
|
905
|
+
});
|
|
873
906
|
}
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
907
|
+
function CodexImageToolRow({ presentation = "tool", block, sessionId, rpc, loadImage, openSketchImage, attachForEdit, getImageViewer, getInternalImageViewer, t, preference }) {
|
|
908
|
+
const features = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
|
|
909
|
+
const settled = block?.kind === "tool-result";
|
|
910
|
+
const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
|
|
911
|
+
const failed = settled && block.isError === true;
|
|
912
|
+
const state = !settled ? "running" : failed ? "error" : "done";
|
|
913
|
+
const status = !settled ? t("imageGenerating") : failed ? t("imageFailed") : t("imageGenerated");
|
|
914
|
+
const error = failed ? block.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text : void 0;
|
|
915
|
+
const original = decodeImagePresentation(block?.meta)?.original;
|
|
916
|
+
const showOutput = presentation === "output";
|
|
917
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
918
|
+
className: "codexImageTool",
|
|
919
|
+
"data-state": state,
|
|
920
|
+
children: [
|
|
921
|
+
showOutput ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
922
|
+
className: "codexImageToolRow",
|
|
923
|
+
children: [
|
|
924
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(!settled ? ForwardRef$3 : failed ? ForwardRef$2 : ForwardRef$1, {
|
|
925
|
+
className: "codexImageToolIcon",
|
|
926
|
+
"aria-hidden": "true"
|
|
927
|
+
}),
|
|
928
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
929
|
+
className: "codexImageToolTitle",
|
|
930
|
+
children: t("imageGenerate")
|
|
931
|
+
}),
|
|
932
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
933
|
+
className: "codexImageBeta",
|
|
934
|
+
children: t("imageBeta")
|
|
935
|
+
}),
|
|
936
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
937
|
+
className: "codexImageToolState",
|
|
938
|
+
children: status
|
|
939
|
+
})
|
|
940
|
+
]
|
|
941
|
+
}),
|
|
942
|
+
!showOutput || image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
943
|
+
className: "codexImageToolGallery",
|
|
944
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexGeneratedImage, {
|
|
945
|
+
features,
|
|
946
|
+
attachment: image.attachment,
|
|
947
|
+
original,
|
|
948
|
+
rpc,
|
|
949
|
+
sessionId,
|
|
950
|
+
loadImage,
|
|
951
|
+
openSketchImage,
|
|
952
|
+
attachForEdit,
|
|
953
|
+
getImageViewer,
|
|
954
|
+
getInternalImageViewer,
|
|
955
|
+
t
|
|
956
|
+
})
|
|
957
|
+
}),
|
|
958
|
+
!showOutput && typeof block?.meta?.requestedModel === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
959
|
+
className: "codexImageDetails",
|
|
960
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("imageDetails") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
961
|
+
t("imageRequestedModel"),
|
|
962
|
+
": ",
|
|
963
|
+
block.meta.requestedModel.slice(0, 100),
|
|
964
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
|
|
965
|
+
t("imageReportedModel"),
|
|
966
|
+
": ",
|
|
967
|
+
typeof block.meta.reportedModel === "string" ? block.meta.reportedModel.slice(0, 100) : t("imageModelUnreported"),
|
|
968
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
|
|
969
|
+
t("imageRequestedSize"),
|
|
970
|
+
": ",
|
|
971
|
+
String(block.meta.requestedSize ?? "auto").slice(0, 40),
|
|
972
|
+
" · ",
|
|
973
|
+
t("imageActualSize"),
|
|
974
|
+
": ",
|
|
975
|
+
original?.width,
|
|
976
|
+
" × ",
|
|
977
|
+
original?.height
|
|
978
|
+
] })]
|
|
979
|
+
}) : null,
|
|
980
|
+
error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
981
|
+
className: "codexImageToolError",
|
|
982
|
+
children: error
|
|
983
|
+
})
|
|
984
|
+
]
|
|
985
|
+
});
|
|
926
986
|
}
|
|
927
|
-
|
|
928
|
-
"
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
const [width, height] = SKETCH_RATIOS[ratio];
|
|
937
|
-
const oldWidth = doc.width ?? 1024, oldHeight = doc.height ?? 1024;
|
|
938
|
-
if (width === oldWidth && height === oldHeight) return doc;
|
|
939
|
-
const scale = Math.min(width / oldWidth, height / oldHeight);
|
|
940
|
-
const dx = (width - oldWidth * scale) / 2, dy = (height - oldHeight * scale) / 2;
|
|
941
|
-
return {
|
|
942
|
-
...doc,
|
|
943
|
-
width,
|
|
944
|
-
height,
|
|
945
|
-
ratio,
|
|
946
|
-
layers: doc.layers.map((layer) => ({
|
|
947
|
-
...layer,
|
|
948
|
-
...layer.image ? { image: {
|
|
949
|
-
...layer.image,
|
|
950
|
-
x: (layer.image.x * oldWidth * scale + dx) / width,
|
|
951
|
-
y: (layer.image.y * oldHeight * scale + dy) / height,
|
|
952
|
-
width: layer.image.width * oldWidth * scale / width,
|
|
953
|
-
height: layer.image.height * oldHeight * scale / height
|
|
954
|
-
} } : {},
|
|
955
|
-
strokes: layer.strokes.map((stroke) => ({
|
|
956
|
-
...stroke,
|
|
957
|
-
width: stroke.width * scale,
|
|
958
|
-
points: stroke.points.map((p) => ({
|
|
959
|
-
x: (p.x * oldWidth * scale + dx) / width,
|
|
960
|
-
y: (p.y * oldHeight * scale + dy) / height
|
|
961
|
-
}))
|
|
962
|
-
}))
|
|
963
|
-
}))
|
|
964
|
-
};
|
|
987
|
+
function CodexImageOutput({ node, ...props }) {
|
|
988
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
989
|
+
className: "codexImageOutput",
|
|
990
|
+
children: node.data.blocks.map((block) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexImageToolRow, {
|
|
991
|
+
block,
|
|
992
|
+
...props,
|
|
993
|
+
presentation: "output"
|
|
994
|
+
}, block.toolCallId))
|
|
995
|
+
});
|
|
965
996
|
}
|
|
966
997
|
//#endregion
|
|
967
998
|
//#region src/sketch-layer-renderer.js
|
|
@@ -1515,7 +1546,8 @@ window.__ModuleLoader__.load({
|
|
|
1515
1546
|
shapes: "line: exactly two endpoints; rectangle/circle (ellipse alias accepted): exactly two opposite bounding-box corners (circle draws an ellipse within that box); polygon: three or more vertices, closed automatically; pen: ordered path points. bezier: start point, then groups of control1/control2/end; use 4 points for one cubic curve, max 64 segments. Prefer bezier for smooth designed curves instead of many pen samples. fill:true closes and fills the curve. fill:true fills rectangle/circle/polygon. Layers and strokes paint in list order, later ones on top. All commands needed for drawing are described here; no source-code search is required.",
|
|
1516
1547
|
commands: {
|
|
1517
1548
|
stroke: "{op:\"stroke\",layer:1,shape:\"pen|line|arrow|text|rectangle|circle|polygon|bezier\",color:\"#rrggbb\",width:2,opacity:1,fill:false,points:[{x:0.1,y:0.1},...]}",
|
|
1518
|
-
layer: "{op:\"layer\",action:\"add
|
|
1549
|
+
layer: "Add: {op:\"layer\",action:\"add\",value:\"name\"}; optional id is the NEW unique integer ID, otherwise allocated automatically. after is the existing insertion anchor, defaults to active layer. Other actions: {op:\"layer\",action:\"select|rename|visible|duplicate|up|down|delete|clear\",id:1,value:\"name\"}; id targets an existing layer.",
|
|
1550
|
+
curve: "Prefer {op:\"stroke\",shape:\"bezier\",start:{x:0,y:0},segments:[{control1:{x:0.2,y:0},control2:{x:0.8,y:1},end:{x:1,y:1}}],color:\"#123456\"}. Each segment has exactly two controls and an endpoint; no point counting required. Legacy points arrays still accepted. Do not provide both forms.",
|
|
1519
1551
|
object: "{op:\"object\",layer:1,id:\"title\",action:\"update|duplicate|delete\",patch:{color:\"#0088ff\",text:\"Title\"},transform:{dx:0.05,dy:0,scaleX:1,scaleY:1}}. All patch and transform fields optional. Inspect returns object IDs and bounds. Prefer targeted edits over redrawing layers.",
|
|
1520
1552
|
resize: "{op:\"resize\",ratio:\"1:1|4:3|3:4|16:9|9:16\"}"
|
|
1521
1553
|
},
|
|
@@ -1548,6 +1580,23 @@ window.__ModuleLoader__.load({
|
|
|
1548
1580
|
"delete",
|
|
1549
1581
|
"clear"
|
|
1550
1582
|
].includes(command.action)) throw Error("Unknown layer action");
|
|
1583
|
+
if (command.action === "add") {
|
|
1584
|
+
const id = command.id ?? doc.nextId, after = command.after ?? doc.active;
|
|
1585
|
+
if (!Number.isSafeInteger(id) || id < 1 || id === Number.MAX_SAFE_INTEGER || doc.layers.some((l) => l.id === id)) throw Error("New layer id must be a unique positive integer; omit id to allocate automatically");
|
|
1586
|
+
const next = changeSketchLayer(doc, "add", after);
|
|
1587
|
+
if (next === doc) throw Error("Cannot add layer: check the existing after layer and the 8-layer limit");
|
|
1588
|
+
doc = {
|
|
1589
|
+
...next,
|
|
1590
|
+
active: id,
|
|
1591
|
+
nextId: Math.max(next.nextId, id + 1),
|
|
1592
|
+
layers: next.layers.map((l) => l.id === next.active ? {
|
|
1593
|
+
...l,
|
|
1594
|
+
id,
|
|
1595
|
+
name: String(command.value ?? "").trim().slice(0, 40)
|
|
1596
|
+
} : l)
|
|
1597
|
+
};
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1551
1600
|
const next = changeSketchLayer(doc, command.action, command.id ?? doc.active, command.value);
|
|
1552
1601
|
if (next === doc) throw Error("Layer action unavailable; inspect the document first");
|
|
1553
1602
|
doc = next;
|
|
@@ -1607,7 +1656,16 @@ window.__ModuleLoader__.load({
|
|
|
1607
1656
|
}
|
|
1608
1657
|
if (command.op !== "stroke") throw Error("Unknown command");
|
|
1609
1658
|
const shape = command.shape === "ellipse" ? "circle" : command.shape ?? "pen";
|
|
1610
|
-
const {
|
|
1659
|
+
const { color, width = shape === "text" ? 24 : 2, opacity = 1, fill = false } = command;
|
|
1660
|
+
let points = command.points;
|
|
1661
|
+
if (command.start !== void 0 || command.segments !== void 0) {
|
|
1662
|
+
if (shape !== "bezier" || points !== void 0 || !command.start || !Array.isArray(command.segments) || !command.segments.length || command.segments.length > 64) throw Error("Bezier requires start and 1–64 segments, without points");
|
|
1663
|
+
points = [command.start, ...command.segments.flatMap((s) => [
|
|
1664
|
+
s?.control1,
|
|
1665
|
+
s?.control2,
|
|
1666
|
+
s?.end
|
|
1667
|
+
])];
|
|
1668
|
+
}
|
|
1611
1669
|
if (![
|
|
1612
1670
|
"pen",
|
|
1613
1671
|
"line",
|
|
@@ -1675,6 +1733,7 @@ window.__ModuleLoader__.load({
|
|
|
1675
1733
|
if (!Number.isInteger(offset) || offset < 0) throw Error("offset must be a non-negative integer");
|
|
1676
1734
|
return {
|
|
1677
1735
|
...current,
|
|
1736
|
+
protocolVersion: 2,
|
|
1678
1737
|
objects: objects.slice(offset, offset + 50),
|
|
1679
1738
|
objectCount: objects.length,
|
|
1680
1739
|
...offset + 50 < objects.length ? { nextOffset: offset + 50 } : {},
|
|
@@ -1703,7 +1762,15 @@ window.__ModuleLoader__.load({
|
|
|
1703
1762
|
if (request.revision !== current.revision || adapter.busy()) throw Error("Sketch changed or is being edited; inspect again");
|
|
1704
1763
|
let changedObjects;
|
|
1705
1764
|
if (request.action === "apply") {
|
|
1706
|
-
const before = adapter.document()
|
|
1765
|
+
const before = adapter.document();
|
|
1766
|
+
let next;
|
|
1767
|
+
try {
|
|
1768
|
+
next = applySketchCommands(before, request.commands);
|
|
1769
|
+
} catch (cause) {
|
|
1770
|
+
const error = new Error(`${cause.message} Correct the batch and retry with the same runId and revision; nothing was applied.`, { cause });
|
|
1771
|
+
error.code = "SKETCH_INVALID_BATCH";
|
|
1772
|
+
throw error;
|
|
1773
|
+
}
|
|
1707
1774
|
adapter.commit(next);
|
|
1708
1775
|
const previous = new Map(before.layers.flatMap((l) => l.strokes.map((s) => [`${l.id}:${s.id}`, s])));
|
|
1709
1776
|
changedObjects = next.layers.flatMap((l) => l.strokes.filter((s) => previous.get(`${l.id}:${s.id}`) !== s).map((s) => ({
|
|
@@ -2263,6 +2330,7 @@ window.__ModuleLoader__.load({
|
|
|
2263
2330
|
update("finished");
|
|
2264
2331
|
return completed.value;
|
|
2265
2332
|
} catch (error) {
|
|
2333
|
+
if (version === generation && error.code === "SKETCH_INVALID_BATCH") throw error;
|
|
2266
2334
|
if (version === generation) {
|
|
2267
2335
|
update("failed");
|
|
2268
2336
|
throw new Error(`${error.message} Call inspect to obtain the current runId and revision before retrying.`, { cause: error });
|
|
@@ -2281,7 +2349,7 @@ window.__ModuleLoader__.load({
|
|
|
2281
2349
|
//#endregion
|
|
2282
2350
|
//#region src/sketch-agent-client.js
|
|
2283
2351
|
function connectSketchAgent(rpc, sessionId, execute, report, pollDelay = () => 350) {
|
|
2284
|
-
let stopped = false, token, timer, attempts = 0;
|
|
2352
|
+
let stopped = false, token, timer, attempts = 0, failures = 0;
|
|
2285
2353
|
const call = (endpoint, payload) => rpc.call(CHANNEL, `sketch/${endpoint}`, {
|
|
2286
2354
|
sessionId,
|
|
2287
2355
|
token,
|
|
@@ -2311,13 +2379,24 @@ window.__ModuleLoader__.load({
|
|
|
2311
2379
|
});
|
|
2312
2380
|
}
|
|
2313
2381
|
} catch (error) {
|
|
2314
|
-
if (!stopped)
|
|
2382
|
+
if (!stopped) {
|
|
2383
|
+
if (++failures > 5) {
|
|
2384
|
+
report(`${error.message}; reconnect failed. Reopen this session to retry.`);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
report(`${error.message}; reconnecting. Inspect recentRequests before retrying a write.`);
|
|
2388
|
+
if (token) call("disconnect").catch(() => {});
|
|
2389
|
+
token = void 0;
|
|
2390
|
+
timer = setTimeout(connect, Math.min(1e4, 1e3 * 2 ** (failures - 1)));
|
|
2391
|
+
}
|
|
2315
2392
|
return;
|
|
2316
2393
|
}
|
|
2394
|
+
failures = 0;
|
|
2317
2395
|
if (!stopped) timer = setTimeout(poll, pollDelay());
|
|
2318
2396
|
};
|
|
2319
2397
|
const connect = () => void call("connect").then((value) => {
|
|
2320
2398
|
token = value.token;
|
|
2399
|
+
attempts = 0;
|
|
2321
2400
|
if (stopped) call("disconnect").catch(() => {});
|
|
2322
2401
|
else poll();
|
|
2323
2402
|
}, (error) => {
|
|
@@ -2344,12 +2423,13 @@ window.__ModuleLoader__.load({
|
|
|
2344
2423
|
"#34c759",
|
|
2345
2424
|
"#0088ff"
|
|
2346
2425
|
];
|
|
2347
|
-
function SketchStudio({ open, agentEnabled, agentPreview, onOpen, onClose, attachSketch, enabled, t, incoming, sessionId, rpc }) {
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
const
|
|
2351
|
-
const
|
|
2352
|
-
const
|
|
2426
|
+
function SketchStudio({ open, agentEnabled, agentPreview, onOpen, onClose, attachSketch, enabled, t, incoming, sessionId, rpc, sessionState }) {
|
|
2427
|
+
const localSession = (0, react.useRef)(null);
|
|
2428
|
+
localSession.current ??= sessionState ?? createSketchSessionState();
|
|
2429
|
+
const { doc, undo, redo, images, saved, dirty, documentId, documentRevision, agentAdapter, agentSession, agentRun } = localSession.current;
|
|
2430
|
+
const dialog = (0, react.useRef)(null), canvas = (0, react.useRef)(null), cache = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
2431
|
+
const active = (0, react.useRef)(null), frame = (0, react.useRef)(null), updateUi = (0, react.useRef)(false);
|
|
2432
|
+
const [agentState, setAgentState] = (0, react.useState)(agentRun.current?.state ?? "idle");
|
|
2353
2433
|
const agentLocked = agentState === "drawing";
|
|
2354
2434
|
const [noticeHidden, setNoticeHidden] = (0, react.useState)(false);
|
|
2355
2435
|
const [stability, setStability] = (0, react.useState)(0), [flow, setFlow] = (0, react.useState)(100), [picturesOpen, setPicturesOpen] = (0, react.useState)(false);
|
|
@@ -2437,10 +2517,10 @@ window.__ModuleLoader__.load({
|
|
|
2437
2517
|
(0, react.useEffect)(() => () => {
|
|
2438
2518
|
cancelAnimationFrame(frame.current);
|
|
2439
2519
|
cache.current.clear();
|
|
2440
|
-
agentRun.current?.dispose();
|
|
2441
2520
|
}, []);
|
|
2442
2521
|
const hasContent = () => doc.current.layers.some((layer) => layer.image || layer.strokes.length);
|
|
2443
2522
|
const save = async (name) => {
|
|
2523
|
+
const savingDocument = documentId.current, savingRevision = documentRevision.current;
|
|
2444
2524
|
const row = {
|
|
2445
2525
|
id: saved.current?.id ?? crypto.randomUUID(),
|
|
2446
2526
|
name: name?.trim() || saved.current?.name || `${t("sketchTitle")} ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
@@ -2448,11 +2528,13 @@ window.__ModuleLoader__.load({
|
|
|
2448
2528
|
doc: structuredClone(doc.current)
|
|
2449
2529
|
};
|
|
2450
2530
|
await sketchDrafts("save", row);
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2531
|
+
if (documentId.current === savingDocument) {
|
|
2532
|
+
saved.current = {
|
|
2533
|
+
id: row.id,
|
|
2534
|
+
name: row.name
|
|
2535
|
+
};
|
|
2536
|
+
if (documentRevision.current === savingRevision) dirty.current = false;
|
|
2537
|
+
}
|
|
2456
2538
|
};
|
|
2457
2539
|
const saveChanges = async () => {
|
|
2458
2540
|
if (dirty.current && (hasContent() || saved.current)) await save();
|
|
@@ -2730,6 +2812,10 @@ window.__ModuleLoader__.load({
|
|
|
2730
2812
|
schedule();
|
|
2731
2813
|
};
|
|
2732
2814
|
Object.assign(agentAdapter.current, {
|
|
2815
|
+
changed: (state) => {
|
|
2816
|
+
setAgentState(state);
|
|
2817
|
+
setNoticeHidden(false);
|
|
2818
|
+
},
|
|
2733
2819
|
available: () => enabled && agentEnabled,
|
|
2734
2820
|
previewEnabled: () => agentPreview,
|
|
2735
2821
|
open: () => onOpen(),
|
|
@@ -2775,17 +2861,14 @@ window.__ModuleLoader__.load({
|
|
|
2775
2861
|
agentRun.current ??= createSketchAgentRun({
|
|
2776
2862
|
execute: (request) => agentSession.current(request),
|
|
2777
2863
|
open: () => agentAdapter.current.open(),
|
|
2778
|
-
changed: (state) =>
|
|
2779
|
-
setAgentState(state);
|
|
2780
|
-
setNoticeHidden(false);
|
|
2781
|
-
},
|
|
2864
|
+
changed: (state) => agentAdapter.current.changed?.(state),
|
|
2782
2865
|
busy: () => agentAdapter.current.busy(),
|
|
2783
2866
|
previewEnabled: () => agentAdapter.current.previewEnabled()
|
|
2784
2867
|
});
|
|
2785
2868
|
(0, react.useEffect)(() => {
|
|
2786
2869
|
if (!enabled || !agentEnabled) return;
|
|
2787
2870
|
const api = Object.freeze({
|
|
2788
|
-
version:
|
|
2871
|
+
version: 2,
|
|
2789
2872
|
sessionId,
|
|
2790
2873
|
execute: (request) => agentRun.current.execute(request),
|
|
2791
2874
|
export: async (format) => {
|
|
@@ -2812,9 +2895,12 @@ window.__ModuleLoader__.load({
|
|
|
2812
2895
|
sessionId
|
|
2813
2896
|
]);
|
|
2814
2897
|
(0, react.useEffect)(() => {
|
|
2815
|
-
if (!enabled || !agentEnabled
|
|
2898
|
+
if (!enabled || !agentEnabled) {
|
|
2899
|
+
if (agentRun.current.locked) agentRun.current.stop();
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
if (!rpc || !sessionId) return;
|
|
2816
2903
|
let live = true;
|
|
2817
|
-
agentRun.current.resume();
|
|
2818
2904
|
const disconnect = connectSketchAgent(rpc, sessionId, (request) => {
|
|
2819
2905
|
if (!live) throw Error("Sketch session disconnected");
|
|
2820
2906
|
return agentRun.current.execute(request);
|
|
@@ -2824,7 +2910,6 @@ window.__ModuleLoader__.load({
|
|
|
2824
2910
|
}, () => 350);
|
|
2825
2911
|
return () => {
|
|
2826
2912
|
live = false;
|
|
2827
|
-
agentRun.current.stop();
|
|
2828
2913
|
disconnect();
|
|
2829
2914
|
};
|
|
2830
2915
|
}, [
|
|
@@ -3707,7 +3792,7 @@ window.__ModuleLoader__.load({
|
|
|
3707
3792
|
`;
|
|
3708
3793
|
//#endregion
|
|
3709
3794
|
//#region src/sketch-workspace.jsx
|
|
3710
|
-
function SketchWorkspace({ preference, attachSketch, registerOpen, t, sessionId, rpc }) {
|
|
3795
|
+
function SketchWorkspace({ preference, attachSketch, registerOpen, t, sessionId, rpc, sessionState }) {
|
|
3711
3796
|
const settings = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
|
|
3712
3797
|
const [open, setOpen] = (0, react.useState)(false);
|
|
3713
3798
|
const [incoming, setIncoming] = (0, react.useState)(null);
|
|
@@ -3720,6 +3805,7 @@ window.__ModuleLoader__.load({
|
|
|
3720
3805
|
}
|
|
3721
3806
|
}), [registerOpen]);
|
|
3722
3807
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SketchStudio, {
|
|
3808
|
+
sessionState,
|
|
3723
3809
|
agentPreview: settings.imageSketchAgentPreview,
|
|
3724
3810
|
agentEnabled: settings.imageSketchAgent,
|
|
3725
3811
|
onOpen: () => {
|
|
@@ -6461,7 +6547,7 @@ window.__ModuleLoader__.load({
|
|
|
6461
6547
|
}
|
|
6462
6548
|
//#endregion
|
|
6463
6549
|
//#region src/version.js
|
|
6464
|
-
const PACKAGE_VERSION = "2.1.0-beta.
|
|
6550
|
+
const PACKAGE_VERSION = "2.1.0-beta.5";
|
|
6465
6551
|
//#endregion
|
|
6466
6552
|
//#region src/client-recovery.js
|
|
6467
6553
|
async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
|
|
@@ -8778,6 +8864,8 @@ window.__ModuleLoader__.load({
|
|
|
8778
8864
|
const conversation = ctx.get("conversation");
|
|
8779
8865
|
const uiConversation = ctx.get("uiConversation");
|
|
8780
8866
|
const sketchOpeners = /* @__PURE__ */ new Map();
|
|
8867
|
+
const sketchSessions = createSketchSessionRegistry();
|
|
8868
|
+
ctx.effect(() => () => sketchSessions.dispose(), "codex-subscription: sketch sessions");
|
|
8781
8869
|
ctx.inject(["inputTriggers"], (triggerContext) => triggerContext.effect(() => triggerContext.get("inputTriggers").registerSource(createSketchTrigger({
|
|
8782
8870
|
enabled: () => {
|
|
8783
8871
|
const value = preference.getSnapshot();
|
|
@@ -8886,6 +8974,7 @@ window.__ModuleLoader__.load({
|
|
|
8886
8974
|
t,
|
|
8887
8975
|
sessionId,
|
|
8888
8976
|
rpc,
|
|
8977
|
+
sessionState: sketchSessions.get(sessionId),
|
|
8889
8978
|
registerOpen: (callback) => {
|
|
8890
8979
|
sketchOpeners.set(sessionId, callback);
|
|
8891
8980
|
return () => {
|