dsh-codex-subscription 2.1.0-beta.3 → 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 +1064 -924
- package/lib/index.js +50 -16
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -29,939 +29,970 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
let react = require("react");
|
|
30
30
|
react = __toESM(react, 1);
|
|
31
31
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
32
|
-
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
33
32
|
let react_dom = require("react-dom");
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
33
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
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);
|
|
127
|
+
}
|
|
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);
|
|
206
159
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}, event.currentTarget);
|
|
214
|
-
},
|
|
215
|
-
children: src ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
216
|
-
src,
|
|
217
|
-
alt: item.name ?? "image"
|
|
218
|
-
}) : failed ? t("accountRetry") : "…"
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
function MessageImagePreviews({ images, align, ...props }) {
|
|
222
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
223
|
-
className: "codexMessageImages",
|
|
224
|
-
"data-align": align,
|
|
225
|
-
"data-single": images.length === 1,
|
|
226
|
-
children: images.map((image, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageImagePreview, {
|
|
227
|
-
image,
|
|
228
|
-
...props
|
|
229
|
-
}, image.attachment?.attachmentId ?? image.preview?.url ?? index))
|
|
230
|
-
});
|
|
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
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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"
|
|
319
|
-
}));
|
|
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
|
+
};
|
|
320
237
|
}
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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);
|
|
242
|
+
};
|
|
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)
|
|
339
279
|
}));
|
|
280
|
+
points = points.map((p) => ({
|
|
281
|
+
x: p.x * width,
|
|
282
|
+
y: p.y * height
|
|
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);
|
|
340
290
|
}
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
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
|
+
});
|
|
416
394
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return data;
|
|
395
|
+
const formatPercent = (value) => {
|
|
396
|
+
return `${Number((value * 100).toFixed(2))}%`;
|
|
397
|
+
};
|
|
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");
|
|
451
428
|
}
|
|
452
429
|
//#endregion
|
|
453
|
-
//#region src/client-
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
+
});
|
|
474
462
|
}
|
|
475
|
-
function
|
|
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);
|
|
476
502
|
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
477
|
-
const [error, setError] = (0, react.useState)(false);
|
|
478
|
-
const [src, setSrc] = (0, react.useState)();
|
|
479
|
-
const triggerRef = (0, react.useRef)(null);
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
]
|
|
549
|
+
function MessageImagePreviews({ images, align, ...props }) {
|
|
550
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
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))
|
|
657
558
|
});
|
|
658
559
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
+
`;
|
|
569
|
+
//#endregion
|
|
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
|
+
}));
|
|
668
628
|
}
|
|
629
|
+
const ForwardRef$3 = /*#__PURE__*/ react.forwardRef(ArrowPathIcon);
|
|
669
630
|
//#endregion
|
|
670
|
-
//#region
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
|
|
687
|
-
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
-
}
|
|
759
|
-
}
|
|
760
|
-
context.globalCompositeOperation = "source-over";
|
|
761
|
-
context.globalAlpha = 1;
|
|
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;
|
|
762
730
|
}
|
|
763
731
|
//#endregion
|
|
764
|
-
//#region src/
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
})), points = [controls[0]];
|
|
777
|
-
const distance = (p, a, b) => {
|
|
778
|
-
const dx = b.x - a.x, dy = b.y - a.y, d = dx * dx + dy * dy;
|
|
779
|
-
const t = d ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / d)) : 0;
|
|
780
|
-
return Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy);
|
|
781
|
-
};
|
|
782
|
-
const split = (a, b, c, d, depth) => {
|
|
783
|
-
if (depth === 10 || Math.max(distance(b, a, d), distance(c, a, d)) <= .5) {
|
|
784
|
-
points.push(d);
|
|
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;
|
|
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");
|
|
740
|
+
}
|
|
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;
|
|
802
744
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
if (
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
|
840
|
-
};
|
|
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
|
+
});
|
|
841
773
|
}
|
|
842
|
-
if (
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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;
|
|
779
|
+
}
|
|
780
|
+
//#endregion
|
|
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);
|
|
850
801
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
802
|
+
}
|
|
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;
|
|
819
|
+
};
|
|
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);
|
|
867
833
|
};
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
|
879
|
+
};
|
|
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
|
|
@@ -1351,8 +1382,39 @@ window.__ModuleLoader__.load({
|
|
|
1351
1382
|
};
|
|
1352
1383
|
}
|
|
1353
1384
|
function SketchViewControls({ navigation, t }) {
|
|
1354
|
-
const [open, setOpen] = (0, react.useState)(false),
|
|
1355
|
-
|
|
1385
|
+
const [open, setOpen] = (0, react.useState)(false), [placement, setPlacement] = (0, react.useState)(null);
|
|
1386
|
+
const host = (0, react.useRef)(null), trigger = (0, react.useRef)(null);
|
|
1387
|
+
const close = () => {
|
|
1388
|
+
setOpen(false);
|
|
1389
|
+
trigger.current?.focus({ preventScroll: true });
|
|
1390
|
+
};
|
|
1391
|
+
useSketchDismiss(open, setOpen, host, [".codexSketchViewControls", ".codexSketchKeyPanel"]);
|
|
1392
|
+
(0, react.useLayoutEffect)(() => {
|
|
1393
|
+
if (!open) return;
|
|
1394
|
+
const dialog = host.current.closest("dialog");
|
|
1395
|
+
const place = () => {
|
|
1396
|
+
const box = dialog.getBoundingClientRect(), anchor = trigger.current.getBoundingClientRect();
|
|
1397
|
+
const width = Math.min(360, box.width - 24);
|
|
1398
|
+
setPlacement({
|
|
1399
|
+
dialog,
|
|
1400
|
+
style: {
|
|
1401
|
+
width,
|
|
1402
|
+
left: Math.max(12, Math.min(anchor.left - box.left, box.width - width - 12)),
|
|
1403
|
+
bottom: box.bottom - anchor.top + 8,
|
|
1404
|
+
maxHeight: Math.max(80, anchor.top - box.top - 24)
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
};
|
|
1408
|
+
place();
|
|
1409
|
+
const observer = new ResizeObserver(place);
|
|
1410
|
+
observer.observe(dialog);
|
|
1411
|
+
observer.observe(host.current);
|
|
1412
|
+
window.addEventListener("resize", place);
|
|
1413
|
+
return () => {
|
|
1414
|
+
observer.disconnect();
|
|
1415
|
+
window.removeEventListener("resize", place);
|
|
1416
|
+
};
|
|
1417
|
+
}, [open]);
|
|
1356
1418
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1357
1419
|
ref: host,
|
|
1358
1420
|
className: "codexSketchViewControls",
|
|
@@ -1376,40 +1438,56 @@ window.__ModuleLoader__.load({
|
|
|
1376
1438
|
children: "+"
|
|
1377
1439
|
}),
|
|
1378
1440
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1441
|
+
ref: trigger,
|
|
1379
1442
|
type: "button",
|
|
1443
|
+
"aria-expanded": open,
|
|
1380
1444
|
onClick: () => setOpen(!open),
|
|
1381
1445
|
children: t("sketchKeys")
|
|
1382
1446
|
}),
|
|
1383
|
-
open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1447
|
+
open && placement ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1384
1448
|
className: "codexSketchKeyPanel",
|
|
1449
|
+
"aria-label": t("sketchKeys"),
|
|
1450
|
+
style: placement.style,
|
|
1385
1451
|
onKeyDown: (e) => {
|
|
1386
1452
|
if (e.key === "Escape") {
|
|
1387
1453
|
e.preventDefault();
|
|
1388
1454
|
e.stopPropagation();
|
|
1389
|
-
|
|
1455
|
+
close();
|
|
1390
1456
|
}
|
|
1391
1457
|
},
|
|
1392
1458
|
children: [
|
|
1393
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
1394
|
-
type: "
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1459
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("sketchKeys") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1460
|
+
type: "button",
|
|
1461
|
+
"aria-label": t("sketchFileClose"),
|
|
1462
|
+
onClick: close,
|
|
1463
|
+
children: "×"
|
|
1464
|
+
})] }),
|
|
1465
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1466
|
+
className: "codexSketchKeysEnabled",
|
|
1467
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sketchKeysEnabled") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1468
|
+
type: "checkbox",
|
|
1469
|
+
checked: navigation.shortcuts,
|
|
1470
|
+
onChange: navigation.toggle
|
|
1471
|
+
})]
|
|
1472
|
+
}),
|
|
1398
1473
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("sketchNavigationHint") }),
|
|
1399
|
-
|
|
1400
|
-
"
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
e
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1474
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1475
|
+
className: "codexSketchKeyGrid",
|
|
1476
|
+
children: Object.entries(navigation.keys).map(([action, key]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t(`sketchKey_${action}`), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1477
|
+
"aria-label": t(`sketchKey_${action}`),
|
|
1478
|
+
value: key === " " ? "Space" : key,
|
|
1479
|
+
readOnly: true,
|
|
1480
|
+
onKeyDown: (e) => {
|
|
1481
|
+
if (e.key === "Tab" || e.key === "Escape") return;
|
|
1482
|
+
e.preventDefault();
|
|
1483
|
+
e.stopPropagation();
|
|
1484
|
+
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) navigation.setKey(action, e.key);
|
|
1485
|
+
}
|
|
1486
|
+
})] }, action))
|
|
1487
|
+
}),
|
|
1410
1488
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("sketchKeyHint") })
|
|
1411
1489
|
]
|
|
1412
|
-
}) : null
|
|
1490
|
+
}), placement.dialog) : null
|
|
1413
1491
|
]
|
|
1414
1492
|
});
|
|
1415
1493
|
}
|
|
@@ -1465,10 +1543,11 @@ window.__ModuleLoader__.load({
|
|
|
1465
1543
|
}
|
|
1466
1544
|
const SKETCH_COMMAND_HELP = {
|
|
1467
1545
|
coordinates: "Assign short meaningful stroke id values for later edits. Text uses two opposite box corners and text content; width is font size in pixels, automatic fitting within the box. Arrow uses two endpoints. Normalized x/y in [0,1]; width is canvas pixels. Read documentId and revision before editing.",
|
|
1468
|
-
shapes: "line: exactly two endpoints; rectangle/circle: 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.",
|
|
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.",
|
|
1469
1547
|
commands: {
|
|
1470
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},...]}",
|
|
1471
|
-
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.",
|
|
1472
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.",
|
|
1473
1552
|
resize: "{op:\"resize\",ratio:\"1:1|4:3|3:4|16:9|9:16\"}"
|
|
1474
1553
|
},
|
|
@@ -1501,6 +1580,23 @@ window.__ModuleLoader__.load({
|
|
|
1501
1580
|
"delete",
|
|
1502
1581
|
"clear"
|
|
1503
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
|
+
}
|
|
1504
1600
|
const next = changeSketchLayer(doc, command.action, command.id ?? doc.active, command.value);
|
|
1505
1601
|
if (next === doc) throw Error("Layer action unavailable; inspect the document first");
|
|
1506
1602
|
doc = next;
|
|
@@ -1559,7 +1655,17 @@ window.__ModuleLoader__.load({
|
|
|
1559
1655
|
continue;
|
|
1560
1656
|
}
|
|
1561
1657
|
if (command.op !== "stroke") throw Error("Unknown command");
|
|
1562
|
-
const
|
|
1658
|
+
const shape = command.shape === "ellipse" ? "circle" : command.shape ?? "pen";
|
|
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
|
+
}
|
|
1563
1669
|
if (![
|
|
1564
1670
|
"pen",
|
|
1565
1671
|
"line",
|
|
@@ -1578,7 +1684,7 @@ window.__ModuleLoader__.load({
|
|
|
1578
1684
|
"rectangle",
|
|
1579
1685
|
"circle"
|
|
1580
1686
|
].includes(shape) && points.length !== 2 || shape === "polygon" && points.length < 3) throw Error("Invalid shape points");
|
|
1581
|
-
if (shape === "bezier" && (points.length < 4 || points.length > 193 || (points.length - 1) % 3 !== 0)) throw Error(
|
|
1687
|
+
if (shape === "bezier" && (points.length < 4 || points.length > 193 || (points.length - 1) % 3 !== 0)) throw Error(`commands[${commands.indexOf(command)}]: Bezier has ${points.length} points; expected 4, 7, 10, ... 193 (start + control1/control2/end per segment). No commands in this batch were applied.`);
|
|
1582
1688
|
if (fill && ![
|
|
1583
1689
|
"rectangle",
|
|
1584
1690
|
"circle",
|
|
@@ -1627,6 +1733,7 @@ window.__ModuleLoader__.load({
|
|
|
1627
1733
|
if (!Number.isInteger(offset) || offset < 0) throw Error("offset must be a non-negative integer");
|
|
1628
1734
|
return {
|
|
1629
1735
|
...current,
|
|
1736
|
+
protocolVersion: 2,
|
|
1630
1737
|
objects: objects.slice(offset, offset + 50),
|
|
1631
1738
|
objectCount: objects.length,
|
|
1632
1739
|
...offset + 50 < objects.length ? { nextOffset: offset + 50 } : {},
|
|
@@ -1655,7 +1762,15 @@ window.__ModuleLoader__.load({
|
|
|
1655
1762
|
if (request.revision !== current.revision || adapter.busy()) throw Error("Sketch changed or is being edited; inspect again");
|
|
1656
1763
|
let changedObjects;
|
|
1657
1764
|
if (request.action === "apply") {
|
|
1658
|
-
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
|
+
}
|
|
1659
1774
|
adapter.commit(next);
|
|
1660
1775
|
const previous = new Map(before.layers.flatMap((l) => l.strokes.map((s) => [`${l.id}:${s.id}`, s])));
|
|
1661
1776
|
changedObjects = next.layers.flatMap((l) => l.strokes.filter((s) => previous.get(`${l.id}:${s.id}`) !== s).map((s) => ({
|
|
@@ -2215,7 +2330,11 @@ window.__ModuleLoader__.load({
|
|
|
2215
2330
|
update("finished");
|
|
2216
2331
|
return completed.value;
|
|
2217
2332
|
} catch (error) {
|
|
2218
|
-
if (version === generation
|
|
2333
|
+
if (version === generation && error.code === "SKETCH_INVALID_BATCH") throw error;
|
|
2334
|
+
if (version === generation) {
|
|
2335
|
+
update("failed");
|
|
2336
|
+
throw new Error(`${error.message} Call inspect to obtain the current runId and revision before retrying.`, { cause: error });
|
|
2337
|
+
}
|
|
2219
2338
|
throw error;
|
|
2220
2339
|
} finally {
|
|
2221
2340
|
pending = false;
|
|
@@ -2230,7 +2349,7 @@ window.__ModuleLoader__.load({
|
|
|
2230
2349
|
//#endregion
|
|
2231
2350
|
//#region src/sketch-agent-client.js
|
|
2232
2351
|
function connectSketchAgent(rpc, sessionId, execute, report, pollDelay = () => 350) {
|
|
2233
|
-
let stopped = false, token, timer, attempts = 0;
|
|
2352
|
+
let stopped = false, token, timer, attempts = 0, failures = 0;
|
|
2234
2353
|
const call = (endpoint, payload) => rpc.call(CHANNEL, `sketch/${endpoint}`, {
|
|
2235
2354
|
sessionId,
|
|
2236
2355
|
token,
|
|
@@ -2260,13 +2379,24 @@ window.__ModuleLoader__.load({
|
|
|
2260
2379
|
});
|
|
2261
2380
|
}
|
|
2262
2381
|
} catch (error) {
|
|
2263
|
-
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
|
+
}
|
|
2264
2392
|
return;
|
|
2265
2393
|
}
|
|
2394
|
+
failures = 0;
|
|
2266
2395
|
if (!stopped) timer = setTimeout(poll, pollDelay());
|
|
2267
2396
|
};
|
|
2268
2397
|
const connect = () => void call("connect").then((value) => {
|
|
2269
2398
|
token = value.token;
|
|
2399
|
+
attempts = 0;
|
|
2270
2400
|
if (stopped) call("disconnect").catch(() => {});
|
|
2271
2401
|
else poll();
|
|
2272
2402
|
}, (error) => {
|
|
@@ -2293,12 +2423,13 @@ window.__ModuleLoader__.load({
|
|
|
2293
2423
|
"#34c759",
|
|
2294
2424
|
"#0088ff"
|
|
2295
2425
|
];
|
|
2296
|
-
function SketchStudio({ open, agentEnabled, agentPreview, onOpen, onClose, attachSketch, enabled, t, incoming, sessionId, rpc }) {
|
|
2297
|
-
const
|
|
2298
|
-
|
|
2299
|
-
const
|
|
2300
|
-
const
|
|
2301
|
-
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");
|
|
2302
2433
|
const agentLocked = agentState === "drawing";
|
|
2303
2434
|
const [noticeHidden, setNoticeHidden] = (0, react.useState)(false);
|
|
2304
2435
|
const [stability, setStability] = (0, react.useState)(0), [flow, setFlow] = (0, react.useState)(100), [picturesOpen, setPicturesOpen] = (0, react.useState)(false);
|
|
@@ -2386,10 +2517,10 @@ window.__ModuleLoader__.load({
|
|
|
2386
2517
|
(0, react.useEffect)(() => () => {
|
|
2387
2518
|
cancelAnimationFrame(frame.current);
|
|
2388
2519
|
cache.current.clear();
|
|
2389
|
-
agentRun.current?.dispose();
|
|
2390
2520
|
}, []);
|
|
2391
2521
|
const hasContent = () => doc.current.layers.some((layer) => layer.image || layer.strokes.length);
|
|
2392
2522
|
const save = async (name) => {
|
|
2523
|
+
const savingDocument = documentId.current, savingRevision = documentRevision.current;
|
|
2393
2524
|
const row = {
|
|
2394
2525
|
id: saved.current?.id ?? crypto.randomUUID(),
|
|
2395
2526
|
name: name?.trim() || saved.current?.name || `${t("sketchTitle")} ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
@@ -2397,11 +2528,13 @@ window.__ModuleLoader__.load({
|
|
|
2397
2528
|
doc: structuredClone(doc.current)
|
|
2398
2529
|
};
|
|
2399
2530
|
await sketchDrafts("save", row);
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
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
|
+
}
|
|
2405
2538
|
};
|
|
2406
2539
|
const saveChanges = async () => {
|
|
2407
2540
|
if (dirty.current && (hasContent() || saved.current)) await save();
|
|
@@ -2679,6 +2812,10 @@ window.__ModuleLoader__.load({
|
|
|
2679
2812
|
schedule();
|
|
2680
2813
|
};
|
|
2681
2814
|
Object.assign(agentAdapter.current, {
|
|
2815
|
+
changed: (state) => {
|
|
2816
|
+
setAgentState(state);
|
|
2817
|
+
setNoticeHidden(false);
|
|
2818
|
+
},
|
|
2682
2819
|
available: () => enabled && agentEnabled,
|
|
2683
2820
|
previewEnabled: () => agentPreview,
|
|
2684
2821
|
open: () => onOpen(),
|
|
@@ -2724,17 +2861,14 @@ window.__ModuleLoader__.load({
|
|
|
2724
2861
|
agentRun.current ??= createSketchAgentRun({
|
|
2725
2862
|
execute: (request) => agentSession.current(request),
|
|
2726
2863
|
open: () => agentAdapter.current.open(),
|
|
2727
|
-
changed: (state) =>
|
|
2728
|
-
setAgentState(state);
|
|
2729
|
-
setNoticeHidden(false);
|
|
2730
|
-
},
|
|
2864
|
+
changed: (state) => agentAdapter.current.changed?.(state),
|
|
2731
2865
|
busy: () => agentAdapter.current.busy(),
|
|
2732
2866
|
previewEnabled: () => agentAdapter.current.previewEnabled()
|
|
2733
2867
|
});
|
|
2734
2868
|
(0, react.useEffect)(() => {
|
|
2735
2869
|
if (!enabled || !agentEnabled) return;
|
|
2736
2870
|
const api = Object.freeze({
|
|
2737
|
-
version:
|
|
2871
|
+
version: 2,
|
|
2738
2872
|
sessionId,
|
|
2739
2873
|
execute: (request) => agentRun.current.execute(request),
|
|
2740
2874
|
export: async (format) => {
|
|
@@ -2761,9 +2895,12 @@ window.__ModuleLoader__.load({
|
|
|
2761
2895
|
sessionId
|
|
2762
2896
|
]);
|
|
2763
2897
|
(0, react.useEffect)(() => {
|
|
2764
|
-
if (!enabled || !agentEnabled
|
|
2898
|
+
if (!enabled || !agentEnabled) {
|
|
2899
|
+
if (agentRun.current.locked) agentRun.current.stop();
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
if (!rpc || !sessionId) return;
|
|
2765
2903
|
let live = true;
|
|
2766
|
-
agentRun.current.resume();
|
|
2767
2904
|
const disconnect = connectSketchAgent(rpc, sessionId, (request) => {
|
|
2768
2905
|
if (!live) throw Error("Sketch session disconnected");
|
|
2769
2906
|
return agentRun.current.execute(request);
|
|
@@ -2773,7 +2910,6 @@ window.__ModuleLoader__.load({
|
|
|
2773
2910
|
}, () => 350);
|
|
2774
2911
|
return () => {
|
|
2775
2912
|
live = false;
|
|
2776
|
-
agentRun.current.stop();
|
|
2777
2913
|
disconnect();
|
|
2778
2914
|
};
|
|
2779
2915
|
}, [
|
|
@@ -3650,13 +3786,13 @@ window.__ModuleLoader__.load({
|
|
|
3650
3786
|
.codexLayerBody{overflow:hidden}.codexLayerStudio canvas{cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Ccircle cx='12' cy='12' r='7' fill='none' stroke='white' stroke-width='3'/%3E%3Ccircle cx='12' cy='12' r='7' fill='none' stroke='%23333' stroke-width='1'/%3E%3C/svg%3E") 12 12,crosshair}
|
|
3651
3787
|
.codexSketchDialog .codexSketchSwatch{width:32px;height:32px;padding:0;border-radius:50%;corner-shape:round}.codexSketchSwatch::before{width:22px;height:22px;flex-shrink:0;border-radius:50%;corner-shape:round;clip-path:circle(50%)}.codexSketchSwatch[aria-pressed=true]::after{inset:1px;border:1.5px solid var(--sketch-fg);border-radius:50%;corner-shape:round}.codexSketchCustom{width:30px;height:30px;margin:1px 4px;corner-shape:round;clip-path:circle(50%)}.codexSketchCustom span{width:22px;height:22px;border-width:2px;corner-shape:round}
|
|
3652
3788
|
.codexSketchPictures{position:absolute;left:0;top:0;z-index:3;background:var(--sketch-bg);border:1px solid var(--sketch-line);border-radius:12px;padding:10px;width:160px;max-height:100%;overflow:auto}.codexSketchPictures header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.codexSketchPictures>div{display:flex;gap:5px;align-items:center;padding:4px;border:1px solid transparent;border-radius:8px}.codexSketchPictures>div[data-active=true]{border-color:#0a84ff}.codexSketchPictures img{width:90px;height:65px;object-fit:contain}.codexSketchPictures small{color:var(--sketch-muted)}
|
|
3653
|
-
.codexSketchViewControls{display:flex;gap:2px;align-items:center}.codexSketchViewControls button{min-height:32px;padding:0 7px;border-radius:8px}.codexSketchKeyPanel{position:absolute;
|
|
3789
|
+
.codexSketchViewControls{display:flex;gap:2px;align-items:center}.codexSketchViewControls button{min-height:32px;padding:0 7px;border-radius:8px}.codexSketchKeyPanel{position:absolute;overflow:auto;overscroll-behavior:contain;padding:12px;border:1px solid var(--sketch-line);border-radius:14px;background:var(--sketch-bg);z-index:5;box-shadow:0 8px 30px #0003;scrollbar-width:thin}.codexSketchKeyPanel header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.codexSketchKeyPanel header button{width:28px;height:28px;border-radius:8px;font-size:20px}.codexSketchKeyPanel label{display:flex;align-items:center;justify-content:space-between;min-height:32px;gap:8px}.codexSketchKeysEnabled{padding-bottom:8px;border-bottom:1px solid var(--sketch-line)}.codexSketchKeysEnabled input{margin:0;accent-color:#0a84ff}.codexSketchKeyGrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px 16px;margin:10px 0}.codexSketchKeyPanel input:not([type=checkbox]){width:52px;min-width:0;border:1px solid var(--sketch-line);border-radius:6px;color:inherit;background:color-mix(in srgb,var(--sketch-fg) 4%,transparent);padding:4px;text-align:center;font:inherit}.codexSketchKeyPanel p,.codexSketchKeyPanel small{display:block;color:var(--sketch-muted);font-size:11px;line-height:1.5;margin:0}.codexSketchKeyPanel small{border-top:1px solid var(--sketch-line);padding-top:8px}@media(max-width:380px){.codexSketchKeyGrid{grid-template-columns:1fr}}.codexSketchControls{position:static}
|
|
3654
3790
|
|
|
3655
3791
|
.codexSketchCursor{position:fixed;left:0;top:0;z-index:100;pointer-events:none;border:1px solid #222;border-radius:50%;corner-shape:round;box-shadow:0 0 0 1px #fff;box-sizing:border-box}.codexSketchCursor[hidden]{display:none}
|
|
3656
3792
|
`;
|
|
3657
3793
|
//#endregion
|
|
3658
3794
|
//#region src/sketch-workspace.jsx
|
|
3659
|
-
function SketchWorkspace({ preference, attachSketch, registerOpen, t, sessionId, rpc }) {
|
|
3795
|
+
function SketchWorkspace({ preference, attachSketch, registerOpen, t, sessionId, rpc, sessionState }) {
|
|
3660
3796
|
const settings = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
|
|
3661
3797
|
const [open, setOpen] = (0, react.useState)(false);
|
|
3662
3798
|
const [incoming, setIncoming] = (0, react.useState)(null);
|
|
@@ -3669,6 +3805,7 @@ window.__ModuleLoader__.load({
|
|
|
3669
3805
|
}
|
|
3670
3806
|
}), [registerOpen]);
|
|
3671
3807
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SketchStudio, {
|
|
3808
|
+
sessionState,
|
|
3672
3809
|
agentPreview: settings.imageSketchAgentPreview,
|
|
3673
3810
|
agentEnabled: settings.imageSketchAgent,
|
|
3674
3811
|
onOpen: () => {
|
|
@@ -6410,7 +6547,7 @@ window.__ModuleLoader__.load({
|
|
|
6410
6547
|
}
|
|
6411
6548
|
//#endregion
|
|
6412
6549
|
//#region src/version.js
|
|
6413
|
-
const PACKAGE_VERSION = "2.1.0-beta.
|
|
6550
|
+
const PACKAGE_VERSION = "2.1.0-beta.5";
|
|
6414
6551
|
//#endregion
|
|
6415
6552
|
//#region src/client-recovery.js
|
|
6416
6553
|
async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
|
|
@@ -8727,6 +8864,8 @@ window.__ModuleLoader__.load({
|
|
|
8727
8864
|
const conversation = ctx.get("conversation");
|
|
8728
8865
|
const uiConversation = ctx.get("uiConversation");
|
|
8729
8866
|
const sketchOpeners = /* @__PURE__ */ new Map();
|
|
8867
|
+
const sketchSessions = createSketchSessionRegistry();
|
|
8868
|
+
ctx.effect(() => () => sketchSessions.dispose(), "codex-subscription: sketch sessions");
|
|
8730
8869
|
ctx.inject(["inputTriggers"], (triggerContext) => triggerContext.effect(() => triggerContext.get("inputTriggers").registerSource(createSketchTrigger({
|
|
8731
8870
|
enabled: () => {
|
|
8732
8871
|
const value = preference.getSnapshot();
|
|
@@ -8835,6 +8974,7 @@ window.__ModuleLoader__.load({
|
|
|
8835
8974
|
t,
|
|
8836
8975
|
sessionId,
|
|
8837
8976
|
rpc,
|
|
8977
|
+
sessionState: sketchSessions.get(sessionId),
|
|
8838
8978
|
registerOpen: (callback) => {
|
|
8839
8979
|
sketchOpeners.set(sessionId, callback);
|
|
8840
8980
|
return () => {
|