@huanlin/dsh-plugin-aigc-canvas 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -111
- package/lib/canvas-registry.d.ts +142 -0
- package/lib/client.js +49 -1674
- package/lib/client.js.map +1 -1
- package/lib/config.d.ts +77 -0
- package/lib/context-types.d.ts +120 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +832 -5917
- package/lib/invariant.d.ts +15 -0
- package/lib/invariant.js +2 -2
- package/lib/media-edit.d.ts +38 -0
- package/lib/provider-http.d.ts +81 -0
- package/lib/provider-store.d.ts +50 -0
- package/lib/tools.d.ts +47 -0
- package/lib/trust-fence.d.ts +25 -0
- package/lib/wire.d.ts +41 -0
- package/package.json +24 -29
- package/LICENSE +0 -664
package/lib/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({
|
|
2
|
-
id: "@
|
|
2
|
+
id: "@dsh-external/dsh-aigc-canvas",
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
@@ -19,53 +19,6 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
this.code = code;
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
-
/** All QualityHint values as a readonly array (for select dropdowns). */
|
|
23
|
-
const RUNTIME_QUALITY_HINTS = [
|
|
24
|
-
"fast",
|
|
25
|
-
"balanced",
|
|
26
|
-
"quality"
|
|
27
|
-
];
|
|
28
|
-
/** All Capability values (for select dropdowns). */
|
|
29
|
-
const RUNTIME_CAPABILITIES = [
|
|
30
|
-
"t2i",
|
|
31
|
-
"i2i",
|
|
32
|
-
"t2v",
|
|
33
|
-
"i2v",
|
|
34
|
-
"fl2v",
|
|
35
|
-
"ref2v",
|
|
36
|
-
"tts",
|
|
37
|
-
"music",
|
|
38
|
-
"transcribe",
|
|
39
|
-
"edit",
|
|
40
|
-
"chat"
|
|
41
|
-
];
|
|
42
|
-
/** All ResponseKind values (for select dropdowns). */
|
|
43
|
-
const RUNTIME_RESPONSE_KINDS = [
|
|
44
|
-
"b64_json_array",
|
|
45
|
-
"b64_json_field",
|
|
46
|
-
"binary",
|
|
47
|
-
"url_field",
|
|
48
|
-
"json_text"
|
|
49
|
-
];
|
|
50
|
-
/** All HTTP methods (for select dropdowns). */
|
|
51
|
-
const RUNTIME_HTTP_METHODS = [
|
|
52
|
-
"GET",
|
|
53
|
-
"POST",
|
|
54
|
-
"PUT",
|
|
55
|
-
"PATCH"
|
|
56
|
-
];
|
|
57
|
-
/** All parameter types (for select dropdowns). */
|
|
58
|
-
const RUNTIME_PARAM_TYPES = [
|
|
59
|
-
"string",
|
|
60
|
-
"number",
|
|
61
|
-
"integer",
|
|
62
|
-
"boolean",
|
|
63
|
-
"array",
|
|
64
|
-
"object",
|
|
65
|
-
"image_ref",
|
|
66
|
-
"video_ref",
|
|
67
|
-
"audio_ref"
|
|
68
|
-
];
|
|
69
22
|
async function call(method, payload, signal) {
|
|
70
23
|
let response;
|
|
71
24
|
try {
|
|
@@ -111,38 +64,6 @@ window.__ModuleLoader__.load({
|
|
|
111
64
|
...opts
|
|
112
65
|
}, signal);
|
|
113
66
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Inject a user-role notice into the agent's next-step context
|
|
116
|
-
* (non-waking). Used by the canvas UI's right-click menu and quick
|
|
117
|
-
* action toolbar to ask the agent to regenerate / edit / run a
|
|
118
|
-
* workflow. Per docs/product/04-ux-reliability.md §1 + §7.
|
|
119
|
-
*
|
|
120
|
-
* The optional `summary` is the short label shown in the agent's
|
|
121
|
-
* inbox (truncated to 120 chars by the host).
|
|
122
|
-
*/
|
|
123
|
-
function notifyAgent(sessionId, message, summary, signal) {
|
|
124
|
-
const payload = {
|
|
125
|
-
sessionId,
|
|
126
|
-
message
|
|
127
|
-
};
|
|
128
|
-
if (summary !== void 0) payload.summary = summary;
|
|
129
|
-
return call("canvas.notify", payload, signal);
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Update one element's lifecycle status (draft/ready/rejected/archived)
|
|
133
|
-
* and optional winner flag. Per docs/product/01-agent-autonomy.md §5
|
|
134
|
-
* + docs/product/04-ux-reliability.md §1 (right-click → mark as
|
|
135
|
-
* winner / rejected / archive).
|
|
136
|
-
*/
|
|
137
|
-
function setElementStatus(sessionId, uuid, status, winner, signal) {
|
|
138
|
-
const payload = {
|
|
139
|
-
sessionId,
|
|
140
|
-
uuid,
|
|
141
|
-
status
|
|
142
|
-
};
|
|
143
|
-
if (winner !== void 0) payload.winner = winner;
|
|
144
|
-
return call("canvas.set_status", payload, signal);
|
|
145
|
-
}
|
|
146
67
|
/** Fetch the full runtime config (providers + global settings). */
|
|
147
68
|
function fetchConfig(signal) {
|
|
148
69
|
return call("config.get", {}, signal);
|
|
@@ -172,26 +93,6 @@ window.__ModuleLoader__.load({
|
|
|
172
93
|
function canvasWsUrl(sessionId) {
|
|
173
94
|
return `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}/aigc-canvas/ws/canvas?sessionId=${encodeURIComponent(sessionId)}`;
|
|
174
95
|
}
|
|
175
|
-
/** Fetch the request log for one session (newest last). */
|
|
176
|
-
function fetchRequestLog(sessionId, signal) {
|
|
177
|
-
return call("logs.list", { sessionId }, signal);
|
|
178
|
-
}
|
|
179
|
-
/** Clear the request log for one session. */
|
|
180
|
-
function clearRequestLog(sessionId, signal) {
|
|
181
|
-
return call("logs.clear", { sessionId }, signal);
|
|
182
|
-
}
|
|
183
|
-
/** Fetch the per-session cost summary (for the canvas header). */
|
|
184
|
-
function fetchSessionCost(sessionId, signal) {
|
|
185
|
-
return call("cost.get", { sessionId }, signal);
|
|
186
|
-
}
|
|
187
|
-
/** Promote one canvas element (by uuid) to the asset library. */
|
|
188
|
-
function promoteAsset(sessionId, uuid, opts, signal) {
|
|
189
|
-
return call("library.promote", {
|
|
190
|
-
sessionId,
|
|
191
|
-
uuid,
|
|
192
|
-
...opts
|
|
193
|
-
}, signal);
|
|
194
|
-
}
|
|
195
96
|
//#endregion
|
|
196
97
|
//#region src/client/store.ts
|
|
197
98
|
/**
|
|
@@ -333,11 +234,11 @@ window.__ModuleLoader__.load({
|
|
|
333
234
|
};
|
|
334
235
|
//#endregion
|
|
335
236
|
//#region \0dsh-css:D:\Projects\deepseek-harness\dsh-aigc-canvas\src\client\canvas.module.css.mjs
|
|
336
|
-
const css$1 = "/* dsh-aigc-canvas client styles.\r\n *\r\n * Renders inside a better-sidebar tab panel. Follows the DSH core design\r\n * language: `--dsw-alias-*` semantic tokens, `--dsw-font-*` typography,\r\n * 36px header bar + 24x24 r6 icon buttons (mirror of SubagentView), 8px\r\n * radius cards on `--dsw-alias-bg-layer-2`, no aggressive borders —\r\n * hover/active fills carry the interaction state.\r\n */\r\n\r\n.i0op5y_canvas {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-primary);\r\n background: transparent;\r\n}\r\n\r\n/* ── Header bar (mirror SubagentView: 36px h, 0 8px 0 12px) ────────────── */\r\n\r\n.i0op5y_header {\r\n flex: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n height: 36px;\r\n padding: 0 8px 0 12px;\r\n}\r\n\r\n.i0op5y_title {\r\n flex: 1;\r\n min-width: 0;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-secondary);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_count {\r\n flex: none;\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n/* Cost display in the header (per docs/product/04-ux-reliability.i0op5y_md §5). */\r\n.i0op5y_costDisplay {\r\n flex: none;\r\n font: var(--dsw-font-xxxs-11);\r\n color: #4caf50;\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.i0op5y_zoom {\r\n flex: none;\r\n min-width: 36px;\r\n text-align: center;\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_iconButton {\r\n flex: none;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n font: var(--dsw-font-s-14);\r\n line-height: 1;\r\n}\r\n\r\n.i0op5y_iconButton:hover {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.i0op5y_iconButtonActive {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* Zoom slider in the header. */\r\n.i0op5y_zoomSlider {\r\n flex: none;\r\n width: 80px;\r\n height: 4px;\r\n -webkit-appearance: none;\r\n appearance: none;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n border-radius: 2px;\r\n outline: none;\r\n cursor: pointer;\r\n}\r\n\r\n.i0op5y_zoomSlider::-webkit-slider-thumb {\r\n -webkit-appearance: none;\r\n appearance: none;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n background: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n}\r\n\r\n.i0op5y_zoomSlider::-moz-range-thumb {\r\n width: 12px;\r\n height: 12px;\r\n border: none;\r\n border-radius: 50%;\r\n background: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n}\r\n\r\n/* ── Infinite canvas surface ───────────────────────────────────────────── */\r\n\r\n.i0op5y_surface {\r\n flex: 1 1 auto;\r\n position: relative;\r\n overflow: hidden;\r\n touch-action: none;\r\n cursor: grab;\r\n background-color: transparent;\r\n background-image:\r\n radial-gradient(var(--dsw-alias-border-l2) 1px, transparent 1px);\r\n background-size: 24px 24px;\r\n}\r\n\r\n.i0op5y_surface:active {\r\n cursor: grabbing;\r\n}\r\n\r\n.i0op5y_world {\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n transform-origin: 0 0;\r\n}\r\n\r\n.i0op5y_edgeLayer {\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n width: 1px;\r\n height: 1px;\r\n overflow: visible;\r\n pointer-events: none;\r\n}\r\n\r\n.i0op5y_edgeLine {\r\n stroke: var(--dsw-alias-label-tertiary);\r\n stroke-width: 2.5;\r\n stroke-opacity: 0.55;\r\n fill: none;\r\n stroke-linecap: round;\r\n stroke-linejoin: round;\r\n}\r\n\r\n/* Line-style variants per EdgeRelation group (see CanvasView.i0op5y_lineStyleOf).\r\n * Applied as an additional class alongside .i0op5y_edgeLine / .i0op5y_edgeArrow. */\r\n\r\n/* Direct inputs: input / first_frame / last_frame / audio_track (default solid). */\r\n.i0op5y_edgeLineSolid {\r\n stroke: var(--dsw-alias-label-tertiary);\r\n stroke-dasharray: none;\r\n stroke-width: 2.5;\r\n}\r\n\r\n/* References: reference / style / mask (dashed). */\r\n.i0op5y_edgeLineDashed {\r\n stroke: #5b8def;\r\n stroke-dasharray: 8 4;\r\n stroke-width: 2;\r\n stroke-opacity: 0.7;\r\n}\r\n\r\n/* Variations / candidates: variation_of / remix_of / alternative_of (dotted). */\r\n.i0op5y_edgeLineDotted {\r\n stroke: #c77dff;\r\n stroke-dasharray: 2 4;\r\n stroke-width: 2;\r\n stroke-opacity: 0.7;\r\n}\r\n\r\n/* Edit chain: edited_from (bold solid). */\r\n.i0op5y_edgeLineBold {\r\n stroke: var(--dsw-alias-label-secondary);\r\n stroke-dasharray: none;\r\n stroke-width: 4;\r\n stroke-opacity: 0.8;\r\n}\r\n\r\n.i0op5y_edgeArrow {\r\n fill: var(--dsw-alias-label-tertiary);\r\n fill-opacity: 0.6;\r\n stroke: var(--dsw-alias-label-tertiary);\r\n stroke-width: 1;\r\n stroke-opacity: 0.6;\r\n}\r\n\r\n/* The line-style class also applies to the arrowhead so the arrow matches\r\n * the line color + opacity of its relation group. */\r\n.i0op5y_edgeArrow.i0op5y_edgeLineDashed {\r\n fill: #5b8def;\r\n stroke: #5b8def;\r\n fill-opacity: 0.7;\r\n stroke-opacity: 0.7;\r\n}\r\n.i0op5y_edgeArrow.i0op5y_edgeLineDotted {\r\n fill: #c77dff;\r\n stroke: #c77dff;\r\n fill-opacity: 0.7;\r\n stroke-opacity: 0.7;\r\n}\r\n.i0op5y_edgeArrow.i0op5y_edgeLineBold {\r\n fill: var(--dsw-alias-label-secondary);\r\n stroke: var(--dsw-alias-label-secondary);\r\n fill-opacity: 0.8;\r\n stroke-opacity: 0.8;\r\n}\r\n\r\n/* Label chip rendered at the curve midpoint (the relation short name). */\r\n.i0op5y_edgeLabelBg {\r\n fill: var(--dsw-alias-bg-layer-2);\r\n stroke: var(--dsw-alias-border-l2);\r\n stroke-width: 1;\r\n}\r\n\r\n.i0op5y_edgeLabel {\r\n font: var(--dsw-font-xxxs-11);\r\n fill: var(--dsw-alias-label-secondary);\r\n text-anchor: middle;\r\n dominant-baseline: middle;\r\n user-select: none;\r\n pointer-events: none;\r\n}\r\n\r\n.i0op5y_edgePort {\r\n fill: var(--dsw-alias-bg-layer-2);\r\n stroke: var(--dsw-alias-label-tertiary);\r\n stroke-width: 2;\r\n stroke-opacity: 0.7;\r\n}\r\n\r\n.i0op5y_nodeBox {\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n width: 240px;\r\n}\r\n\r\n.i0op5y_nodeBoxDraggable {\r\n cursor: move;\r\n touch-action: none;\r\n}\r\n\r\n.i0op5y_empty {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n padding: 24px 16px;\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-label-tertiary);\r\n text-align: center;\r\n}\r\n\r\n.i0op5y_emptyHint {\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n/* ── Node card (8px radius, bg-layer-2, no aggressive borders) ─────────── */\r\n\r\n.i0op5y_node {\r\n border-radius: 8px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n overflow: hidden;\r\n}\r\n\r\n/* Lifecycle status visual differences (per docs/product/01-agent-autonomy.i0op5y_md §5).\r\n * draft: semi-transparent + loading animation\r\n * rejected: greyed 50% + strikethrough title\r\n * archived: greyed 30% (default hidden, shown only when filter is on)\r\n */\r\n.i0op5y_nodeStatus_draft {\r\n opacity: 0.6;\r\n}\r\n\r\n.i0op5y_nodeStatus_rejected {\r\n opacity: 0.5;\r\n filter: grayscale(0.7);\r\n}\r\n\r\n.i0op5y_nodeStatus_rejected .i0op5y_nodeTitle {\r\n text-decoration: line-through;\r\n}\r\n\r\n.i0op5y_nodeStatus_archived {\r\n opacity: 0.3;\r\n filter: grayscale(0.8);\r\n}\r\n\r\n/* Winner badge (gold star, shown when element.i0op5y_winner === true). */\r\n.i0op5y_winnerBadge {\r\n margin-left: 4px;\r\n color: #ffc107;\r\n font-size: 14px;\r\n line-height: 1;\r\n}\r\n\r\n.i0op5y_nodeHeader {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 6px 10px;\r\n}\r\n\r\n.i0op5y_kindDot {\r\n flex: none;\r\n width: 6px;\r\n height: 6px;\r\n border-radius: 50%;\r\n background: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_kindDot_image { background: #4caf50; }\r\n.i0op5y_kindDot_video { background: #ff9800; }\r\n.i0op5y_kindDot_audio { background: #ab47bc; }\r\n.i0op5y_kindDot_prompt { background: #6b8cff; }\r\n\r\n.i0op5y_kindLabel {\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n text-transform: lowercase;\r\n}\r\n\r\n.i0op5y_nodeTime {\r\n margin-left: auto;\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n.i0op5y_nodeTitle {\r\n padding: 0 10px 4px;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-primary);\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_nodeDescription {\r\n padding: 0 10px 4px;\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-label-tertiary);\r\n font-style: italic;\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_nodeMedia {\r\n padding: 0 10px 8px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n max-width: 100%;\r\n}\r\n\r\n.i0op5y_promptText {\r\n margin: 0;\r\n padding: 6px 8px;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n border-radius: 6px;\r\n font: var(--dsw-font-xxs-12);\r\n font-family: var(--ds-font-family-code);\r\n white-space: pre-wrap;\r\n word-break: break-word;\r\n max-height: 200px;\r\n overflow-y: auto;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.i0op5y_mediaImage {\r\n /* Counter-scale trick: decode at the on-screen pixel size (layout width\r\n * × zoom) so zooming in stays crisp, then visually shrink back to the\r\n * layout box. `--canvas-scale` is set on the world layer by CanvasView;\r\n * `--media-ratio` (h/w) is set on each img by CanvasNode after onLoad.\r\n *\r\n * width = 100% × scale → layout box (e.i0op5y_g. 880px at 4× zoom)\r\n * transform: scale(1/scale) → visual 220px (back to layout box size)\r\n * margin-right: 100% × (1-s) → cancel horizontal layout inflation\r\n * margin-bottom: 100% × ratio × (1-s) → cancel vertical layout inflation\r\n *\r\n * Percentages on margin resolve against the containing block's WIDTH\r\n * (the 220px card content area), so `100%` = 220px here — which is\r\n * exactly the visual width, making the math work out. */\r\n width: calc(100% * var(--canvas-scale, 1));\r\n max-width: none;\r\n height: auto;\r\n transform: scale(calc(1 / var(--canvas-scale, 1)));\r\n transform-origin: top left;\r\n margin-right: calc(100% * (1 - var(--canvas-scale, 1)));\r\n margin-bottom: calc(100% * var(--media-ratio, 0.75) * (1 - var(--canvas-scale, 1)));\r\n border-radius: 4px;\r\n display: block;\r\n}\r\n\r\n.i0op5y_mediaVideo {\r\n width: calc(100% * var(--canvas-scale, 1));\r\n max-width: none;\r\n height: auto;\r\n transform: scale(calc(1 / var(--canvas-scale, 1)));\r\n transform-origin: top left;\r\n margin-right: calc(100% * (1 - var(--canvas-scale, 1)));\r\n margin-bottom: calc(100% * var(--media-ratio, 0.75) * (1 - var(--canvas-scale, 1)));\r\n border-radius: 4px;\r\n display: block;\r\n}\r\n\r\n.i0op5y_mediaAudio {\r\n width: 100%;\r\n display: block;\r\n}\r\n\r\n.i0op5y_boundaryError {\r\n margin: 8px;\r\n padding: 8px 12px;\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-state-error-primary);\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n border-radius: 6px;\r\n}\r\n\r\n/* ── Detail panel (elevated surface, mirror Modal/Panel aesthetic) ─────── */\r\n\r\n.i0op5y_detailPanel {\r\n position: absolute;\r\n right: 8px;\r\n top: 44px;\r\n bottom: 8px;\r\n width: 280px;\r\n display: flex;\r\n flex-direction: column;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 12px;\r\n box-shadow: var(--dsw-shadow-lv3);\r\n z-index: 10;\r\n overflow: hidden;\r\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\r\n --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);\r\n}\r\n\r\n.i0op5y_detailHeader {\r\n flex: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 10px;\r\n}\r\n\r\n.i0op5y_detailTitle {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-primary);\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_detailClose {\r\n flex: none;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n font: var(--dsw-font-s-14);\r\n line-height: 1;\r\n}\r\n\r\n.i0op5y_detailClose:hover {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.i0op5y_detailBody {\r\n flex: 1 1 auto;\r\n min-height: 0;\r\n overflow-y: auto;\r\n padding: 0 10px 10px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 10px;\r\n}\r\n\r\n.i0op5y_detailBlock {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n min-width: 0;\r\n}\r\n\r\n.i0op5y_detailLabel {\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n text-transform: lowercase;\r\n}\r\n\r\n.i0op5y_detailPrompt {\r\n margin: 0;\r\n padding: 6px 8px;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n border-radius: 6px;\r\n font: var(--dsw-font-xxs-12);\r\n font-family: var(--ds-font-family-code);\r\n white-space: pre-wrap;\r\n word-break: break-word;\r\n max-height: 160px;\r\n overflow-y: auto;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.i0op5y_detailValue {\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-label-secondary);\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_metaList {\r\n margin: 0;\r\n padding: 0;\r\n display: grid;\r\n grid-template-columns: auto 1fr;\r\n gap: 2px 8px;\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_metaKey {\r\n font: var(--dsw-font-xxxs-strong-11);\r\n color: var(--dsw-alias-label-secondary);\r\n text-transform: lowercase;\r\n}\r\n\r\n.i0op5y_metaValue {\r\n margin: 0;\r\n color: var(--dsw-alias-label-secondary);\r\n word-break: break-all;\r\n font-family: var(--ds-font-family-code);\r\n}\r\n\r\n.i0op5y_filePath {\r\n display: block;\r\n font: var(--dsw-font-xxxs-11);\r\n font-family: var(--ds-font-family-code);\r\n color: var(--dsw-alias-label-tertiary);\r\n word-break: break-all;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n padding: 4px 6px;\r\n border-radius: 4px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n/* ── Minimap (bottom-right overview) ──────────────────────────────────── */\r\n\r\n.i0op5y_minimap {\r\n position: absolute;\r\n right: 8px;\r\n bottom: 8px;\r\n z-index: 5;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 8px;\r\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);\r\n padding: 4px;\r\n cursor: pointer;\r\n overflow: hidden;\r\n}\r\n\r\n.i0op5y_minimapSvg {\r\n display: block;\r\n pointer-events: none;\r\n}\r\n\r\n/* ── Right-click context menu ─────────────────────────────────────────── */\r\n\r\n.i0op5y_contextMenu {\r\n position: fixed;\r\n z-index: 20;\r\n min-width: 160px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 8px;\r\n box-shadow: var(--dsw-shadow-lv3);\r\n padding: 4px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n}\r\n\r\n.i0op5y_contextMenuItem {\r\n display: block;\r\n width: 100%;\r\n padding: 6px 10px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n font: var(--dsw-font-xxs-12);\r\n text-align: left;\r\n cursor: pointer;\r\n}\r\n\r\n.i0op5y_contextMenuItem:hover {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* The Delete item keeps the destructive (red) styling from the old\r\n * single-item menu so it's visually distinct from the status / agent\r\n * actions above the separator. */\r\n.i0op5y_contextMenuItemDanger:hover {\r\n background: var(--dsw-alias-state-error-bg);\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n/* Disabled state (e.i0op5y_g. \"Mark as winner\" on a prompt element with no media,\r\n * or \"Edit selected\" with no selection). */\r\n.i0op5y_contextMenuItemDisabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n\r\n.i0op5y_contextMenuItemDisabled:hover {\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* Horizontal rule between action groups (regenerate → download,\r\n * promote → status, status → delete). */\r\n.i0op5y_contextMenuSeparator {\r\n height: 1px;\r\n margin: 4px 6px;\r\n background: var(--dsw-alias-border-l2);\r\n border: none;\r\n}\r\n\r\n/* ── Quick action toolbar (left side of the canvas, per doc 04 §7) ────── */\r\n\r\n.i0op5y_toolbar {\r\n position: absolute;\r\n left: 8px;\r\n top: 8px;\r\n z-index: 8;\r\n display: flex;\r\n gap: 4px;\r\n padding: 4px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 8px;\r\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);\r\n}\r\n\r\n.i0op5y_toolbarButton {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 5px 10px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n font: var(--dsw-font-xxs-12);\r\n cursor: pointer;\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_toolbarButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.i0op5y_toolbarButton:disabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n\r\n/* ── Drag-drop indicator + upload overlay ─────────────────────────────── */\r\n\r\n.i0op5y_dropIndicator {\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n width: 240px;\r\n height: 110px;\r\n border: 2px dashed var(--dsw-alias-label-secondary);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n opacity: 0.5;\r\n pointer-events: none;\r\n transform-origin: 0 0;\r\n}\r\n\r\n.i0op5y_uploadOverlay {\r\n position: absolute;\r\n inset: 0;\r\n z-index: 15;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background: var(--dsw-alias-bg-layer-1);\r\n opacity: 0.6;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-secondary);\r\n pointer-events: none;\r\n}\r\n\r\n/* ── Request log panel (per docs/product/04-ux-reliability.i0op5y_md §3) ──────── */\r\n\r\n.i0op5y_logPanel {\r\n position: absolute;\r\n right: 8px;\r\n top: 44px;\r\n bottom: 8px;\r\n width: 480px;\r\n max-width: calc(100% - 16px);\r\n display: flex;\r\n flex-direction: column;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 12px;\r\n box-shadow: var(--dsw-shadow-lv3);\r\n z-index: 12;\r\n overflow: hidden;\r\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\r\n --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);\r\n}\r\n\r\n.i0op5y_logPanelHeader {\r\n flex: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 10px;\r\n border-bottom: 1px solid var(--dsw-alias-border-l2);\r\n}\r\n\r\n.i0op5y_logPanelTitle {\r\n flex: 1;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.i0op5y_logPanelClear {\r\n flex: none;\r\n padding: 4px 10px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n font: var(--dsw-font-xxs-12);\r\n cursor: pointer;\r\n}\r\n\r\n.i0op5y_logPanelClear:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.i0op5y_logPanelClear:disabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n\r\n.i0op5y_logList {\r\n flex: 1 1 auto;\r\n min-height: 0;\r\n overflow-y: auto;\r\n padding: 4px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n}\r\n\r\n.i0op5y_logRow {\r\n display: flex;\r\n flex-direction: column;\r\n border-radius: 6px;\r\n overflow: hidden;\r\n}\r\n\r\n.i0op5y_logRowHeader {\r\n display: grid;\r\n grid-template-columns: 80px 1fr 40px 50px 60px 16px;\r\n gap: 6px;\r\n align-items: center;\r\n padding: 5px 8px;\r\n border: none;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n font: var(--dsw-font-xxxs-11);\r\n font-family: var(--ds-font-family-code);\r\n cursor: pointer;\r\n text-align: left;\r\n border-radius: 6px;\r\n}\r\n\r\n.i0op5y_logRowHeader:hover {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n}\r\n\r\n.i0op5y_logRowFailed {\r\n color: var(--dsw-alias-state-error-primary);\r\n background: var(--dsw-alias-state-error-bg);\r\n}\r\n\r\n.i0op5y_logRowFailed:hover {\r\n background: var(--dsw-alias-state-error-bg);\r\n filter: brightness(0.95);\r\n}\r\n\r\n.i0op5y_logTime {\r\n color: var(--dsw-alias-label-dimmed);\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_logLabel {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.i0op5y_logStatus {\r\n text-align: right;\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.i0op5y_logDuration {\r\n text-align: right;\r\n font-variant-numeric: tabular-nums;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_logSize {\r\n text-align: right;\r\n font-variant-numeric: tabular-nums;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_logExpand {\r\n text-align: center;\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n.i0op5y_logDetail {\r\n padding: 6px 10px 8px;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n border-top: 1px solid var(--dsw-alias-border-l2);\r\n}\r\n\r\n.i0op5y_logError {\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-state-error-primary);\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_logDetailBlock {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n min-width: 0;\r\n}\r\n\r\n.i0op5y_logDetailLabel {\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n text-transform: lowercase;\r\n}\r\n\r\n.i0op5y_logDetailPre {\r\n margin: 0;\r\n padding: 4px 6px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n border-radius: 4px;\r\n font: var(--dsw-font-xxxs-11);\r\n font-family: var(--ds-font-family-code);\r\n white-space: pre-wrap;\r\n word-break: break-all;\r\n max-height: 160px;\r\n overflow-y: auto;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.i0op5y_logFilePath {\r\n display: block;\r\n font: var(--dsw-font-xxxs-11);\r\n font-family: var(--ds-font-family-code);\r\n color: var(--dsw-alias-label-tertiary);\r\n word-break: break-all;\r\n background: var(--dsw-alias-bg-layer-1);\r\n padding: 4px 6px;\r\n border-radius: 4px;\r\n}\r\n\r\n.i0op5y_logLocateButton {\r\n align-self: flex-start;\r\n margin-top: 4px;\r\n padding: 3px 8px;\r\n border: none;\r\n border-radius: 4px;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-secondary);\r\n font: var(--dsw-font-xxxs-11);\r\n cursor: pointer;\r\n}\r\n\r\n.i0op5y_logLocateButton:hover {\r\n background: var(--dsw-alias-interactive-bg-active);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* ── Multi-select + Compare view (per docs/product/04-ux-reliability.i0op5y_md §2) */\r\n\r\n/* Outline drawn around nodes that are in the multi-select set. The\r\n * outline uses the brand color so it's clearly distinguishable from the\r\n * regular hover/focus ring. Drawn on the nodeBox wrapper (not the inner\r\n * .i0op5y_node card) so it doesn't fight with the card's own border-radius. */\r\n.i0op5y_nodeBoxMultiSelected {\r\n outline: 2px solid var(--dsw-alias-brand-primary);\r\n outline-offset: 2px;\r\n border-radius: 10px;\r\n}\r\n\r\n/* Floating bar above the canvas (top-center) that appears when 2-4\r\n * elements are in the multi-select set. Shows the count + a \"Compare\"\r\n * button + a \"Clear\" button. */\r\n.i0op5y_multiSelectBar {\r\n position: absolute;\r\n top: 8px;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n z-index: 9;\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 4px 6px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 8px;\r\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);\r\n}\r\n\r\n.i0op5y_multiSelectCount {\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-label-secondary);\r\n padding: 0 4px;\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_multiSelectCompareButton {\r\n display: inline-flex;\r\n align-items: center;\r\n padding: 5px 12px;\r\n border: none;\r\n border-radius: 6px;\r\n background: var(--dsw-alias-brand-primary);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n font: var(--dsw-font-xxs-12);\r\n cursor: pointer;\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_multiSelectCompareButton:hover:not(:disabled) {\r\n filter: brightness(1.05);\r\n}\r\n\r\n.i0op5y_multiSelectCompareButton:disabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n\r\n.i0op5y_multiSelectClearButton {\r\n display: inline-flex;\r\n align-items: center;\r\n padding: 5px 10px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n font: var(--dsw-font-xxs-12);\r\n cursor: pointer;\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_multiSelectClearButton:hover {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* The compare overlay: covers the entire canvas surface with a semi-opaque\r\n * backdrop so the user can focus on comparing the selected elements.\r\n * Stops wheel + pointer events from reaching the canvas surface below. */\r\n.i0op5y_compareOverlay {\r\n position: absolute;\r\n inset: 36px 0 0 0;\r\n z-index: 18;\r\n display: flex;\r\n flex-direction: column;\r\n background: var(--dsw-alias-bg-layer-1);\r\n /* Slight opacity so the canvas is still hinted behind, but the focus\r\n * is clearly on the compare cards. */\r\n backdrop-filter: blur(2px);\r\n}\r\n\r\n.i0op5y_compareHeader {\r\n flex: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 12px;\r\n border-bottom: 1px solid var(--dsw-alias-border-l2);\r\n}\r\n\r\n.i0op5y_compareTitle {\r\n flex: 1;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.i0op5y_compareCloseButton {\r\n flex: none;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n font: var(--dsw-font-s-14);\r\n line-height: 1;\r\n}\r\n\r\n.i0op5y_compareCloseButton:hover {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* Grid of compare cards. Up to 4 columns; each card takes an equal\r\n * fraction of the available width. On narrow viewports the cards wrap\r\n * to the next row (rare in practice — the overlay is wide). */\r\n.i0op5y_compareGrid {\r\n flex: 1 1 auto;\r\n min-height: 0;\r\n overflow-y: auto;\r\n padding: 12px;\r\n display: grid;\r\n grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));\r\n gap: 12px;\r\n align-content: start;\r\n}\r\n\r\n/* One compare card: media box on top, prompt + meta + winner button below. */\r\n.i0op5y_compareCard {\r\n display: flex;\r\n flex-direction: column;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 12px;\r\n box-shadow: var(--dsw-shadow-lv3);\r\n overflow: hidden;\r\n /* Equal height per row (so the winner buttons line up at the bottom). */\r\n min-height: 320px;\r\n}\r\n\r\n/* Media box: fixed target height so all cards in one row line up.\r\n * Media is `object-fit: contain` so different aspect ratios still\r\n * render fully without distortion. */\r\n.i0op5y_compareCardMedia {\r\n flex: 0 0 200px;\r\n width: 100%;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n overflow: hidden;\r\n}\r\n\r\n.i0op5y_compareMediaImage,\r\n.i0op5y_compareMediaVideo {\r\n max-width: 100%;\r\n max-height: 100%;\r\n object-fit: contain;\r\n display: block;\r\n}\r\n\r\n.i0op5y_compareMediaAudioWrap {\r\n width: 100%;\r\n padding: 0 12px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.i0op5y_compareMediaAudio {\r\n width: 100%;\r\n}\r\n\r\n.i0op5y_compareMediaEmpty {\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-label-tertiary);\r\n text-align: center;\r\n padding: 12px;\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_compareCardBody {\r\n flex: 1 1 auto;\r\n min-height: 0;\r\n overflow-y: auto;\r\n padding: 10px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n.i0op5y_comparePrompt {\r\n margin: 0;\r\n padding: 6px 8px;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n border-radius: 6px;\r\n font: var(--dsw-font-xxs-12);\r\n font-family: var(--ds-font-family-code);\r\n white-space: pre-wrap;\r\n word-break: break-word;\r\n max-height: 120px;\r\n overflow-y: auto;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.i0op5y_comparePromptEmpty {\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-label-dimmed);\r\n font-style: italic;\r\n padding: 6px 8px;\r\n}\r\n\r\n.i0op5y_compareMetaRow {\r\n display: flex;\r\n gap: 6px;\r\n align-items: baseline;\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_compareMetaLabel {\r\n flex: none;\r\n color: var(--dsw-alias-label-secondary);\r\n text-transform: lowercase;\r\n}\r\n\r\n.i0op5y_compareMetaValue {\r\n flex: 1 1 auto;\r\n color: var(--dsw-alias-label-primary);\r\n font-variant-numeric: tabular-nums;\r\n word-break: break-all;\r\n}\r\n\r\n.i0op5y_compareCardFooter {\r\n flex: none;\r\n padding: 8px 10px;\r\n border-top: 1px solid var(--dsw-alias-border-l2);\r\n display: flex;\r\n justify-content: center;\r\n}\r\n\r\n/* Compare footer (overlay-level): the \"Reject all\" + \"Close\" buttons. */\r\n.i0op5y_compareFooter {\r\n flex: none;\r\n display: flex;\r\n justify-content: flex-end;\r\n gap: 8px;\r\n padding: 10px 12px;\r\n border-top: 1px solid var(--dsw-alias-border-l2);\r\n background: var(--dsw-alias-bg-layer-2);\r\n}\r\n\r\n/* Button styles for the compare view (primary / secondary / danger).\r\n * These mirror the toolbar button aesthetic but are slightly taller\r\n * (h32 r8 instead of h24 r6) since the compare footer has more room\r\n * and the actions are consequential. */\r\n.i0op5y_compareButtonPrimary {\r\n display: inline-flex;\r\n align-items: center;\r\n padding: 6px 14px;\r\n border: none;\r\n border-radius: 6px;\r\n background: var(--dsw-alias-brand-primary);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n font: var(--dsw-font-xxs-12);\r\n cursor: pointer;\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_compareButtonPrimary:hover:not(:disabled) {\r\n filter: brightness(1.05);\r\n}\r\n\r\n.i0op5y_compareButtonPrimary:disabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n\r\n.i0op5y_compareButtonSecondary {\r\n display: inline-flex;\r\n align-items: center;\r\n padding: 6px 14px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n font: var(--dsw-font-xxs-12);\r\n cursor: pointer;\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_compareButtonSecondary:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n}\r\n\r\n.i0op5y_compareButtonDanger {\r\n display: inline-flex;\r\n align-items: center;\r\n padding: 6px 14px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-state-error-primary);\r\n font: var(--dsw-font-xxs-12);\r\n cursor: pointer;\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_compareButtonDanger:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n}\r\n\r\n.i0op5y_compareButtonDanger:disabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n";
|
|
337
|
-
const tagId$1 = "@
|
|
237
|
+
const css$1 = "/* dsh-aigc-canvas client styles.\r\n *\r\n * Renders inside a better-sidebar tab panel. Follows the DSH core design\r\n * language: `--dsw-alias-*` semantic tokens, `--dsw-font-*` typography,\r\n * 36px header bar + 24x24 r6 icon buttons (mirror of SubagentView), 8px\r\n * radius cards on `--dsw-alias-bg-layer-2`, no aggressive borders —\r\n * hover/active fills carry the interaction state.\r\n */\r\n\r\n.i0op5y_canvas {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-primary);\r\n background: transparent;\r\n}\r\n\r\n/* ── Header bar (mirror SubagentView: 36px h, 0 8px 0 12px) ────────────── */\r\n\r\n.i0op5y_header {\r\n flex: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n height: 36px;\r\n padding: 0 8px 0 12px;\r\n}\r\n\r\n.i0op5y_title {\r\n flex: 1;\r\n min-width: 0;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-secondary);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.i0op5y_count {\r\n flex: none;\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_zoom {\r\n flex: none;\r\n min-width: 36px;\r\n text-align: center;\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_iconButton {\r\n flex: none;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n font: var(--dsw-font-s-14);\r\n line-height: 1;\r\n}\r\n\r\n.i0op5y_iconButton:hover {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* Zoom slider in the header. */\r\n.i0op5y_zoomSlider {\r\n flex: none;\r\n width: 80px;\r\n height: 4px;\r\n -webkit-appearance: none;\r\n appearance: none;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n border-radius: 2px;\r\n outline: none;\r\n cursor: pointer;\r\n}\r\n\r\n.i0op5y_zoomSlider::-webkit-slider-thumb {\r\n -webkit-appearance: none;\r\n appearance: none;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n background: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n}\r\n\r\n.i0op5y_zoomSlider::-moz-range-thumb {\r\n width: 12px;\r\n height: 12px;\r\n border: none;\r\n border-radius: 50%;\r\n background: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n}\r\n\r\n/* ── Infinite canvas surface ───────────────────────────────────────────── */\r\n\r\n.i0op5y_surface {\r\n flex: 1 1 auto;\r\n position: relative;\r\n overflow: hidden;\r\n touch-action: none;\r\n cursor: grab;\r\n background-color: transparent;\r\n background-image:\r\n radial-gradient(var(--dsw-alias-border-l2) 1px, transparent 1px);\r\n background-size: 24px 24px;\r\n}\r\n\r\n.i0op5y_surface:active {\r\n cursor: grabbing;\r\n}\r\n\r\n.i0op5y_world {\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n transform-origin: 0 0;\r\n}\r\n\r\n.i0op5y_edgeLayer {\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n width: 1px;\r\n height: 1px;\r\n overflow: visible;\r\n pointer-events: none;\r\n}\r\n\r\n.i0op5y_edgeLine {\r\n stroke: var(--dsw-alias-label-tertiary);\r\n stroke-width: 2.5;\r\n stroke-opacity: 0.55;\r\n fill: none;\r\n stroke-linecap: round;\r\n stroke-linejoin: round;\r\n}\r\n\r\n.i0op5y_edgeArrow {\r\n fill: var(--dsw-alias-label-tertiary);\r\n fill-opacity: 0.6;\r\n stroke: var(--dsw-alias-label-tertiary);\r\n stroke-width: 1;\r\n stroke-opacity: 0.6;\r\n}\r\n\r\n.i0op5y_edgePort {\r\n fill: var(--dsw-alias-bg-layer-2);\r\n stroke: var(--dsw-alias-label-tertiary);\r\n stroke-width: 2;\r\n stroke-opacity: 0.7;\r\n}\r\n\r\n.i0op5y_nodeBox {\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n width: 240px;\r\n}\r\n\r\n.i0op5y_nodeBoxDraggable {\r\n cursor: move;\r\n touch-action: none;\r\n}\r\n\r\n.i0op5y_empty {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n padding: 24px 16px;\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-label-tertiary);\r\n text-align: center;\r\n}\r\n\r\n.i0op5y_emptyHint {\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n/* ── Node card (8px radius, bg-layer-2, no aggressive borders) ─────────── */\r\n\r\n.i0op5y_node {\r\n border-radius: 8px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n overflow: hidden;\r\n}\r\n\r\n.i0op5y_nodeHeader {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 6px 10px;\r\n}\r\n\r\n.i0op5y_kindDot {\r\n flex: none;\r\n width: 6px;\r\n height: 6px;\r\n border-radius: 50%;\r\n background: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_kindDot_image { background: #4caf50; }\r\n.i0op5y_kindDot_video { background: #ff9800; }\r\n.i0op5y_kindDot_audio { background: #ab47bc; }\r\n.i0op5y_kindDot_prompt { background: #6b8cff; }\r\n\r\n.i0op5y_kindLabel {\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n text-transform: lowercase;\r\n}\r\n\r\n.i0op5y_nodeTime {\r\n margin-left: auto;\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n.i0op5y_nodeTitle {\r\n padding: 0 10px 4px;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-primary);\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_nodeDescription {\r\n padding: 0 10px 4px;\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-label-tertiary);\r\n font-style: italic;\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_nodeMedia {\r\n padding: 0 10px 8px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n max-width: 100%;\r\n}\r\n\r\n.i0op5y_promptText {\r\n margin: 0;\r\n padding: 6px 8px;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n border-radius: 6px;\r\n font: var(--dsw-font-xxs-12);\r\n font-family: var(--ds-font-family-code);\r\n white-space: pre-wrap;\r\n word-break: break-word;\r\n max-height: 200px;\r\n overflow-y: auto;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.i0op5y_mediaImage {\r\n /* Counter-scale trick: decode at the on-screen pixel size (layout width\r\n * × zoom) so zooming in stays crisp, then visually shrink back to the\r\n * layout box. `--canvas-scale` is set on the world layer by CanvasView;\r\n * `--media-ratio` (h/w) is set on each img by CanvasNode after onLoad.\r\n *\r\n * width = 100% × scale → layout box (e.i0op5y_g. 880px at 4× zoom)\r\n * transform: scale(1/scale) → visual 220px (back to layout box size)\r\n * margin-right: 100% × (1-s) → cancel horizontal layout inflation\r\n * margin-bottom: 100% × ratio × (1-s) → cancel vertical layout inflation\r\n *\r\n * Percentages on margin resolve against the containing block's WIDTH\r\n * (the 220px card content area), so `100%` = 220px here — which is\r\n * exactly the visual width, making the math work out. */\r\n width: calc(100% * var(--canvas-scale, 1));\r\n max-width: none;\r\n height: auto;\r\n transform: scale(calc(1 / var(--canvas-scale, 1)));\r\n transform-origin: top left;\r\n margin-right: calc(100% * (1 - var(--canvas-scale, 1)));\r\n margin-bottom: calc(100% * var(--media-ratio, 0.75) * (1 - var(--canvas-scale, 1)));\r\n border-radius: 4px;\r\n display: block;\r\n}\r\n\r\n.i0op5y_mediaVideo {\r\n width: calc(100% * var(--canvas-scale, 1));\r\n max-width: none;\r\n height: auto;\r\n transform: scale(calc(1 / var(--canvas-scale, 1)));\r\n transform-origin: top left;\r\n margin-right: calc(100% * (1 - var(--canvas-scale, 1)));\r\n margin-bottom: calc(100% * var(--media-ratio, 0.75) * (1 - var(--canvas-scale, 1)));\r\n border-radius: 4px;\r\n display: block;\r\n}\r\n\r\n.i0op5y_mediaAudio {\r\n width: 100%;\r\n display: block;\r\n}\r\n\r\n.i0op5y_boundaryError {\r\n margin: 8px;\r\n padding: 8px 12px;\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-state-error-primary);\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n border-radius: 6px;\r\n}\r\n\r\n/* ── Detail panel (elevated surface, mirror Modal/Panel aesthetic) ─────── */\r\n\r\n.i0op5y_detailPanel {\r\n position: absolute;\r\n right: 8px;\r\n top: 44px;\r\n bottom: 8px;\r\n width: 280px;\r\n display: flex;\r\n flex-direction: column;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 12px;\r\n box-shadow: var(--dsw-shadow-lv3);\r\n z-index: 10;\r\n overflow: hidden;\r\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\r\n --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);\r\n}\r\n\r\n.i0op5y_detailHeader {\r\n flex: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 10px;\r\n}\r\n\r\n.i0op5y_detailTitle {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-primary);\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_detailClose {\r\n flex: none;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n font: var(--dsw-font-s-14);\r\n line-height: 1;\r\n}\r\n\r\n.i0op5y_detailClose:hover {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.i0op5y_detailBody {\r\n flex: 1 1 auto;\r\n min-height: 0;\r\n overflow-y: auto;\r\n padding: 0 10px 10px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 10px;\r\n}\r\n\r\n.i0op5y_detailBlock {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n min-width: 0;\r\n}\r\n\r\n.i0op5y_detailLabel {\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n text-transform: lowercase;\r\n}\r\n\r\n.i0op5y_detailPrompt {\r\n margin: 0;\r\n padding: 6px 8px;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n border-radius: 6px;\r\n font: var(--dsw-font-xxs-12);\r\n font-family: var(--ds-font-family-code);\r\n white-space: pre-wrap;\r\n word-break: break-word;\r\n max-height: 160px;\r\n overflow-y: auto;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.i0op5y_detailValue {\r\n font: var(--dsw-font-xxs-12);\r\n color: var(--dsw-alias-label-secondary);\r\n word-break: break-word;\r\n}\r\n\r\n.i0op5y_metaList {\r\n margin: 0;\r\n padding: 0;\r\n display: grid;\r\n grid-template-columns: auto 1fr;\r\n gap: 2px 8px;\r\n font: var(--dsw-font-xxxs-11);\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.i0op5y_metaKey {\r\n font: var(--dsw-font-xxxs-strong-11);\r\n color: var(--dsw-alias-label-secondary);\r\n text-transform: lowercase;\r\n}\r\n\r\n.i0op5y_metaValue {\r\n margin: 0;\r\n color: var(--dsw-alias-label-secondary);\r\n word-break: break-all;\r\n font-family: var(--ds-font-family-code);\r\n}\r\n\r\n.i0op5y_filePath {\r\n display: block;\r\n font: var(--dsw-font-xxxs-11);\r\n font-family: var(--ds-font-family-code);\r\n color: var(--dsw-alias-label-tertiary);\r\n word-break: break-all;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n padding: 4px 6px;\r\n border-radius: 4px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n/* ── Minimap (bottom-right overview) ──────────────────────────────────── */\r\n\r\n.i0op5y_minimap {\r\n position: absolute;\r\n right: 8px;\r\n bottom: 8px;\r\n z-index: 5;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 8px;\r\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);\r\n padding: 4px;\r\n cursor: pointer;\r\n overflow: hidden;\r\n}\r\n\r\n.i0op5y_minimapSvg {\r\n display: block;\r\n pointer-events: none;\r\n}\r\n\r\n/* ── Right-click context menu ─────────────────────────────────────────── */\r\n\r\n.i0op5y_contextMenu {\r\n position: fixed;\r\n z-index: 20;\r\n min-width: 120px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border-radius: 8px;\r\n box-shadow: var(--dsw-shadow-lv3);\r\n padding: 4px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n}\r\n\r\n.i0op5y_contextMenuItem {\r\n display: block;\r\n width: 100%;\r\n padding: 6px 10px;\r\n border: none;\r\n border-radius: 6px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n font: var(--dsw-font-xxs-12);\r\n text-align: left;\r\n cursor: pointer;\r\n}\r\n\r\n.i0op5y_contextMenuItem:hover {\r\n background: var(--dsw-alias-state-error-bg);\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n/* ── Drag-drop indicator + upload overlay ─────────────────────────────── */\r\n\r\n.i0op5y_dropIndicator {\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n width: 240px;\r\n height: 110px;\r\n border: 2px dashed var(--dsw-alias-label-secondary);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n opacity: 0.5;\r\n pointer-events: none;\r\n transform-origin: 0 0;\r\n}\r\n\r\n.i0op5y_uploadOverlay {\r\n position: absolute;\r\n inset: 0;\r\n z-index: 15;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background: var(--dsw-alias-bg-layer-1);\r\n opacity: 0.6;\r\n font: var(--dsw-font-s-14);\r\n color: var(--dsw-alias-label-secondary);\r\n pointer-events: none;\r\n}\r\n";
|
|
238
|
+
const tagId$1 = "@dsh-external/dsh-aigc-canvas/canvas.module.css";
|
|
338
239
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
339
240
|
const tag = document.createElement("style");
|
|
340
|
-
tag.dataset.plugin = "@
|
|
241
|
+
tag.dataset.plugin = "@dsh-external/dsh-aigc-canvas";
|
|
341
242
|
tag.dataset.pluginCss = tagId$1;
|
|
342
243
|
tag.textContent = css$1;
|
|
343
244
|
document.head.appendChild(tag);
|
|
@@ -347,36 +248,20 @@ window.__ModuleLoader__.load({
|
|
|
347
248
|
"header": "i0op5y_header",
|
|
348
249
|
"title": "i0op5y_title",
|
|
349
250
|
"count": "i0op5y_count",
|
|
350
|
-
"md": "i0op5y_md",
|
|
351
|
-
"costDisplay": "i0op5y_costDisplay",
|
|
352
251
|
"zoom": "i0op5y_zoom",
|
|
353
252
|
"iconButton": "i0op5y_iconButton",
|
|
354
|
-
"iconButtonActive": "i0op5y_iconButtonActive",
|
|
355
253
|
"zoomSlider": "i0op5y_zoomSlider",
|
|
356
254
|
"surface": "i0op5y_surface",
|
|
357
255
|
"world": "i0op5y_world",
|
|
358
256
|
"edgeLayer": "i0op5y_edgeLayer",
|
|
359
257
|
"edgeLine": "i0op5y_edgeLine",
|
|
360
|
-
"lineStyleOf": "i0op5y_lineStyleOf",
|
|
361
258
|
"edgeArrow": "i0op5y_edgeArrow",
|
|
362
|
-
"edgeLineSolid": "i0op5y_edgeLineSolid",
|
|
363
|
-
"edgeLineDashed": "i0op5y_edgeLineDashed",
|
|
364
|
-
"edgeLineDotted": "i0op5y_edgeLineDotted",
|
|
365
|
-
"edgeLineBold": "i0op5y_edgeLineBold",
|
|
366
|
-
"edgeLabelBg": "i0op5y_edgeLabelBg",
|
|
367
|
-
"edgeLabel": "i0op5y_edgeLabel",
|
|
368
259
|
"edgePort": "i0op5y_edgePort",
|
|
369
260
|
"nodeBox": "i0op5y_nodeBox",
|
|
370
261
|
"nodeBoxDraggable": "i0op5y_nodeBoxDraggable",
|
|
371
262
|
"empty": "i0op5y_empty",
|
|
372
263
|
"emptyHint": "i0op5y_emptyHint",
|
|
373
264
|
"node": "i0op5y_node",
|
|
374
|
-
"nodeStatus_draft": "i0op5y_nodeStatus_draft",
|
|
375
|
-
"nodeStatus_rejected": "i0op5y_nodeStatus_rejected",
|
|
376
|
-
"nodeTitle": "i0op5y_nodeTitle",
|
|
377
|
-
"nodeStatus_archived": "i0op5y_nodeStatus_archived",
|
|
378
|
-
"winner": "i0op5y_winner",
|
|
379
|
-
"winnerBadge": "i0op5y_winnerBadge",
|
|
380
265
|
"nodeHeader": "i0op5y_nodeHeader",
|
|
381
266
|
"kindDot": "i0op5y_kindDot",
|
|
382
267
|
"kindDot_image": "i0op5y_kindDot_image",
|
|
@@ -385,6 +270,7 @@ window.__ModuleLoader__.load({
|
|
|
385
270
|
"kindDot_prompt": "i0op5y_kindDot_prompt",
|
|
386
271
|
"kindLabel": "i0op5y_kindLabel",
|
|
387
272
|
"nodeTime": "i0op5y_nodeTime",
|
|
273
|
+
"nodeTitle": "i0op5y_nodeTitle",
|
|
388
274
|
"nodeDescription": "i0op5y_nodeDescription",
|
|
389
275
|
"nodeMedia": "i0op5y_nodeMedia",
|
|
390
276
|
"promptText": "i0op5y_promptText",
|
|
@@ -410,62 +296,8 @@ window.__ModuleLoader__.load({
|
|
|
410
296
|
"minimapSvg": "i0op5y_minimapSvg",
|
|
411
297
|
"contextMenu": "i0op5y_contextMenu",
|
|
412
298
|
"contextMenuItem": "i0op5y_contextMenuItem",
|
|
413
|
-
"contextMenuItemDanger": "i0op5y_contextMenuItemDanger",
|
|
414
|
-
"contextMenuItemDisabled": "i0op5y_contextMenuItemDisabled",
|
|
415
|
-
"contextMenuSeparator": "i0op5y_contextMenuSeparator",
|
|
416
|
-
"toolbar": "i0op5y_toolbar",
|
|
417
|
-
"toolbarButton": "i0op5y_toolbarButton",
|
|
418
299
|
"dropIndicator": "i0op5y_dropIndicator",
|
|
419
|
-
"uploadOverlay": "i0op5y_uploadOverlay"
|
|
420
|
-
"logPanel": "i0op5y_logPanel",
|
|
421
|
-
"logPanelHeader": "i0op5y_logPanelHeader",
|
|
422
|
-
"logPanelTitle": "i0op5y_logPanelTitle",
|
|
423
|
-
"logPanelClear": "i0op5y_logPanelClear",
|
|
424
|
-
"logList": "i0op5y_logList",
|
|
425
|
-
"logRow": "i0op5y_logRow",
|
|
426
|
-
"logRowHeader": "i0op5y_logRowHeader",
|
|
427
|
-
"logRowFailed": "i0op5y_logRowFailed",
|
|
428
|
-
"logTime": "i0op5y_logTime",
|
|
429
|
-
"logLabel": "i0op5y_logLabel",
|
|
430
|
-
"logStatus": "i0op5y_logStatus",
|
|
431
|
-
"logDuration": "i0op5y_logDuration",
|
|
432
|
-
"logSize": "i0op5y_logSize",
|
|
433
|
-
"logExpand": "i0op5y_logExpand",
|
|
434
|
-
"logDetail": "i0op5y_logDetail",
|
|
435
|
-
"logError": "i0op5y_logError",
|
|
436
|
-
"logDetailBlock": "i0op5y_logDetailBlock",
|
|
437
|
-
"logDetailLabel": "i0op5y_logDetailLabel",
|
|
438
|
-
"logDetailPre": "i0op5y_logDetailPre",
|
|
439
|
-
"logFilePath": "i0op5y_logFilePath",
|
|
440
|
-
"logLocateButton": "i0op5y_logLocateButton",
|
|
441
|
-
"nodeBoxMultiSelected": "i0op5y_nodeBoxMultiSelected",
|
|
442
|
-
"multiSelectBar": "i0op5y_multiSelectBar",
|
|
443
|
-
"multiSelectCount": "i0op5y_multiSelectCount",
|
|
444
|
-
"multiSelectCompareButton": "i0op5y_multiSelectCompareButton",
|
|
445
|
-
"multiSelectClearButton": "i0op5y_multiSelectClearButton",
|
|
446
|
-
"compareOverlay": "i0op5y_compareOverlay",
|
|
447
|
-
"compareHeader": "i0op5y_compareHeader",
|
|
448
|
-
"compareTitle": "i0op5y_compareTitle",
|
|
449
|
-
"compareCloseButton": "i0op5y_compareCloseButton",
|
|
450
|
-
"compareGrid": "i0op5y_compareGrid",
|
|
451
|
-
"compareCard": "i0op5y_compareCard",
|
|
452
|
-
"compareCardMedia": "i0op5y_compareCardMedia",
|
|
453
|
-
"compareMediaImage": "i0op5y_compareMediaImage",
|
|
454
|
-
"compareMediaVideo": "i0op5y_compareMediaVideo",
|
|
455
|
-
"compareMediaAudioWrap": "i0op5y_compareMediaAudioWrap",
|
|
456
|
-
"compareMediaAudio": "i0op5y_compareMediaAudio",
|
|
457
|
-
"compareMediaEmpty": "i0op5y_compareMediaEmpty",
|
|
458
|
-
"compareCardBody": "i0op5y_compareCardBody",
|
|
459
|
-
"comparePrompt": "i0op5y_comparePrompt",
|
|
460
|
-
"comparePromptEmpty": "i0op5y_comparePromptEmpty",
|
|
461
|
-
"compareMetaRow": "i0op5y_compareMetaRow",
|
|
462
|
-
"compareMetaLabel": "i0op5y_compareMetaLabel",
|
|
463
|
-
"compareMetaValue": "i0op5y_compareMetaValue",
|
|
464
|
-
"compareCardFooter": "i0op5y_compareCardFooter",
|
|
465
|
-
"compareFooter": "i0op5y_compareFooter",
|
|
466
|
-
"compareButtonPrimary": "i0op5y_compareButtonPrimary",
|
|
467
|
-
"compareButtonSecondary": "i0op5y_compareButtonSecondary",
|
|
468
|
-
"compareButtonDanger": "i0op5y_compareButtonDanger"
|
|
300
|
+
"uploadOverlay": "i0op5y_uploadOverlay"
|
|
469
301
|
};
|
|
470
302
|
//#endregion
|
|
471
303
|
//#region src/client/CanvasNode.tsx
|
|
@@ -505,7 +337,7 @@ window.__ModuleLoader__.load({
|
|
|
505
337
|
}
|
|
506
338
|
}
|
|
507
339
|
/** Format the createdAt timestamp as a short HH:MM:SS. */
|
|
508
|
-
function formatTime
|
|
340
|
+
function formatTime(ms) {
|
|
509
341
|
const d = new Date(ms);
|
|
510
342
|
const pad = (n) => n < 10 ? `0${n}` : String(n);
|
|
511
343
|
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
@@ -572,405 +404,16 @@ window.__ModuleLoader__.load({
|
|
|
572
404
|
/** One canvas node (fixed-width card; height follows content). */
|
|
573
405
|
function CanvasNode({ element, t }) {
|
|
574
406
|
const kindDotClass = `${canvas_module_css_default.kindDot} ${canvas_module_css_default[`kindDot_${element.kind}`] ?? ""}`;
|
|
575
|
-
const statusClass = element.status !== void 0 && element.status !== "ready" ? ` ${canvas_module_css_default[`nodeStatus_${element.status}`] ?? ""}` : "";
|
|
576
|
-
const winnerBadge = element.winner === true;
|
|
577
407
|
return (0, react.createElement)("div", {
|
|
578
|
-
className:
|
|
408
|
+
className: canvas_module_css_default.node,
|
|
579
409
|
"data-uuid": element.uuid ?? "",
|
|
580
410
|
"data-filepath": element.filePath
|
|
581
411
|
}, (0, react.createElement)("div", { className: canvas_module_css_default.nodeHeader }, (0, react.createElement)("span", {
|
|
582
412
|
className: kindDotClass,
|
|
583
413
|
"aria-hidden": true
|
|
584
|
-
}), (0, react.createElement)("span", { className: canvas_module_css_default.kindLabel }, kindLabel(element.kind, t)),
|
|
585
|
-
className: canvas_module_css_default.winnerBadge,
|
|
586
|
-
title: t("winner")
|
|
587
|
-
}, "★") : null, (0, react.createElement)("span", { className: canvas_module_css_default.nodeTime }, formatTime$1(element.createdAt))), (0, react.createElement)("div", { className: canvas_module_css_default.nodeTitle }, element.title), element.description !== void 0 && element.description !== "" ? (0, react.createElement)("div", { className: canvas_module_css_default.nodeDescription }, element.description) : null, (0, react.createElement)("div", { className: canvas_module_css_default.nodeMedia }, renderMedia(element)));
|
|
588
|
-
}
|
|
589
|
-
//#endregion
|
|
590
|
-
//#region src/client/RequestLogPanel.tsx
|
|
591
|
-
/**
|
|
592
|
-
* Floating request log panel: shows every aigc_http_request + aigc_media_edit
|
|
593
|
-
* call (success or failure) so the user can debug failed generations from the
|
|
594
|
-
* canvas UI. Per docs/product/04-ux-reliability.md §3.
|
|
595
|
-
*
|
|
596
|
-
* Features:
|
|
597
|
-
* - Toggle button in the canvas header (shows the entry count badge)
|
|
598
|
-
* - Floating panel (right side, like the detail panel) with the entry list
|
|
599
|
-
* - Each entry: timestamp + type icon + provider/path + status + duration + size
|
|
600
|
-
* - Click an entry to expand details (request headers/body + response preview;
|
|
601
|
-
* apiKey is already redacted on the host side)
|
|
602
|
-
* - "Clear" button wipes the session log
|
|
603
|
-
* - Failed entries (status >= 400) are highlighted in red
|
|
604
|
-
* - "Locate on canvas" button pans to the element produced by the request
|
|
605
|
-
* (when elementPath is set) — wired through a callback prop
|
|
606
|
-
*
|
|
607
|
-
* The panel polls /aigc-canvas/api/logs.list every 2s when open (lightweight;
|
|
608
|
-
* avoids WS protocol changes). The host caps at 200 entries per session.
|
|
609
|
-
*/
|
|
610
|
-
/** Format a timestamp as HH:MM:SS.mmm. */
|
|
611
|
-
function formatTime(ms) {
|
|
612
|
-
const d = new Date(ms);
|
|
613
|
-
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}.${String(d.getMilliseconds()).padStart(3, "0")}`;
|
|
614
|
-
}
|
|
615
|
-
/** Format a byte size human-readably. */
|
|
616
|
-
function formatSize(bytes) {
|
|
617
|
-
if (bytes === void 0) return "-";
|
|
618
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
619
|
-
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
620
|
-
return `${(bytes / 1048576).toFixed(1)}MB`;
|
|
621
|
-
}
|
|
622
|
-
/** Format a duration in ms. */
|
|
623
|
-
function formatDuration(ms) {
|
|
624
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
625
|
-
return `${(ms / 1e3).toFixed(1)}s`;
|
|
626
|
-
}
|
|
627
|
-
/** One collapsible log entry row. */
|
|
628
|
-
var LogEntryRow = class extends react.Component {
|
|
629
|
-
state = { expanded: false };
|
|
630
|
-
render() {
|
|
631
|
-
const { entry, t, locateElement } = this.props;
|
|
632
|
-
const failed = entry.status >= 400 || entry.error !== void 0;
|
|
633
|
-
const label = entry.type === "http" ? `${entry.method ?? "?"} ${entry.path ?? "?"}` : `ffmpeg: ${entry.operation ?? "?"}`;
|
|
634
|
-
const provider = entry.providerId !== void 0 ? ` ${entry.providerId}` : "";
|
|
635
|
-
return (0, react.createElement)("div", { className: canvas_module_css_default.logRow }, (0, react.createElement)("button", {
|
|
636
|
-
type: "button",
|
|
637
|
-
className: `${canvas_module_css_default.logRowHeader} ${failed ? canvas_module_css_default.logRowFailed : ""}`,
|
|
638
|
-
onClick: () => this.setState({ expanded: !this.state.expanded })
|
|
639
|
-
}, (0, react.createElement)("span", { className: canvas_module_css_default.logTime }, formatTime(entry.timestamp)), (0, react.createElement)("span", { className: canvas_module_css_default.logLabel }, `${label}${provider}`), (0, react.createElement)("span", { className: canvas_module_css_default.logStatus }, String(entry.status)), (0, react.createElement)("span", { className: canvas_module_css_default.logDuration }, formatDuration(entry.durationMs)), (0, react.createElement)("span", { className: canvas_module_css_default.logSize }, formatSize(entry.size)), (0, react.createElement)("span", { className: canvas_module_css_default.logExpand }, this.state.expanded ? "▼" : "▶")), this.state.expanded && (0, react.createElement)("div", { className: canvas_module_css_default.logDetail }, entry.error !== void 0 && (0, react.createElement)("div", { className: canvas_module_css_default.logError }, `${t("logError")}: ${entry.error}`), entry.requestBodyPreview !== void 0 && (0, react.createElement)("div", { className: canvas_module_css_default.logDetailBlock }, (0, react.createElement)("span", { className: canvas_module_css_default.logDetailLabel }, t("logRequestBody")), (0, react.createElement)("pre", { className: canvas_module_css_default.logDetailPre }, entry.requestBodyPreview)), entry.requestHeaders !== void 0 && (0, react.createElement)("div", { className: canvas_module_css_default.logDetailBlock }, (0, react.createElement)("span", { className: canvas_module_css_default.logDetailLabel }, t("logRequestHeaders")), (0, react.createElement)("pre", { className: canvas_module_css_default.logDetailPre }, JSON.stringify(entry.requestHeaders, null, 2))), entry.responseBodyPreview !== void 0 && (0, react.createElement)("div", { className: canvas_module_css_default.logDetailBlock }, (0, react.createElement)("span", { className: canvas_module_css_default.logDetailLabel }, t("logResponseBody")), (0, react.createElement)("pre", { className: canvas_module_css_default.logDetailPre }, entry.responseBodyPreview)), entry.elementPath !== void 0 && (0, react.createElement)("div", { className: canvas_module_css_default.logDetailBlock }, (0, react.createElement)("span", { className: canvas_module_css_default.logDetailLabel }, t("logProducedFile")), (0, react.createElement)("code", { className: canvas_module_css_default.logFilePath }, entry.elementPath), (0, react.createElement)("button", {
|
|
640
|
-
type: "button",
|
|
641
|
-
className: canvas_module_css_default.logLocateButton,
|
|
642
|
-
onClick: () => locateElement(entry.elementPath)
|
|
643
|
-
}, t("logLocate")))));
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
/** Error boundary so a render failure in the panel doesn't blank the canvas. */
|
|
647
|
-
var LogBoundary = class extends react.Component {
|
|
648
|
-
state = { error: null };
|
|
649
|
-
static getDerivedStateFromError(error) {
|
|
650
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
651
|
-
}
|
|
652
|
-
componentDidCatch(error, info) {
|
|
653
|
-
console.error("[dsh-aigc-canvas] log panel error:", error, info.componentStack);
|
|
654
|
-
}
|
|
655
|
-
render() {
|
|
656
|
-
if (this.state.error !== null) return (0, react.createElement)("div", { className: canvas_module_css_default.boundaryError }, `log panel: ${this.state.error}`);
|
|
657
|
-
return this.props.children;
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
/**
|
|
661
|
-
* The request log panel. Renders as a floating panel on the right side of
|
|
662
|
-
* the canvas. Polls the host every 2s for new entries while open.
|
|
663
|
-
*/
|
|
664
|
-
function RequestLogPanel({ sessionId, t, locateElement }) {
|
|
665
|
-
const [entries, setEntries] = (0, react.useState)([]);
|
|
666
|
-
const [loading, setLoading] = (0, react.useState)(false);
|
|
667
|
-
const [error, setError] = (0, react.useState)(null);
|
|
668
|
-
(0, react.useEffect)(() => {
|
|
669
|
-
let cancelled = false;
|
|
670
|
-
const poll = async () => {
|
|
671
|
-
try {
|
|
672
|
-
const result = await fetchRequestLog(sessionId);
|
|
673
|
-
if (!cancelled) {
|
|
674
|
-
setEntries([...result.entries]);
|
|
675
|
-
setError(null);
|
|
676
|
-
}
|
|
677
|
-
} catch (e) {
|
|
678
|
-
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
|
679
|
-
} finally {
|
|
680
|
-
if (!cancelled) setLoading(false);
|
|
681
|
-
}
|
|
682
|
-
};
|
|
683
|
-
poll();
|
|
684
|
-
const timer = setInterval(poll, 2e3);
|
|
685
|
-
return () => {
|
|
686
|
-
cancelled = true;
|
|
687
|
-
clearInterval(timer);
|
|
688
|
-
};
|
|
689
|
-
}, [sessionId]);
|
|
690
|
-
const onClear = async () => {
|
|
691
|
-
try {
|
|
692
|
-
await clearRequestLog(sessionId);
|
|
693
|
-
setEntries([]);
|
|
694
|
-
} catch (e) {
|
|
695
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
return (0, react.createElement)(LogBoundary, null, (0, react.createElement)("div", { className: canvas_module_css_default.logPanel }, (0, react.createElement)("div", { className: canvas_module_css_default.logPanelHeader }, (0, react.createElement)("span", { className: canvas_module_css_default.logPanelTitle }, `${t("logTitle")} (${entries.length})`), (0, react.createElement)("button", {
|
|
699
|
-
type: "button",
|
|
700
|
-
className: canvas_module_css_default.logPanelClear,
|
|
701
|
-
onClick: () => {
|
|
702
|
-
onClear();
|
|
703
|
-
},
|
|
704
|
-
disabled: entries.length === 0
|
|
705
|
-
}, t("logClear"))), error !== null && (0, react.createElement)("div", { className: canvas_module_css_default.boundaryError }, `${t("logError")}: ${error}`), loading && entries.length === 0 ? (0, react.createElement)("div", { className: canvas_module_css_default.empty }, t("logLoading")) : entries.length === 0 ? (0, react.createElement)("div", { className: canvas_module_css_default.empty }, t("logEmpty")) : (0, react.createElement)("div", { className: canvas_module_css_default.logList }, ...[...entries].reverse().map((entry) => (0, react.createElement)(LogEntryRow, {
|
|
706
|
-
key: entry.id,
|
|
707
|
-
entry,
|
|
708
|
-
t,
|
|
709
|
-
locateElement
|
|
710
|
-
})))));
|
|
711
|
-
}
|
|
712
|
-
//#endregion
|
|
713
|
-
//#region src/client/compare-helpers.ts
|
|
714
|
-
/**
|
|
715
|
-
* Read a numeric field from a free-form meta object, tolerating:
|
|
716
|
-
* - missing key
|
|
717
|
-
* - non-numeric value (string-encoded numbers are accepted; everything
|
|
718
|
-
* else returns undefined)
|
|
719
|
-
* - `null` / `undefined`
|
|
720
|
-
*
|
|
721
|
-
* Strings are accepted because the agent sometimes writes `"seed": "42"`
|
|
722
|
-
* (JSON-object values sneak through as strings when the model wraps them
|
|
723
|
-
* in quotes).
|
|
724
|
-
*/
|
|
725
|
-
function readNumber(meta, key) {
|
|
726
|
-
if (meta === void 0) return void 0;
|
|
727
|
-
const v = meta[key];
|
|
728
|
-
if (v === null || v === void 0) return void 0;
|
|
729
|
-
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
730
|
-
if (typeof v === "string") {
|
|
731
|
-
const n = Number(v);
|
|
732
|
-
if (Number.isFinite(n) && v.trim() !== "") return n;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Extract the generation seed from an element's meta.
|
|
737
|
-
*
|
|
738
|
-
* Looks at `seed` (the canonical key) and falls back to `random_seed`
|
|
739
|
-
* (some providers use that name). Returns undefined when neither is a
|
|
740
|
-
* finite number.
|
|
741
|
-
*/
|
|
742
|
-
function getElementSeed(el) {
|
|
743
|
-
const m = el.meta;
|
|
744
|
-
if (m === void 0) return void 0;
|
|
745
|
-
return readNumber(m, "seed") ?? readNumber(m, "random_seed");
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
|
-
* Extract the USD cost of producing one element from its meta.
|
|
749
|
-
*
|
|
750
|
-
* Looks at `cost`, then `costUsd`. Returns undefined when neither is a
|
|
751
|
-
* finite number (the agent doesn't always set this — the host's
|
|
752
|
-
* cost-tracker is the source of truth, but it isn't yet written back
|
|
753
|
-
* into meta on placement).
|
|
754
|
-
*/
|
|
755
|
-
function getElementCost(el) {
|
|
756
|
-
const m = el.meta;
|
|
757
|
-
if (m === void 0) return void 0;
|
|
758
|
-
return readNumber(m, "cost") ?? readNumber(m, "costUsd");
|
|
759
|
-
}
|
|
760
|
-
/**
|
|
761
|
-
* Extract the wall-clock duration (in ms) of producing one element.
|
|
762
|
-
*
|
|
763
|
-
* Looks at `durationMs` (host convention) and falls back to
|
|
764
|
-
* `durationSeconds` × 1000 (some agents write the duration in seconds).
|
|
765
|
-
* Returns undefined when neither is set.
|
|
766
|
-
*/
|
|
767
|
-
function getElementDurationMs(el) {
|
|
768
|
-
const m = el.meta;
|
|
769
|
-
if (m === void 0) return void 0;
|
|
770
|
-
const ms = readNumber(m, "durationMs");
|
|
771
|
-
if (ms !== void 0) return ms;
|
|
772
|
-
const seconds = readNumber(m, "durationSeconds");
|
|
773
|
-
if (seconds !== void 0) return seconds * 1e3;
|
|
774
|
-
const raw = readNumber(m, "duration");
|
|
775
|
-
if (raw === void 0) return void 0;
|
|
776
|
-
if (el.kind === "video" || el.kind === "audio") return raw * 1e3;
|
|
777
|
-
return raw;
|
|
778
|
-
}
|
|
779
|
-
/** Format a USD cost as a short `$X.XX` string, or "—" when unknown. */
|
|
780
|
-
function formatCost(usd) {
|
|
781
|
-
if (usd === void 0) return "—";
|
|
782
|
-
if (usd === 0) return "$0.00";
|
|
783
|
-
if (usd > 0 && usd < .01) return `$${usd.toFixed(4)}`;
|
|
784
|
-
return `$${usd.toFixed(2)}`;
|
|
785
|
-
}
|
|
786
|
-
/** Format a duration in ms as `X.Xs` (or `XXXms` when < 1s). Returns "—" when undefined. */
|
|
787
|
-
function formatDurationShort(ms) {
|
|
788
|
-
if (ms === void 0) return "—";
|
|
789
|
-
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
790
|
-
return `${(ms / 1e3).toFixed(1)}s`;
|
|
791
|
-
}
|
|
792
|
-
/** Format a seed value (integer) as a string, or "—" when undefined. */
|
|
793
|
-
function formatSeed(seed) {
|
|
794
|
-
if (seed === void 0) return "—";
|
|
795
|
-
return String(seed);
|
|
414
|
+
}), (0, react.createElement)("span", { className: canvas_module_css_default.kindLabel }, kindLabel(element.kind, t)), (0, react.createElement)("span", { className: canvas_module_css_default.nodeTime }, formatTime(element.createdAt))), (0, react.createElement)("div", { className: canvas_module_css_default.nodeTitle }, element.title), element.description !== void 0 && element.description !== "" ? (0, react.createElement)("div", { className: canvas_module_css_default.nodeDescription }, element.description) : null, (0, react.createElement)("div", { className: canvas_module_css_default.nodeMedia }, renderMedia(element)));
|
|
796
415
|
}
|
|
797
416
|
//#endregion
|
|
798
|
-
//#region src/client/CompareView.tsx
|
|
799
|
-
/**
|
|
800
|
-
* CompareView — floating overlay that lets the user compare 2-4 selected
|
|
801
|
-
* canvas elements side by side and pick a winner.
|
|
802
|
-
*
|
|
803
|
-
* Per docs/product/04-ux-reliability.md §2:
|
|
804
|
-
* - All selected elements shown at the same scale, side by side
|
|
805
|
-
* - Each element shows: image/video, prompt, seed, cost, duration (from meta)
|
|
806
|
-
* - "Select as winner" button under each → calls setElementStatus(uuid,
|
|
807
|
-
* 'ready', true) + archives the others
|
|
808
|
-
* - "Reject all" button → calls setElementStatus(uuid, 'rejected') for all
|
|
809
|
-
* - "Close" button → unmounts the overlay (handled by parent)
|
|
810
|
-
*
|
|
811
|
-
* The overlay is rendered as a fixed-position layer above the canvas
|
|
812
|
-
* surface (z-index above the detail panel + log panel + toolbar). It
|
|
813
|
-
* does NOT intercept canvas pointer events outside its own bounds.
|
|
814
|
-
*
|
|
815
|
-
* @module @huanlin/dsh-plugin-aigc-canvas/client/CompareView
|
|
816
|
-
*/
|
|
817
|
-
/**
|
|
818
|
-
* The compare view overlay. Renders as a fixed-position layer above the
|
|
819
|
-
* canvas. Winner/reject actions fire `setElementStatus` calls; the WS
|
|
820
|
-
* push carries the authoritative state back into the canvas snapshot.
|
|
821
|
-
*/
|
|
822
|
-
function CompareView({ sessionId, elements, t, onClose }) {
|
|
823
|
-
return (0, react.createElement)(CompareBoundary, {
|
|
824
|
-
t,
|
|
825
|
-
children: (0, react.createElement)(CompareViewInner, {
|
|
826
|
-
sessionId,
|
|
827
|
-
elements,
|
|
828
|
-
t,
|
|
829
|
-
onClose
|
|
830
|
-
})
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
/** Inner component (wrapped by the boundary so a render error doesn't blank the canvas). */
|
|
834
|
-
function CompareViewInner({ sessionId, elements, t, onClose }) {
|
|
835
|
-
const [pendingWinner, setPendingWinner] = (0, react.useState)(void 0);
|
|
836
|
-
const [pendingReject, setPendingReject] = (0, react.useState)(false);
|
|
837
|
-
const [error, setError] = (0, react.useState)(null);
|
|
838
|
-
(0, react.useEffect)(() => {
|
|
839
|
-
const onKey = (e) => {
|
|
840
|
-
if (e.key === "Escape") onClose();
|
|
841
|
-
};
|
|
842
|
-
window.addEventListener("keydown", onKey);
|
|
843
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
844
|
-
}, [onClose]);
|
|
845
|
-
/**
|
|
846
|
-
* Pick one element as the winner: keep it as `ready` with the winner
|
|
847
|
-
* flag, archive the others. Per doc 04 §2: "选 winner 后其他自动归档
|
|
848
|
-
* (status = archived)".
|
|
849
|
-
*/
|
|
850
|
-
const onPickWinner = async (winner) => {
|
|
851
|
-
if (winner.uuid === void 0) return;
|
|
852
|
-
const winnerUuid = winner.uuid;
|
|
853
|
-
const losers = elements.filter((e) => e.uuid !== void 0 && e.uuid !== winnerUuid);
|
|
854
|
-
setPendingWinner(winnerUuid);
|
|
855
|
-
setError(null);
|
|
856
|
-
try {
|
|
857
|
-
await setElementStatus(sessionId, winnerUuid, "ready", true);
|
|
858
|
-
await Promise.all(losers.map((e) => setElementStatus(sessionId, e.uuid, "archived")));
|
|
859
|
-
} catch (e) {
|
|
860
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
861
|
-
} finally {
|
|
862
|
-
setPendingWinner(void 0);
|
|
863
|
-
}
|
|
864
|
-
};
|
|
865
|
-
/**
|
|
866
|
-
* Reject all: mark every selected element as `rejected`. Per doc 04 §2:
|
|
867
|
-
* "全部否决 → 所有标 rejected".
|
|
868
|
-
*/
|
|
869
|
-
const onRejectAll = async () => {
|
|
870
|
-
setPendingReject(true);
|
|
871
|
-
setError(null);
|
|
872
|
-
try {
|
|
873
|
-
await Promise.all(elements.map((e) => e.uuid !== void 0 ? setElementStatus(sessionId, e.uuid, "rejected") : Promise.resolve()));
|
|
874
|
-
} catch (e) {
|
|
875
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
876
|
-
} finally {
|
|
877
|
-
setPendingReject(false);
|
|
878
|
-
}
|
|
879
|
-
};
|
|
880
|
-
const count = elements.length;
|
|
881
|
-
return (0, react.createElement)("div", {
|
|
882
|
-
className: canvas_module_css_default.compareOverlay,
|
|
883
|
-
onWheel: (e) => e.stopPropagation(),
|
|
884
|
-
onPointerDown: (e) => e.stopPropagation()
|
|
885
|
-
}, (0, react.createElement)("div", { className: canvas_module_css_default.compareHeader }, (0, react.createElement)("span", { className: canvas_module_css_default.compareTitle }, `${t("compareTitle")} (${count})`), (0, react.createElement)("button", {
|
|
886
|
-
type: "button",
|
|
887
|
-
className: canvas_module_css_default.compareCloseButton,
|
|
888
|
-
onClick: onClose,
|
|
889
|
-
"aria-label": t("compareClose")
|
|
890
|
-
}, "×")), error !== null && (0, react.createElement)("div", { className: canvas_module_css_default.boundaryError }, error), (0, react.createElement)("div", { className: canvas_module_css_default.compareGrid }, ...elements.map((el) => (0, react.createElement)(CompareCard, {
|
|
891
|
-
key: el.uuid ?? el.filePath,
|
|
892
|
-
element: el,
|
|
893
|
-
sessionId,
|
|
894
|
-
t,
|
|
895
|
-
pending: pendingWinner === el.uuid || pendingReject,
|
|
896
|
-
onPickWinner: () => {
|
|
897
|
-
onPickWinner(el);
|
|
898
|
-
}
|
|
899
|
-
}))), (0, react.createElement)("div", { className: canvas_module_css_default.compareFooter }, (0, react.createElement)("button", {
|
|
900
|
-
type: "button",
|
|
901
|
-
className: `${canvas_module_css_default.compareButtonDanger}`,
|
|
902
|
-
onClick: () => {
|
|
903
|
-
onRejectAll();
|
|
904
|
-
},
|
|
905
|
-
disabled: pendingReject || pendingWinner !== void 0
|
|
906
|
-
}, t("compareRejectAll")), (0, react.createElement)("button", {
|
|
907
|
-
type: "button",
|
|
908
|
-
className: canvas_module_css_default.compareButtonSecondary,
|
|
909
|
-
onClick: onClose
|
|
910
|
-
}, t("compareClose"))));
|
|
911
|
-
}
|
|
912
|
-
/** One element card in the compare grid. */
|
|
913
|
-
function CompareCard(props) {
|
|
914
|
-
const { element: el, sessionId, t, pending, onPickWinner } = props;
|
|
915
|
-
const seed = getElementSeed(el);
|
|
916
|
-
const cost = getElementCost(el);
|
|
917
|
-
const durationMs = getElementDurationMs(el);
|
|
918
|
-
return (0, react.createElement)("div", { className: canvas_module_css_default.compareCard }, (0, react.createElement)("div", { className: canvas_module_css_default.compareCardMedia }, (0, react.createElement)(CompareMedia, {
|
|
919
|
-
element: el,
|
|
920
|
-
sessionId,
|
|
921
|
-
t
|
|
922
|
-
})), (0, react.createElement)("div", { className: canvas_module_css_default.compareCardBody }, el.promptText !== void 0 && el.promptText !== "" ? (0, react.createElement)("pre", { className: canvas_module_css_default.comparePrompt }, el.promptText) : (0, react.createElement)("div", { className: canvas_module_css_default.comparePromptEmpty }, t("comparePrompt")), (0, react.createElement)("div", { className: canvas_module_css_default.compareMetaRow }, (0, react.createElement)("span", { className: canvas_module_css_default.compareMetaLabel }, `${t("compareSeed")}:`), (0, react.createElement)("span", { className: canvas_module_css_default.compareMetaValue }, formatSeed(seed))), (0, react.createElement)("div", { className: canvas_module_css_default.compareMetaRow }, (0, react.createElement)("span", { className: canvas_module_css_default.compareMetaLabel }, `${t("compareCost")}:`), (0, react.createElement)("span", { className: canvas_module_css_default.compareMetaValue }, formatCost(cost))), (0, react.createElement)("div", { className: canvas_module_css_default.compareMetaRow }, (0, react.createElement)("span", { className: canvas_module_css_default.compareMetaLabel }, `${t("compareDuration")}:`), (0, react.createElement)("span", { className: canvas_module_css_default.compareMetaValue }, formatDurationShort(durationMs)))), (0, react.createElement)("div", { className: canvas_module_css_default.compareCardFooter }, (0, react.createElement)("button", {
|
|
923
|
-
type: "button",
|
|
924
|
-
className: canvas_module_css_default.compareButtonPrimary,
|
|
925
|
-
onClick: onPickWinner,
|
|
926
|
-
disabled: pending || el.uuid === void 0,
|
|
927
|
-
title: el.title
|
|
928
|
-
}, t("compareSelectWinner"))));
|
|
929
|
-
}
|
|
930
|
-
/** Renders the element's media (image / video / audio) at a fixed target size. */
|
|
931
|
-
function CompareMedia(props) {
|
|
932
|
-
const { element: el, sessionId, t } = props;
|
|
933
|
-
if (el.kind === "prompt") return (0, react.createElement)("div", { className: canvas_module_css_default.compareMediaEmpty }, el.title);
|
|
934
|
-
if (el.uuid === void 0) return (0, react.createElement)("div", { className: canvas_module_css_default.compareMediaEmpty }, t("compareNoMedia"));
|
|
935
|
-
const url = mediaUrlOf(sessionId, el.uuid);
|
|
936
|
-
if (el.kind === "image") return (0, react.createElement)("img", {
|
|
937
|
-
className: canvas_module_css_default.compareMediaImage,
|
|
938
|
-
src: url,
|
|
939
|
-
alt: el.title,
|
|
940
|
-
loading: "lazy",
|
|
941
|
-
draggable: false
|
|
942
|
-
});
|
|
943
|
-
if (el.kind === "video") return (0, react.createElement)("video", {
|
|
944
|
-
className: canvas_module_css_default.compareMediaVideo,
|
|
945
|
-
src: url,
|
|
946
|
-
controls: true,
|
|
947
|
-
preload: "metadata"
|
|
948
|
-
});
|
|
949
|
-
return (0, react.createElement)("div", { className: canvas_module_css_default.compareMediaAudioWrap }, (0, react.createElement)("audio", {
|
|
950
|
-
className: canvas_module_css_default.compareMediaAudio,
|
|
951
|
-
src: url,
|
|
952
|
-
controls: true,
|
|
953
|
-
preload: "metadata"
|
|
954
|
-
}));
|
|
955
|
-
}
|
|
956
|
-
/** Error boundary so a render failure shows a strip instead of blanking the overlay. */
|
|
957
|
-
var CompareBoundary = class extends react.Component {
|
|
958
|
-
state = { error: null };
|
|
959
|
-
static getDerivedStateFromError(error) {
|
|
960
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
961
|
-
}
|
|
962
|
-
componentDidCatch(error, info) {
|
|
963
|
-
console.error("[dsh-aigc-canvas] compare view error:", error, info.componentStack);
|
|
964
|
-
}
|
|
965
|
-
render() {
|
|
966
|
-
if (this.state.error !== null) return (0, react.createElement)("div", {
|
|
967
|
-
className: canvas_module_css_default.boundaryError,
|
|
968
|
-
style: { margin: "8px" }
|
|
969
|
-
}, `${this.props.t("loadError")}: ${this.state.error}`);
|
|
970
|
-
return this.props.children;
|
|
971
|
-
}
|
|
972
|
-
};
|
|
973
|
-
//#endregion
|
|
974
417
|
//#region src/client/CanvasView.tsx
|
|
975
418
|
/**
|
|
976
419
|
* The infinite canvas view: a free, pannable + zoomable surface where
|
|
@@ -1027,57 +470,14 @@ window.__ModuleLoader__.load({
|
|
|
1027
470
|
}
|
|
1028
471
|
/** Port radius (the small circle drawn at each connection point). */
|
|
1029
472
|
const PORT_R = 5;
|
|
1030
|
-
function lineStyleOf(relation) {
|
|
1031
|
-
switch (relation) {
|
|
1032
|
-
case "reference":
|
|
1033
|
-
case "style":
|
|
1034
|
-
case "mask": return "dashed";
|
|
1035
|
-
case "variation_of":
|
|
1036
|
-
case "remix_of":
|
|
1037
|
-
case "alternative_of": return "dotted";
|
|
1038
|
-
case "edited_from": return "bold";
|
|
1039
|
-
case "input":
|
|
1040
|
-
case "first_frame":
|
|
1041
|
-
case "last_frame":
|
|
1042
|
-
case "audio_track":
|
|
1043
|
-
case void 0:
|
|
1044
|
-
default: return "solid";
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
/** Short human-readable label for one EdgeRelation (rendered at the curve midpoint). */
|
|
1048
|
-
const EDGE_RELATION_LABEL = {
|
|
1049
|
-
input: "",
|
|
1050
|
-
first_frame: "首帧",
|
|
1051
|
-
last_frame: "尾帧",
|
|
1052
|
-
audio_track: "音轨",
|
|
1053
|
-
reference: "参考",
|
|
1054
|
-
style: "风格",
|
|
1055
|
-
mask: "蒙版",
|
|
1056
|
-
variation_of: "变体",
|
|
1057
|
-
remix_of: "再创作",
|
|
1058
|
-
alternative_of: "候选",
|
|
1059
|
-
edited_from: "编辑自"
|
|
1060
|
-
};
|
|
1061
|
-
/** CSS class suffix for one EdgeLineStyle (appended to `edgeLine` / `edgeArrow`). */
|
|
1062
|
-
function edgeLineClass(style) {
|
|
1063
|
-
switch (style) {
|
|
1064
|
-
case "dashed": return canvas_module_css_default.edgeLineDashed ?? "";
|
|
1065
|
-
case "dotted": return canvas_module_css_default.edgeLineDotted ?? "";
|
|
1066
|
-
case "bold": return canvas_module_css_default.edgeLineBold ?? "";
|
|
1067
|
-
default: return canvas_module_css_default.edgeLineSolid ?? "";
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
473
|
/**
|
|
1071
474
|
* One smooth-curve edge: exits the source's right-center port, curves
|
|
1072
475
|
* through two control points, enters the target's left-center port.
|
|
1073
|
-
* Drawn as an SVG cubic-bezier path + two port circles + an arrowhead
|
|
1074
|
-
* with line style + label driven by the edge's `relation`.
|
|
476
|
+
* Drawn as an SVG cubic-bezier path + two port circles + an arrowhead.
|
|
1075
477
|
*
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1078
|
-
*
|
|
1079
|
-
* - dotted: variation_of / remix_of / alternative_of
|
|
1080
|
-
* - bold: edited_from (ffmpeg edit chain)
|
|
478
|
+
* The control points sit on the horizontal axis at a fixed offset from
|
|
479
|
+
* each port so the curve bows out smoothly regardless of distance —
|
|
480
|
+
* looks like a relaxed S when the ports are vertically offset.
|
|
1081
481
|
*
|
|
1082
482
|
* Uses the position resolver so the edge follows live drag positions
|
|
1083
483
|
* (drafts) in real time, not just the persisted snapshot.
|
|
@@ -1092,26 +492,18 @@ window.__ModuleLoader__.load({
|
|
|
1092
492
|
const ty = tgtPos.y + NODE_H / 2;
|
|
1093
493
|
const dx = Math.abs(tx - sx);
|
|
1094
494
|
const offset = Math.max(40, Math.min(dx * .5, 160));
|
|
1095
|
-
const
|
|
1096
|
-
const c1y = sy;
|
|
1097
|
-
const c2x = tx - offset;
|
|
1098
|
-
const c2y = ty;
|
|
1099
|
-
const d = `M ${sx} ${sy} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${tx} ${ty}`;
|
|
495
|
+
const d = `M ${sx} ${sy} C ${sx + offset} ${sy}, ${tx - offset} ${ty}, ${tx} ${ty}`;
|
|
1100
496
|
const arrow = 9;
|
|
1101
497
|
const wing = arrow * .55;
|
|
1102
498
|
const baseX = tx - arrow;
|
|
1103
499
|
const arrowPath = `M ${tx} ${ty} L ${baseX} ${ty - wing} L ${baseX} ${ty + wing} Z`;
|
|
1104
|
-
const lineClass = edgeLineClass(lineStyleOf(edge.relation));
|
|
1105
|
-
const label = edge.relation !== void 0 ? EDGE_RELATION_LABEL[edge.relation] : "";
|
|
1106
|
-
const mx = .125 * sx + .375 * c1x + .375 * c2x + .125 * tx;
|
|
1107
|
-
const my = .125 * sy + .375 * c1y + .375 * c2y + .125 * ty;
|
|
1108
500
|
return (0, react.createElement)("g", { key: `${edge.source}:${edge.target}` }, (0, react.createElement)("path", {
|
|
1109
501
|
d,
|
|
1110
|
-
className:
|
|
502
|
+
className: canvas_module_css_default.edgeLine,
|
|
1111
503
|
fill: "none"
|
|
1112
504
|
}), (0, react.createElement)("path", {
|
|
1113
505
|
d: arrowPath,
|
|
1114
|
-
className:
|
|
506
|
+
className: canvas_module_css_default.edgeArrow
|
|
1115
507
|
}), (0, react.createElement)("circle", {
|
|
1116
508
|
cx: sx,
|
|
1117
509
|
cy: sy,
|
|
@@ -1122,21 +514,7 @@ window.__ModuleLoader__.load({
|
|
|
1122
514
|
cy: ty,
|
|
1123
515
|
r: PORT_R,
|
|
1124
516
|
className: canvas_module_css_default.edgePort
|
|
1125
|
-
})
|
|
1126
|
-
key: "label-bg",
|
|
1127
|
-
x: mx - 24,
|
|
1128
|
-
y: my - 9,
|
|
1129
|
-
width: 48,
|
|
1130
|
-
height: 18,
|
|
1131
|
-
rx: 9,
|
|
1132
|
-
className: canvas_module_css_default.edgeLabelBg
|
|
1133
|
-
}), (0, react.createElement)("text", {
|
|
1134
|
-
key: "label-text",
|
|
1135
|
-
x: mx,
|
|
1136
|
-
y: my + 4,
|
|
1137
|
-
textAnchor: "middle",
|
|
1138
|
-
className: canvas_module_css_default.edgeLabel
|
|
1139
|
-
}, label)] : []);
|
|
517
|
+
}));
|
|
1140
518
|
}
|
|
1141
519
|
/**
|
|
1142
520
|
* The infinite canvas view.
|
|
@@ -1152,59 +530,11 @@ window.__ModuleLoader__.load({
|
|
|
1152
530
|
});
|
|
1153
531
|
const [drafts, setDrafts] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
1154
532
|
const [selected, setSelected] = (0, react.useState)(void 0);
|
|
1155
|
-
/**
|
|
1156
|
-
* Multi-select set for the compare view (per docs/product/04-ux-reliability.md §2).
|
|
1157
|
-
*
|
|
1158
|
-
* Holds 0-4 element uuids. Shift+click on a node toggles its uuid in
|
|
1159
|
-
* this set; click on the empty surface clears it. When the set has
|
|
1160
|
-
* 2-4 entries, the canvas header shows a "Compare" button that opens
|
|
1161
|
-
* the CompareView overlay.
|
|
1162
|
-
*/
|
|
1163
|
-
const [multiSelected, setMultiSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
1164
|
-
/** Whether the CompareView overlay is currently mounted. */
|
|
1165
|
-
const [showCompare, setShowCompare] = (0, react.useState)(false);
|
|
1166
533
|
const [contextMenu, setContextMenu] = (0, react.useState)(void 0);
|
|
1167
534
|
const [dropTarget, setDropTarget] = (0, react.useState)(void 0);
|
|
1168
535
|
const [uploading, setUploading] = (0, react.useState)(false);
|
|
1169
|
-
const [showLog, setShowLog] = (0, react.useState)(false);
|
|
1170
|
-
const [statusFilter, setStatusFilter] = (0, react.useState)(/* @__PURE__ */ new Set(["ready"]));
|
|
1171
|
-
const [sessionCost, setSessionCost] = (0, react.useState)(null);
|
|
1172
536
|
const surfaceRef = (0, react.useRef)(null);
|
|
1173
|
-
(0, react.useEffect)(() => {
|
|
1174
|
-
if (state.sessionId === "") return;
|
|
1175
|
-
let cancelled = false;
|
|
1176
|
-
const poll = async () => {
|
|
1177
|
-
try {
|
|
1178
|
-
const cost = await fetchSessionCost(state.sessionId);
|
|
1179
|
-
if (!cancelled) setSessionCost(cost);
|
|
1180
|
-
} catch {}
|
|
1181
|
-
};
|
|
1182
|
-
poll();
|
|
1183
|
-
const timer = setInterval(poll, 3e3);
|
|
1184
|
-
return () => {
|
|
1185
|
-
cancelled = true;
|
|
1186
|
-
clearInterval(timer);
|
|
1187
|
-
};
|
|
1188
|
-
}, [state.sessionId]);
|
|
1189
537
|
const prevUuidsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
1190
|
-
/** Toggle one status in the filter set (checkbox handler). */
|
|
1191
|
-
const toggleStatus = (status) => {
|
|
1192
|
-
setStatusFilter((prev) => {
|
|
1193
|
-
const next = new Set(prev);
|
|
1194
|
-
if (next.has(status)) next.delete(status);
|
|
1195
|
-
else next.add(status);
|
|
1196
|
-
if (next.size === 0) next.add("ready");
|
|
1197
|
-
return next;
|
|
1198
|
-
});
|
|
1199
|
-
};
|
|
1200
|
-
/** Filtered elements based on the status filter checkboxes. */
|
|
1201
|
-
const filteredElements = state.elements.filter((el) => {
|
|
1202
|
-
const status = el.status ?? "ready";
|
|
1203
|
-
return statusFilter.has(status);
|
|
1204
|
-
});
|
|
1205
|
-
/** Filtered edges: only those whose source AND target are in the filtered set. */
|
|
1206
|
-
const filteredElementPaths = new Set(filteredElements.map((e) => e.filePath));
|
|
1207
|
-
const filteredEdges = state.edges.filter((e) => filteredElementPaths.has(e.source) && filteredElementPaths.has(e.target));
|
|
1208
538
|
const panRef = (0, react.useRef)(null);
|
|
1209
539
|
const dragRef = (0, react.useRef)(null);
|
|
1210
540
|
(0, react.useEffect)(() => {
|
|
@@ -1347,15 +677,6 @@ window.__ModuleLoader__.load({
|
|
|
1347
677
|
const onNodePointerDown = (event, el) => {
|
|
1348
678
|
if (el.uuid === void 0) return;
|
|
1349
679
|
event.stopPropagation();
|
|
1350
|
-
if (event.shiftKey) {
|
|
1351
|
-
setMultiSelected((prev) => {
|
|
1352
|
-
const next = new Set(prev);
|
|
1353
|
-
if (next.has(el.uuid)) next.delete(el.uuid);
|
|
1354
|
-
else if (next.size < 4) next.add(el.uuid);
|
|
1355
|
-
return next;
|
|
1356
|
-
});
|
|
1357
|
-
return;
|
|
1358
|
-
}
|
|
1359
680
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
1360
681
|
dragRef.current = {
|
|
1361
682
|
pointerId: event.pointerId,
|
|
@@ -1381,186 +702,12 @@ window.__ModuleLoader__.load({
|
|
|
1381
702
|
event.stopPropagation();
|
|
1382
703
|
setContextMenu(void 0);
|
|
1383
704
|
}
|
|
1384
|
-
if (multiSelected.size > 0) setMultiSelected(/* @__PURE__ */ new Set());
|
|
1385
705
|
};
|
|
1386
706
|
const onDeleteElement = (uuid) => {
|
|
1387
707
|
setContextMenu(void 0);
|
|
1388
708
|
if (selected?.uuid === uuid) setSelected(void 0);
|
|
1389
709
|
store.deleteElement(uuid);
|
|
1390
710
|
};
|
|
1391
|
-
/**
|
|
1392
|
-
* Resolve the element for one context-menu uuid. The menu is only
|
|
1393
|
-
* shown for elements with a uuid (see onNodeContextMenu), so this
|
|
1394
|
-
* always finds a match — but be defensive in case the WS push
|
|
1395
|
-
* removed the element between the right-click and the action.
|
|
1396
|
-
*/
|
|
1397
|
-
const elementForMenu = (uuid) => {
|
|
1398
|
-
return state.elements.find((e) => e.uuid === uuid);
|
|
1399
|
-
};
|
|
1400
|
-
/**
|
|
1401
|
-
* Send a user-role notice to the agent (non-waking). The host's
|
|
1402
|
-
* `canvas.notify` endpoint wraps `agent.inject` so the client does
|
|
1403
|
-
* not need direct access to the agent registry. Best-effort: a
|
|
1404
|
-
* network failure is swallowed (the next WS push carries the
|
|
1405
|
-
* agent's response, which the user will see in the conversation).
|
|
1406
|
-
*/
|
|
1407
|
-
const sendNotice = (uuid, message, summary) => {
|
|
1408
|
-
setContextMenu(void 0);
|
|
1409
|
-
if (state.sessionId === "") return;
|
|
1410
|
-
notifyAgent(state.sessionId, message, summary).catch(() => {});
|
|
1411
|
-
};
|
|
1412
|
-
/** Replace `{filePath}` / `{kind}` / `{title}` placeholders in a notice template. */
|
|
1413
|
-
const fillTemplate = (template, el) => {
|
|
1414
|
-
return template.replaceAll("{filePath}", el.filePath).replaceAll("{kind}", el.kind).replaceAll("{title}", el.title);
|
|
1415
|
-
};
|
|
1416
|
-
/** 重新生成... → ask the agent to reroll the element with aigc_reroll. */
|
|
1417
|
-
const onRegenerate = (el) => {
|
|
1418
|
-
sendNotice(el.uuid ?? "", fillTemplate(t("noticeRegenerate"), el), `regenerate ${el.kind} "${el.title}"`);
|
|
1419
|
-
};
|
|
1420
|
-
/**
|
|
1421
|
-
* 用作参考... → copy the filePath to the clipboard AND send a notice
|
|
1422
|
-
* to the agent. The doc also describes a relation-picker dialog
|
|
1423
|
-
* (first_frame / last_frame / style / mask / reference); for the
|
|
1424
|
-
* initial implementation we send a generic "use as reference"
|
|
1425
|
-
* notice and let the agent pick the relation based on context.
|
|
1426
|
-
* The clipboard copy is the user-facing half (so the user can also
|
|
1427
|
-
* paste the path into a manual prompt).
|
|
1428
|
-
*/
|
|
1429
|
-
const onUseAsReference = (el) => {
|
|
1430
|
-
setContextMenu(void 0);
|
|
1431
|
-
navigator.clipboard?.writeText(el.filePath).catch(() => {});
|
|
1432
|
-
if (state.sessionId !== "") notifyAgent(state.sessionId, fillTemplate(t("noticeUseAsReference"), el), `use ${el.kind} "${el.title}" as reference`).catch(() => {});
|
|
1433
|
-
};
|
|
1434
|
-
/** 发到对话 → send the element's filePath + kind + title to the agent. */
|
|
1435
|
-
const onSendToChat = (el) => {
|
|
1436
|
-
sendNotice(el.uuid ?? "", fillTemplate(t("noticeSendToChat"), el), `use ${el.kind} "${el.title}" as reference`);
|
|
1437
|
-
};
|
|
1438
|
-
/** 下载 → open the media URL in a new tab with download=1. */
|
|
1439
|
-
const onDownload = (el) => {
|
|
1440
|
-
setContextMenu(void 0);
|
|
1441
|
-
if (el.uuid === void 0 || state.sessionId === "" && el.sessionId === void 0) return;
|
|
1442
|
-
const sid = el.sessionId ?? state.sessionId;
|
|
1443
|
-
if (sid === "") return;
|
|
1444
|
-
const url = mediaUrlOf(sid, el.uuid, true);
|
|
1445
|
-
window.open(url, "_blank", "noopener");
|
|
1446
|
-
};
|
|
1447
|
-
/**
|
|
1448
|
-
* 提升到资产库... → call library.promote. The doc describes a
|
|
1449
|
-
* category-picker dialog; for the initial implementation we
|
|
1450
|
-
* promote with the default `final-product` category and let the
|
|
1451
|
-
* user re-tag from the asset library UI later.
|
|
1452
|
-
*/
|
|
1453
|
-
const onPromoteToLibrary = (el) => {
|
|
1454
|
-
setContextMenu(void 0);
|
|
1455
|
-
if (el.uuid === void 0 || state.sessionId === "") return;
|
|
1456
|
-
promoteAsset(state.sessionId, el.uuid, {
|
|
1457
|
-
category: "final-product",
|
|
1458
|
-
title: el.title
|
|
1459
|
-
}).catch(() => {});
|
|
1460
|
-
};
|
|
1461
|
-
/**
|
|
1462
|
-
* Update an element's lifecycle status via `canvas.set_status`.
|
|
1463
|
-
* Best-effort: the WS push carries the authoritative state, so
|
|
1464
|
-
* we don't optimistically patch the local snapshot.
|
|
1465
|
-
*/
|
|
1466
|
-
const onSetStatus = (uuid, status, winner) => {
|
|
1467
|
-
setContextMenu(void 0);
|
|
1468
|
-
if (state.sessionId === "") return;
|
|
1469
|
-
setElementStatus(state.sessionId, uuid, status, winner).catch(() => {});
|
|
1470
|
-
};
|
|
1471
|
-
/**
|
|
1472
|
-
* + 生成 → ask the agent to generate a new asset (provider info →
|
|
1473
|
-
* http_request → canvas_place). The doc describes a t2i/t2v/tts
|
|
1474
|
-
* picker dialog; for the initial implementation we send a generic
|
|
1475
|
-
* "please generate something" notice and let the agent ask the
|
|
1476
|
-
* user for the specifics (prompt, kind).
|
|
1477
|
-
*/
|
|
1478
|
-
const onToolbarGenerate = () => {
|
|
1479
|
-
if (state.sessionId === "") return;
|
|
1480
|
-
notifyAgent(state.sessionId, t("noticeGenerate"), "user requested generation").catch(() => {});
|
|
1481
|
-
};
|
|
1482
|
-
/**
|
|
1483
|
-
* ✂ 编辑选中 → ask the agent to run aigc_media_edit (ffmpeg) on
|
|
1484
|
-
* the currently-selected element. Disabled when nothing is selected.
|
|
1485
|
-
*/
|
|
1486
|
-
const onToolbarEditSelected = () => {
|
|
1487
|
-
if (state.sessionId === "" || selected === void 0) return;
|
|
1488
|
-
notifyAgent(state.sessionId, fillTemplate(t("noticeEditSelected"), selected), `edit ${selected.kind} "${selected.title}"`).catch(() => {});
|
|
1489
|
-
};
|
|
1490
|
-
/**
|
|
1491
|
-
* ▶ 运行工作流 → ask the agent to list + run a pipeline template.
|
|
1492
|
-
* (Per docs/product/02-pipeline.md; the template picker UI is a
|
|
1493
|
-
* future enhancement — for now the agent lists templates in the
|
|
1494
|
-
* conversation and the user picks one.)
|
|
1495
|
-
*/
|
|
1496
|
-
const onToolbarRunWorkflow = () => {
|
|
1497
|
-
if (state.sessionId === "") return;
|
|
1498
|
-
notifyAgent(state.sessionId, t("noticeRunWorkflow"), "user requested workflow run").catch(() => {});
|
|
1499
|
-
};
|
|
1500
|
-
/**
|
|
1501
|
-
* Build the right-click context menu items for one element uuid.
|
|
1502
|
-
* Per docs/product/04-ux-reliability.md §1: 5 action items, a
|
|
1503
|
-
* separator, 3 status items, a separator, and Delete (in the
|
|
1504
|
-
* destructive style).
|
|
1505
|
-
*
|
|
1506
|
-
* The element is resolved from the current snapshot. If it was
|
|
1507
|
-
* removed between the right-click and the menu render (race with
|
|
1508
|
-
* the WS push), every item except Delete is disabled — Delete is
|
|
1509
|
-
* kept enabled because store.deleteElement is idempotent.
|
|
1510
|
-
*/
|
|
1511
|
-
const buildContextMenuItems = (uuid) => {
|
|
1512
|
-
const el = elementForMenu(uuid);
|
|
1513
|
-
const missing = el === void 0;
|
|
1514
|
-
return [
|
|
1515
|
-
{
|
|
1516
|
-
label: t("menuRegenerate"),
|
|
1517
|
-
disabled: missing,
|
|
1518
|
-
onClick: el !== void 0 ? () => onRegenerate(el) : void 0
|
|
1519
|
-
},
|
|
1520
|
-
{
|
|
1521
|
-
label: t("menuUseAsReference"),
|
|
1522
|
-
disabled: missing,
|
|
1523
|
-
onClick: el !== void 0 ? () => onUseAsReference(el) : void 0
|
|
1524
|
-
},
|
|
1525
|
-
{
|
|
1526
|
-
label: t("menuSendToChat"),
|
|
1527
|
-
disabled: missing,
|
|
1528
|
-
onClick: el !== void 0 ? () => onSendToChat(el) : void 0
|
|
1529
|
-
},
|
|
1530
|
-
{
|
|
1531
|
-
label: t("menuDownload"),
|
|
1532
|
-
disabled: missing || el?.uuid === void 0,
|
|
1533
|
-
onClick: el !== void 0 ? () => onDownload(el) : void 0
|
|
1534
|
-
},
|
|
1535
|
-
{
|
|
1536
|
-
label: t("menuPromoteToLibrary"),
|
|
1537
|
-
disabled: missing || el?.uuid === void 0,
|
|
1538
|
-
onClick: el !== void 0 ? () => onPromoteToLibrary(el) : void 0
|
|
1539
|
-
},
|
|
1540
|
-
{ separator: true },
|
|
1541
|
-
{
|
|
1542
|
-
label: t("menuMarkWinner"),
|
|
1543
|
-
disabled: missing,
|
|
1544
|
-
onClick: el !== void 0 ? () => onSetStatus(uuid, "ready", true) : void 0
|
|
1545
|
-
},
|
|
1546
|
-
{
|
|
1547
|
-
label: t("menuMarkRejected"),
|
|
1548
|
-
disabled: missing,
|
|
1549
|
-
onClick: el !== void 0 ? () => onSetStatus(uuid, "rejected") : void 0
|
|
1550
|
-
},
|
|
1551
|
-
{
|
|
1552
|
-
label: t("menuArchive"),
|
|
1553
|
-
disabled: missing,
|
|
1554
|
-
onClick: el !== void 0 ? () => onSetStatus(uuid, "archived") : void 0
|
|
1555
|
-
},
|
|
1556
|
-
{ separator: true },
|
|
1557
|
-
{
|
|
1558
|
-
label: t("delete"),
|
|
1559
|
-
danger: true,
|
|
1560
|
-
onClick: () => onDeleteElement(uuid)
|
|
1561
|
-
}
|
|
1562
|
-
];
|
|
1563
|
-
};
|
|
1564
711
|
const onSurfaceDragOver = (event) => {
|
|
1565
712
|
if (event.dataTransfer.types.includes("Files")) {
|
|
1566
713
|
event.preventDefault();
|
|
@@ -1634,7 +781,7 @@ window.__ModuleLoader__.load({
|
|
|
1634
781
|
y: el.y
|
|
1635
782
|
};
|
|
1636
783
|
};
|
|
1637
|
-
return (0, react.createElement)("div", { className: canvas_module_css_default.canvas }, (0, react.createElement)("div", { className: canvas_module_css_default.header }, (0, react.createElement)("span", { className: canvas_module_css_default.title }, t("title")), (0, react.createElement)("span", { className: canvas_module_css_default.count }, `${state.elements.length} ${t("elementCount")}`), (0, react.createElement)("span", { className: canvas_module_css_default.count }, `${state.edges.length} ${t("edgeCount")}`),
|
|
784
|
+
return (0, react.createElement)("div", { className: canvas_module_css_default.canvas }, (0, react.createElement)("div", { className: canvas_module_css_default.header }, (0, react.createElement)("span", { className: canvas_module_css_default.title }, t("title")), (0, react.createElement)("span", { className: canvas_module_css_default.count }, `${state.elements.length} ${t("elementCount")}`), (0, react.createElement)("span", { className: canvas_module_css_default.count }, `${state.edges.length} ${t("edgeCount")}`), (0, react.createElement)("span", { className: canvas_module_css_default.zoom }, `${Math.round(viewport.scale * 100)}%`), (0, react.createElement)("button", {
|
|
1638
785
|
type: "button",
|
|
1639
786
|
className: canvas_module_css_default.iconButton,
|
|
1640
787
|
onClick: () => zoomToCenter(viewport.scale * .8),
|
|
@@ -1672,29 +819,7 @@ window.__ModuleLoader__.load({
|
|
|
1672
819
|
}),
|
|
1673
820
|
title: t("resetView"),
|
|
1674
821
|
"aria-label": t("resetView")
|
|
1675
|
-
}, "⤢"), (0, react.createElement)("
|
|
1676
|
-
type: "button",
|
|
1677
|
-
className: `${canvas_module_css_default.iconButton} ${showLog ? canvas_module_css_default.iconButtonActive : ""}`,
|
|
1678
|
-
onClick: () => setShowLog(!showLog),
|
|
1679
|
-
title: t("logButton"),
|
|
1680
|
-
"aria-label": t("logButton")
|
|
1681
|
-
}, "📊"), (0, react.createElement)("span", { className: canvas_module_css_default.statusFilter }, (0, react.createElement)("label", { className: canvas_module_css_default.statusFilterLabel }, (0, react.createElement)("input", {
|
|
1682
|
-
type: "checkbox",
|
|
1683
|
-
checked: statusFilter.has("ready"),
|
|
1684
|
-
onChange: () => toggleStatus("ready")
|
|
1685
|
-
}), t("statusReady")), (0, react.createElement)("label", { className: canvas_module_css_default.statusFilterLabel }, (0, react.createElement)("input", {
|
|
1686
|
-
type: "checkbox",
|
|
1687
|
-
checked: statusFilter.has("draft"),
|
|
1688
|
-
onChange: () => toggleStatus("draft")
|
|
1689
|
-
}), t("statusDraft")), (0, react.createElement)("label", { className: canvas_module_css_default.statusFilterLabel }, (0, react.createElement)("input", {
|
|
1690
|
-
type: "checkbox",
|
|
1691
|
-
checked: statusFilter.has("rejected"),
|
|
1692
|
-
onChange: () => toggleStatus("rejected")
|
|
1693
|
-
}), t("statusRejected")), (0, react.createElement)("label", { className: canvas_module_css_default.statusFilterLabel }, (0, react.createElement)("input", {
|
|
1694
|
-
type: "checkbox",
|
|
1695
|
-
checked: statusFilter.has("archived"),
|
|
1696
|
-
onChange: () => toggleStatus("archived")
|
|
1697
|
-
}), t("statusArchived")))), (0, react.createElement)("div", {
|
|
822
|
+
}, "⤢")), (0, react.createElement)("div", {
|
|
1698
823
|
className: canvas_module_css_default.surface,
|
|
1699
824
|
ref: surfaceRef,
|
|
1700
825
|
onPointerDown: onSurfacePointerDown,
|
|
@@ -1720,12 +845,11 @@ window.__ModuleLoader__.load({
|
|
|
1720
845
|
}, (0, react.createElement)("svg", {
|
|
1721
846
|
className: canvas_module_css_default.edgeLayer,
|
|
1722
847
|
"aria-hidden": true
|
|
1723
|
-
}, ...
|
|
848
|
+
}, ...state.edges.map((edge) => renderEdge(edge, resolvePos))), ...state.elements.map((el) => {
|
|
1724
849
|
const pos = posOf(el);
|
|
1725
|
-
const selectedClass = el.uuid !== void 0 && multiSelected.has(el.uuid) ? ` ${canvas_module_css_default.nodeBoxMultiSelected ?? ""}` : "";
|
|
1726
850
|
return (0, react.createElement)("div", {
|
|
1727
851
|
key: el.uuid ?? el.filePath,
|
|
1728
|
-
className: `${canvas_module_css_default.nodeBox} ${el.uuid !== void 0 ? canvas_module_css_default.nodeBoxDraggable : ""}
|
|
852
|
+
className: `${canvas_module_css_default.nodeBox} ${el.uuid !== void 0 ? canvas_module_css_default.nodeBoxDraggable : ""}`,
|
|
1729
853
|
style: { transform: `translate(${pos.x}px, ${pos.y}px)` },
|
|
1730
854
|
onPointerDown: (event) => onNodePointerDown(event, el),
|
|
1731
855
|
onDoubleClick: (event) => {
|
|
@@ -1744,58 +868,14 @@ window.__ModuleLoader__.load({
|
|
|
1744
868
|
element: selected,
|
|
1745
869
|
t,
|
|
1746
870
|
onClose: () => setSelected(void 0)
|
|
1747
|
-
}) : null, showLog && state.sessionId !== "" ? (0, react.createElement)(RequestLogPanel, {
|
|
1748
|
-
sessionId: state.sessionId,
|
|
1749
|
-
t,
|
|
1750
|
-
locateElement: (filePath) => {
|
|
1751
|
-
const el = state.elements.find((e) => e.filePath === filePath);
|
|
1752
|
-
if (el !== void 0) {
|
|
1753
|
-
const cx = el.x + 120;
|
|
1754
|
-
const cy = el.y + 55;
|
|
1755
|
-
setViewport({
|
|
1756
|
-
x: (surfaceRef.current?.clientWidth ?? 800) / 2 - cx * viewport.scale,
|
|
1757
|
-
y: (surfaceRef.current?.clientHeight ?? 600) / 2 - cy * viewport.scale,
|
|
1758
|
-
scale: viewport.scale
|
|
1759
|
-
});
|
|
1760
|
-
setSelected(el);
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
871
|
}) : null, contextMenu !== void 0 ? (0, react.createElement)(ContextMenu, {
|
|
1764
872
|
x: contextMenu.x,
|
|
1765
873
|
y: contextMenu.y,
|
|
1766
|
-
items:
|
|
874
|
+
items: [{
|
|
875
|
+
label: t("delete"),
|
|
876
|
+
onClick: () => onDeleteElement(contextMenu.uuid)
|
|
877
|
+
}],
|
|
1767
878
|
onClose: () => setContextMenu(void 0)
|
|
1768
|
-
}) : null, state.sessionId !== "" ? (0, react.createElement)("div", { className: canvas_module_css_default.toolbar }, (0, react.createElement)("button", {
|
|
1769
|
-
type: "button",
|
|
1770
|
-
className: canvas_module_css_default.toolbarButton,
|
|
1771
|
-
onClick: onToolbarGenerate,
|
|
1772
|
-
title: t("toolbarGenerateTitle")
|
|
1773
|
-
}, t("toolbarGenerate")), (0, react.createElement)("button", {
|
|
1774
|
-
type: "button",
|
|
1775
|
-
className: canvas_module_css_default.toolbarButton,
|
|
1776
|
-
onClick: onToolbarEditSelected,
|
|
1777
|
-
disabled: selected === void 0,
|
|
1778
|
-
title: selected === void 0 ? t("toolbarNoSelection") : t("toolbarEditSelectedTitle")
|
|
1779
|
-
}, t("toolbarEditSelected")), (0, react.createElement)("button", {
|
|
1780
|
-
type: "button",
|
|
1781
|
-
className: canvas_module_css_default.toolbarButton,
|
|
1782
|
-
onClick: onToolbarRunWorkflow,
|
|
1783
|
-
title: t("toolbarRunWorkflowTitle")
|
|
1784
|
-
}, t("toolbarRunWorkflow"))) : null, multiSelected.size >= 2 ? (0, react.createElement)("div", { className: canvas_module_css_default.multiSelectBar }, (0, react.createElement)("span", { className: canvas_module_css_default.multiSelectCount }, t("compareNSelected").replace("{n}", String(multiSelected.size))), (0, react.createElement)("button", {
|
|
1785
|
-
type: "button",
|
|
1786
|
-
className: canvas_module_css_default.multiSelectCompareButton,
|
|
1787
|
-
onClick: () => setShowCompare(true),
|
|
1788
|
-
disabled: multiSelected.size < 2 || multiSelected.size > 4,
|
|
1789
|
-
title: multiSelected.size > 4 ? t("compareTooMany") : t("compareButton")
|
|
1790
|
-
}, t("compareButton")), (0, react.createElement)("button", {
|
|
1791
|
-
type: "button",
|
|
1792
|
-
className: canvas_module_css_default.multiSelectClearButton,
|
|
1793
|
-
onClick: () => setMultiSelected(/* @__PURE__ */ new Set())
|
|
1794
|
-
}, t("compareClearSelection"))) : null, showCompare && state.sessionId !== "" ? (0, react.createElement)(CompareView, {
|
|
1795
|
-
sessionId: state.sessionId,
|
|
1796
|
-
elements: state.elements.filter((e) => e.uuid !== void 0 && multiSelected.has(e.uuid)),
|
|
1797
|
-
t,
|
|
1798
|
-
onClose: () => setShowCompare(false)
|
|
1799
879
|
}) : null, state.elements.length > 0 ? (0, react.createElement)(Minimap, {
|
|
1800
880
|
elements: state.elements,
|
|
1801
881
|
viewport,
|
|
@@ -1817,34 +897,21 @@ window.__ModuleLoader__.load({
|
|
|
1817
897
|
window.removeEventListener("keydown", onKey);
|
|
1818
898
|
};
|
|
1819
899
|
}, [props]);
|
|
1820
|
-
const style = {
|
|
1821
|
-
left: props.x,
|
|
1822
|
-
top: props.y
|
|
1823
|
-
};
|
|
1824
900
|
return (0, react.createElement)("div", {
|
|
1825
901
|
className: canvas_module_css_default.contextMenu,
|
|
1826
|
-
style
|
|
902
|
+
style: {
|
|
903
|
+
left: props.x,
|
|
904
|
+
top: props.y
|
|
905
|
+
},
|
|
1827
906
|
onPointerDown: (e) => e.stopPropagation()
|
|
1828
|
-
}, ...props.items.map((item, i) => {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
item.disabled === true ? canvas_module_css_default.contextMenuItemDisabled ?? "" : ""
|
|
1837
|
-
].filter((s) => s !== "").join(" ");
|
|
1838
|
-
return (0, react.createElement)("button", {
|
|
1839
|
-
key: i,
|
|
1840
|
-
type: "button",
|
|
1841
|
-
className,
|
|
1842
|
-
disabled: item.disabled === true,
|
|
1843
|
-
onClick: () => {
|
|
1844
|
-
if (item.disabled !== true) item.onClick?.();
|
|
1845
|
-
}
|
|
1846
|
-
}, item.label);
|
|
1847
|
-
}));
|
|
907
|
+
}, ...props.items.map((item, i) => (0, react.createElement)("button", {
|
|
908
|
+
key: i,
|
|
909
|
+
type: "button",
|
|
910
|
+
className: canvas_module_css_default.contextMenuItem,
|
|
911
|
+
onClick: () => {
|
|
912
|
+
item.onClick();
|
|
913
|
+
}
|
|
914
|
+
}, item.label)));
|
|
1848
915
|
}
|
|
1849
916
|
/** The double-click detail panel: prompt + generation params + path. */
|
|
1850
917
|
function DetailPanel({ element, t, onClose }) {
|
|
@@ -1992,11 +1059,11 @@ window.__ModuleLoader__.load({
|
|
|
1992
1059
|
}
|
|
1993
1060
|
//#endregion
|
|
1994
1061
|
//#region \0dsh-css:D:\Projects\deepseek-harness\dsh-aigc-canvas\src\client\SettingsPage.module.css.mjs
|
|
1995
|
-
const css = "/* AIGC canvas settings section, in the settings-panel design language shared\r\n * with ModelsSection / GeneralSection / yet-another-subagent: 14/22 body,\r\n * 12/18 caption, 16/24 title, capsule controls (h36 r18 primary, h28 r14\r\n * secondary), 32px fields, border-l2 hairlines, and the editor as a filled\r\n * module on the panel fill.\r\n *\r\n * Every color resolves through a --dsw-alias-* token (no literal colors). */\r\n\r\n.o6nt8s_section {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 12px;\r\n max-width: 720px;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_title {\r\n margin: 0;\r\n font-size: 16px;\r\n line-height: 24px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_intro {\r\n margin: 0;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.o6nt8s_error {\r\n margin: 0;\r\n padding: 8px 12px;\r\n border: 1px solid var(--dsw-alias-state-error-primary);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-state-error-primary);\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.o6nt8s_errorDismiss {\r\n flex: none;\r\n border: none;\r\n background: transparent;\r\n color: inherit;\r\n font-size: 16px;\r\n line-height: 1;\r\n cursor: pointer;\r\n padding: 0 4px;\r\n}\r\n\r\n.o6nt8s_rows {\r\n list-style: none;\r\n margin: 12px 0 0;\r\n padding: 0;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n}\r\n\r\n/* A configured provider: outlined on the panel fill, matching the rowCard\r\n * chrome in ModelsSection. */\r\n.o6nt8s_rowCard {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 12px;\r\n padding: 12px 14px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 12px;\r\n}\r\n\r\n.o6nt8s_rowHead {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n}\r\n\r\n/* Chevron toggle: a small square with two borders, rotated to point right\r\n * (collapsed) or down (expanded). Pure CSS, no icon font. */\r\n.o6nt8s_chevronButton {\r\n flex: none;\r\n width: 24px;\r\n height: 24px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n border: none;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n padding: 0;\r\n border-radius: 4px;\r\n}\r\n\r\n.o6nt8s_chevronButton:hover {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* Spacer that occupies the chevron slot on cards without a toggle (e.o6nt8s_g. the\r\n * new-draft card, which is always expanded). Keeps row-head alignment. */\r\n.o6nt8s_chevronSpacer {\r\n flex: none;\r\n width: 24px;\r\n height: 24px;\r\n}\r\n\r\n.o6nt8s_chevron {\r\n width: 7px;\r\n height: 7px;\r\n border-right: 1.5px solid currentColor;\r\n border-bottom: 1.5px solid currentColor;\r\n transform: rotate(-45deg);\r\n transition: transform 0.15s ease;\r\n}\r\n\r\n.o6nt8s_chevronExpanded {\r\n transform: rotate(45deg);\r\n}\r\n\r\n.o6nt8s_rowIdentity {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n min-width: 0;\r\n flex: 1 1 auto;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.o6nt8s_rowName {\r\n font-size: 14px;\r\n line-height: 22px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.o6nt8s_rowNamePlaceholder {\r\n font-size: 14px;\r\n line-height: 22px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-tertiary);\r\n font-style: italic;\r\n}\r\n\r\n/* Builtin badge: uses DSH <Pill> with a brand-colored override to mark\r\n * seed providers. */\r\n.o6nt8s_builtinBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-brand-primary);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n letter-spacing: 0.02em;\r\n}\r\n\r\n/* Default provider badge. */\r\n.o6nt8s_defaultBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-bg-layer-2);\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Stub/real mode badge. */\r\n.o6nt8s_stubBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-bg-layer-2);\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.o6nt8s_realBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-state-success-primary);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n}\r\n\r\n.o6nt8s_rowId {\r\n flex: none;\r\n padding: 1px 6px;\r\n border: 1px solid var(--dsw-alias-border-l3);\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-family: var(--dsw-font-markdown-code-block-small, monospace);\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.o6nt8s_rowActions {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 4px;\r\n margin-left: auto;\r\n flex: none;\r\n}\r\n\r\n/* Editor surface: a filled module on the panel, matching ModelsSection's\r\n * editor chrome (bg-module-platform, r12, p14/16). */\r\n.o6nt8s_editor {\r\n border-radius: 12px;\r\n background: var(--dsw-alias-bg-module-platform);\r\n padding: 14px 16px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 14px;\r\n}\r\n\r\n.o6nt8s_field {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n.o6nt8s_fieldLabel {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 10px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Input: matching ModelsSection .o6nt8s_input — h32, r8, border-l2, bg-layer-1. */\r\n.o6nt8s_input {\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_input:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.o6nt8s_input::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n.o6nt8s_input:disabled {\r\n opacity: 0.6;\r\n cursor: default;\r\n}\r\n\r\n.o6nt8s_textarea {\r\n box-sizing: border-box;\r\n width: 100%;\r\n min-height: 64px;\r\n padding: 6px 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n resize: vertical;\r\n}\r\n\r\n.o6nt8s_textarea:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.o6nt8s_textarea::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n/* Auth scheme row: scheme select + optional name input side by side. */\r\n.o6nt8s_authRow {\r\n display: flex;\r\n gap: 8px;\r\n}\r\n\r\n.o6nt8s_authRow .o6nt8s_input {\r\n flex: 1 1 auto;\r\n}\r\n\r\n/* Select: same chrome as .o6nt8s_input. */\r\n.o6nt8s_select {\r\n box-sizing: border-box;\r\n flex: 0 0 auto;\r\n min-width: 130px;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_select:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n/* Caption labels under fields. */\r\n.o6nt8s_desc {\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.o6nt8s_hint {\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Buttons: capsule controls matching ModelsSection (h36 r18 primary,\r\n * h28 r14 secondary in row context). */\r\n.o6nt8s_primaryButton,\r\n.o6nt8s_secondaryButton,\r\n.o6nt8s_dangerButton,\r\n.o6nt8s_addBlockButton {\r\n box-sizing: border-box;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n height: 36px;\r\n padding: 0 14px;\r\n border: none;\r\n border-radius: 18px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n cursor: pointer;\r\n}\r\n\r\n.o6nt8s_primaryButton {\r\n background: var(--dsw-alias-button-primary-fill);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n}\r\n\r\n.o6nt8s_primaryButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-button-primary-hover);\r\n}\r\n\r\n.o6nt8s_secondaryButton,\r\n.o6nt8s_addBlockButton {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_secondaryButton:hover:not(:disabled),\r\n.o6nt8s_addBlockButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n}\r\n\r\n.o6nt8s_dangerButton {\r\n background: transparent;\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n.o6nt8s_dangerButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n}\r\n\r\n/* Row-context buttons go dense (h28 r14, 12/18). */\r\n.o6nt8s_rowActions .o6nt8s_secondaryButton,\r\n.o6nt8s_rowActions .o6nt8s_dangerButton {\r\n height: 28px;\r\n padding: 0 10px;\r\n border-radius: 14px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.o6nt8s_rowActions .o6nt8s_primaryButton {\r\n height: 28px;\r\n padding: 0 10px;\r\n border-radius: 14px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.o6nt8s_primaryButton:disabled,\r\n.o6nt8s_secondaryButton:disabled,\r\n.o6nt8s_dangerButton:disabled,\r\n.o6nt8s_addBlockButton:disabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n\r\n.o6nt8s_primaryButton:focus-visible,\r\n.o6nt8s_secondaryButton:focus-visible,\r\n.o6nt8s_dangerButton:focus-visible,\r\n.o6nt8s_addBlockButton:focus-visible {\r\n outline: none;\r\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\r\n}\r\n\r\n/* Add-provider action: a full-width dashed-outline place card matching\r\n * ModelsSection's addBlock. */\r\n.o6nt8s_addBlockButton {\r\n width: 100%;\r\n margin-top: 12px;\r\n border-style: dashed;\r\n border-radius: 12px;\r\n height: 40px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.o6nt8s_addBlockButton:hover:not(:disabled) {\r\n color: var(--dsw-alias-label-primary);\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n/* Empty state. */\r\n.o6nt8s_empty {\r\n margin: 0;\r\n padding: 24px 12px;\r\n text-align: center;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n/* Modal confirm body text. */\r\n.o6nt8s_confirmText {\r\n margin: 0;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.o6nt8s_loading {\r\n padding: 12px;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n/* ── Structured endpoint catalog editor (per doc 03-provider-catalog.o6nt8s_md §5) ─ */\r\n\r\n/* A row of fields laid out side-by-side (priority + quality, the three\r\n * cost fields, path + method + capability). Wraps on narrow viewports. */\r\n.o6nt8s_fieldRow {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 12px;\r\n align-items: flex-start;\r\n}\r\n\r\n.o6nt8s_fieldRow > .o6nt8s_field {\r\n flex: 1 1 160px;\r\n min-width: 0;\r\n}\r\n\r\n/* The label row inside .o6nt8s_field that also holds a trailing action button\r\n * (the \"Auto-detect\" button on the Endpoints field). */\r\n.o6nt8s_fieldLabelRow {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.o6nt8s_endpointsAutoDetectButton {\r\n flex: none;\r\n height: 24px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 12px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n font: inherit;\r\n font-size: 12px;\r\n line-height: 18px;\r\n cursor: pointer;\r\n}\r\n\r\n.o6nt8s_endpointsAutoDetectButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* The empty-state hint shown when the endpoints list (or a single\r\n * endpoint's params list) is empty. */\r\n.o6nt8s_endpointsEmpty {\r\n padding: 12px;\r\n border: 1px dashed var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-tertiary);\r\n text-align: center;\r\n}\r\n\r\n/* Container for the list of endpoint cards. */\r\n.o6nt8s_endpointsList {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n}\r\n\r\n/* One endpoint card: outlined on the editor fill, matching the rowCard\r\n * chrome but tighter (8px padding, smaller radius). */\r\n.o6nt8s_endpointCard {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n padding: 10px 12px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 10px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n}\r\n\r\n/* Head row of an endpoint card: shows method + path + capability badge\r\n * on the left, remove button on the right. */\r\n.o6nt8s_endpointHead {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n min-width: 0;\r\n}\r\n\r\n.o6nt8s_endpointHeadLabel {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n min-width: 0;\r\n font-family: var(--dsw-font-markdown-code-block-small, monospace);\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-primary);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Capability badge: a small pill next to the endpoint path (e.o6nt8s_g. \"[t2i]\"). */\r\n.o6nt8s_endpointCapabilityBadge {\r\n height: 16px;\r\n padding: 0 5px;\r\n border-radius: 3px;\r\n font-size: 10px;\r\n line-height: 14px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-brand-primary);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n letter-spacing: 0.02em;\r\n}\r\n\r\n/* Remove button (×) on an endpoint or param row. */\r\n.o6nt8s_endpointRemoveButton,\r\n.o6nt8s_paramRemoveButton {\r\n flex: none;\r\n width: 24px;\r\n height: 24px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n border: none;\r\n border-radius: 4px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-tertiary);\r\n cursor: pointer;\r\n font-size: 16px;\r\n line-height: 1;\r\n padding: 0;\r\n}\r\n\r\n.o6nt8s_endpointRemoveButton:hover,\r\n.o6nt8s_paramRemoveButton:hover {\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n/* \"+ Add endpoint\" / \"+ Add parameter\" button: a dashed-outline place\r\n * card, matching the .o6nt8s_addBlockButton aesthetic but tighter. */\r\n.o6nt8s_addEndpointButton {\r\n align-self: flex-start;\r\n margin-top: 4px;\r\n padding: 4px 12px;\r\n border: 1px dashed var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n font: inherit;\r\n font-size: 12px;\r\n line-height: 18px;\r\n cursor: pointer;\r\n}\r\n\r\n.o6nt8s_addEndpointButton:hover:not(:disabled) {\r\n color: var(--dsw-alias-label-primary);\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n/* Parameter table: a grid of name / type / required / default / remove.\r\n * The columns are sized so the name + default fields get the most room\r\n * (they're the most variable in length); the type + required columns\r\n * are fixed-width. */\r\n.o6nt8s_paramsTable {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n}\r\n\r\n.o6nt8s_paramRow {\r\n display: grid;\r\n grid-template-columns: 1fr 110px auto 1fr 24px;\r\n gap: 6px;\r\n align-items: center;\r\n}\r\n\r\n/* \"Required\" checkbox cell: a tight label + checkbox. */\r\n.o6nt8s_paramRequired {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n color: var(--dsw-alias-label-tertiary);\r\n white-space: nowrap;\r\n}\r\n\r\n.o6nt8s_paramRequired input[type=\"checkbox\"] {\r\n margin: 0;\r\n}\r\n\r\n/* \"Accepts $base64\" checkbox row: full-width label + checkbox. */\r\n.o6nt8s_fieldRow > input[type=\"checkbox\"] {\r\n margin-top: 4px;\r\n}\r\n";
|
|
1996
|
-
const tagId = "@
|
|
1062
|
+
const css = "/* AIGC canvas settings section, in the settings-panel design language shared\r\n * with ModelsSection / GeneralSection / yet-another-subagent: 14/22 body,\r\n * 12/18 caption, 16/24 title, capsule controls (h36 r18 primary, h28 r14\r\n * secondary), 32px fields, border-l2 hairlines, and the editor as a filled\r\n * module on the panel fill.\r\n *\r\n * Every color resolves through a --dsw-alias-* token (no literal colors). */\r\n\r\n.o6nt8s_section {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 12px;\r\n max-width: 720px;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_title {\r\n margin: 0;\r\n font-size: 16px;\r\n line-height: 24px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_intro {\r\n margin: 0;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.o6nt8s_error {\r\n margin: 0;\r\n padding: 8px 12px;\r\n border: 1px solid var(--dsw-alias-state-error-primary);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-state-error-primary);\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.o6nt8s_errorDismiss {\r\n flex: none;\r\n border: none;\r\n background: transparent;\r\n color: inherit;\r\n font-size: 16px;\r\n line-height: 1;\r\n cursor: pointer;\r\n padding: 0 4px;\r\n}\r\n\r\n.o6nt8s_rows {\r\n list-style: none;\r\n margin: 12px 0 0;\r\n padding: 0;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n}\r\n\r\n/* A configured provider: outlined on the panel fill, matching the rowCard\r\n * chrome in ModelsSection. */\r\n.o6nt8s_rowCard {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 12px;\r\n padding: 12px 14px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 12px;\r\n}\r\n\r\n.o6nt8s_rowHead {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n}\r\n\r\n/* Chevron toggle: a small square with two borders, rotated to point right\r\n * (collapsed) or down (expanded). Pure CSS, no icon font. */\r\n.o6nt8s_chevronButton {\r\n flex: none;\r\n width: 24px;\r\n height: 24px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n border: none;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n padding: 0;\r\n border-radius: 4px;\r\n}\r\n\r\n.o6nt8s_chevronButton:hover {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* Spacer that occupies the chevron slot on cards without a toggle (e.o6nt8s_g. the\r\n * new-draft card, which is always expanded). Keeps row-head alignment. */\r\n.o6nt8s_chevronSpacer {\r\n flex: none;\r\n width: 24px;\r\n height: 24px;\r\n}\r\n\r\n.o6nt8s_chevron {\r\n width: 7px;\r\n height: 7px;\r\n border-right: 1.5px solid currentColor;\r\n border-bottom: 1.5px solid currentColor;\r\n transform: rotate(-45deg);\r\n transition: transform 0.15s ease;\r\n}\r\n\r\n.o6nt8s_chevronExpanded {\r\n transform: rotate(45deg);\r\n}\r\n\r\n.o6nt8s_rowIdentity {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n min-width: 0;\r\n flex: 1 1 auto;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.o6nt8s_rowName {\r\n font-size: 14px;\r\n line-height: 22px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.o6nt8s_rowNamePlaceholder {\r\n font-size: 14px;\r\n line-height: 22px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-tertiary);\r\n font-style: italic;\r\n}\r\n\r\n/* Builtin badge: uses DSH <Pill> with a brand-colored override to mark\r\n * seed providers. */\r\n.o6nt8s_builtinBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-brand-primary);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n letter-spacing: 0.02em;\r\n}\r\n\r\n/* Default provider badge. */\r\n.o6nt8s_defaultBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-bg-layer-2);\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Stub/real mode badge. */\r\n.o6nt8s_stubBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-bg-layer-2);\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.o6nt8s_realBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-state-success-primary);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n}\r\n\r\n.o6nt8s_rowId {\r\n flex: none;\r\n padding: 1px 6px;\r\n border: 1px solid var(--dsw-alias-border-l3);\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-family: var(--dsw-font-markdown-code-block-small, monospace);\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.o6nt8s_rowActions {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 4px;\r\n margin-left: auto;\r\n flex: none;\r\n}\r\n\r\n/* Editor surface: a filled module on the panel, matching ModelsSection's\r\n * editor chrome (bg-module-platform, r12, p14/16). */\r\n.o6nt8s_editor {\r\n border-radius: 12px;\r\n background: var(--dsw-alias-bg-module-platform);\r\n padding: 14px 16px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 14px;\r\n}\r\n\r\n.o6nt8s_field {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n.o6nt8s_fieldLabel {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 10px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Input: matching ModelsSection .o6nt8s_input — h32, r8, border-l2, bg-layer-1. */\r\n.o6nt8s_input {\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_input:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.o6nt8s_input::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n.o6nt8s_input:disabled {\r\n opacity: 0.6;\r\n cursor: default;\r\n}\r\n\r\n.o6nt8s_textarea {\r\n box-sizing: border-box;\r\n width: 100%;\r\n min-height: 64px;\r\n padding: 6px 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n resize: vertical;\r\n}\r\n\r\n.o6nt8s_textarea:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.o6nt8s_textarea::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n/* Auth scheme row: scheme select + optional name input side by side. */\r\n.o6nt8s_authRow {\r\n display: flex;\r\n gap: 8px;\r\n}\r\n\r\n.o6nt8s_authRow .o6nt8s_input {\r\n flex: 1 1 auto;\r\n}\r\n\r\n/* Select: same chrome as .o6nt8s_input. */\r\n.o6nt8s_select {\r\n box-sizing: border-box;\r\n flex: 0 0 auto;\r\n min-width: 130px;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_select:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n/* Caption labels under fields. */\r\n.o6nt8s_desc {\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.o6nt8s_hint {\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Buttons: capsule controls matching ModelsSection (h36 r18 primary,\r\n * h28 r14 secondary in row context). */\r\n.o6nt8s_primaryButton,\r\n.o6nt8s_secondaryButton,\r\n.o6nt8s_dangerButton,\r\n.o6nt8s_addBlockButton {\r\n box-sizing: border-box;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n height: 36px;\r\n padding: 0 14px;\r\n border: none;\r\n border-radius: 18px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n cursor: pointer;\r\n}\r\n\r\n.o6nt8s_primaryButton {\r\n background: var(--dsw-alias-button-primary-fill);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n}\r\n\r\n.o6nt8s_primaryButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-button-primary-hover);\r\n}\r\n\r\n.o6nt8s_secondaryButton,\r\n.o6nt8s_addBlockButton {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.o6nt8s_secondaryButton:hover:not(:disabled),\r\n.o6nt8s_addBlockButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n}\r\n\r\n.o6nt8s_dangerButton {\r\n background: transparent;\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n.o6nt8s_dangerButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n}\r\n\r\n/* Row-context buttons go dense (h28 r14, 12/18). */\r\n.o6nt8s_rowActions .o6nt8s_secondaryButton,\r\n.o6nt8s_rowActions .o6nt8s_dangerButton {\r\n height: 28px;\r\n padding: 0 10px;\r\n border-radius: 14px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.o6nt8s_rowActions .o6nt8s_primaryButton {\r\n height: 28px;\r\n padding: 0 10px;\r\n border-radius: 14px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.o6nt8s_primaryButton:disabled,\r\n.o6nt8s_secondaryButton:disabled,\r\n.o6nt8s_dangerButton:disabled,\r\n.o6nt8s_addBlockButton:disabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n\r\n.o6nt8s_primaryButton:focus-visible,\r\n.o6nt8s_secondaryButton:focus-visible,\r\n.o6nt8s_dangerButton:focus-visible,\r\n.o6nt8s_addBlockButton:focus-visible {\r\n outline: none;\r\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\r\n}\r\n\r\n/* Add-provider action: a full-width dashed-outline place card matching\r\n * ModelsSection's addBlock. */\r\n.o6nt8s_addBlockButton {\r\n width: 100%;\r\n margin-top: 12px;\r\n border-style: dashed;\r\n border-radius: 12px;\r\n height: 40px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.o6nt8s_addBlockButton:hover:not(:disabled) {\r\n color: var(--dsw-alias-label-primary);\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n/* Empty state. */\r\n.o6nt8s_empty {\r\n margin: 0;\r\n padding: 24px 12px;\r\n text-align: center;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n/* Modal confirm body text. */\r\n.o6nt8s_confirmText {\r\n margin: 0;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.o6nt8s_loading {\r\n padding: 12px;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n";
|
|
1063
|
+
const tagId = "@dsh-external/dsh-aigc-canvas/SettingsPage.module.css";
|
|
1997
1064
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1998
1065
|
const tag = document.createElement("style");
|
|
1999
|
-
tag.dataset.plugin = "@
|
|
1066
|
+
tag.dataset.plugin = "@dsh-external/dsh-aigc-canvas";
|
|
2000
1067
|
tag.dataset.pluginCss = tagId;
|
|
2001
1068
|
tag.textContent = css;
|
|
2002
1069
|
document.head.appendChild(tag);
|
|
@@ -2039,23 +1106,7 @@ window.__ModuleLoader__.load({
|
|
|
2039
1106
|
"addBlockButton": "o6nt8s_addBlockButton",
|
|
2040
1107
|
"empty": "o6nt8s_empty",
|
|
2041
1108
|
"confirmText": "o6nt8s_confirmText",
|
|
2042
|
-
"loading": "o6nt8s_loading"
|
|
2043
|
-
"md": "o6nt8s_md",
|
|
2044
|
-
"fieldRow": "o6nt8s_fieldRow",
|
|
2045
|
-
"fieldLabelRow": "o6nt8s_fieldLabelRow",
|
|
2046
|
-
"endpointsAutoDetectButton": "o6nt8s_endpointsAutoDetectButton",
|
|
2047
|
-
"endpointsEmpty": "o6nt8s_endpointsEmpty",
|
|
2048
|
-
"endpointsList": "o6nt8s_endpointsList",
|
|
2049
|
-
"endpointCard": "o6nt8s_endpointCard",
|
|
2050
|
-
"endpointHead": "o6nt8s_endpointHead",
|
|
2051
|
-
"endpointHeadLabel": "o6nt8s_endpointHeadLabel",
|
|
2052
|
-
"endpointCapabilityBadge": "o6nt8s_endpointCapabilityBadge",
|
|
2053
|
-
"endpointRemoveButton": "o6nt8s_endpointRemoveButton",
|
|
2054
|
-
"paramRemoveButton": "o6nt8s_paramRemoveButton",
|
|
2055
|
-
"addEndpointButton": "o6nt8s_addEndpointButton",
|
|
2056
|
-
"paramsTable": "o6nt8s_paramsTable",
|
|
2057
|
-
"paramRow": "o6nt8s_paramRow",
|
|
2058
|
-
"paramRequired": "o6nt8s_paramRequired"
|
|
1109
|
+
"loading": "o6nt8s_loading"
|
|
2059
1110
|
};
|
|
2060
1111
|
//#endregion
|
|
2061
1112
|
//#region src/client/SettingsPage.tsx
|
|
@@ -2081,14 +1132,9 @@ window.__ModuleLoader__.load({
|
|
|
2081
1132
|
* (bearer / custom header / query param) the aigc_http_request tool uses to
|
|
2082
1133
|
* attach the apiKey.
|
|
2083
1134
|
*
|
|
2084
|
-
* @module @
|
|
2085
|
-
*/
|
|
2086
|
-
/**
|
|
2087
|
-
* Default shape for a brand-new draft (before the user fills in id/name).
|
|
2088
|
-
* The structured-catalog fields default to sane values so the endpoints
|
|
2089
|
-
* editor starts empty (the agent's "Initialize" / "Auto-detect" buttons
|
|
2090
|
-
* populate it after probing the API).
|
|
1135
|
+
* @module @dsh-external/dsh-aigc-canvas/client/SettingsPage
|
|
2091
1136
|
*/
|
|
1137
|
+
/** Default shape for a brand-new draft (before the user fills in id/name). */
|
|
2092
1138
|
function emptyDraft() {
|
|
2093
1139
|
return {
|
|
2094
1140
|
id: "",
|
|
@@ -2100,56 +1146,9 @@ window.__ModuleLoader__.load({
|
|
|
2100
1146
|
scheme: "bearer",
|
|
2101
1147
|
name: ""
|
|
2102
1148
|
},
|
|
2103
|
-
builtin: false
|
|
2104
|
-
endpoints: [],
|
|
2105
|
-
priority: 100,
|
|
2106
|
-
costPerCall: 0,
|
|
2107
|
-
costPerKiloToken: 0,
|
|
2108
|
-
costPerSecond: 0,
|
|
2109
|
-
avgLatencyMs: 0,
|
|
2110
|
-
qualityHint: "balanced"
|
|
1149
|
+
builtin: false
|
|
2111
1150
|
};
|
|
2112
1151
|
}
|
|
2113
|
-
/** Build a fresh blank endpoint (for the "+ Add endpoint" button). */
|
|
2114
|
-
function emptyEndpoint() {
|
|
2115
|
-
return {
|
|
2116
|
-
path: "",
|
|
2117
|
-
method: "POST",
|
|
2118
|
-
capability: "t2i",
|
|
2119
|
-
params: [],
|
|
2120
|
-
response: {
|
|
2121
|
-
kind: "json_text",
|
|
2122
|
-
path: ""
|
|
2123
|
-
},
|
|
2124
|
-
acceptsCanvasRef: false,
|
|
2125
|
-
notes: ""
|
|
2126
|
-
};
|
|
2127
|
-
}
|
|
2128
|
-
/** Build a fresh blank parameter (for the "+ Add parameter" button). */
|
|
2129
|
-
function emptyParam() {
|
|
2130
|
-
return {
|
|
2131
|
-
name: "",
|
|
2132
|
-
type: "string",
|
|
2133
|
-
required: false,
|
|
2134
|
-
default: "",
|
|
2135
|
-
description: ""
|
|
2136
|
-
};
|
|
2137
|
-
}
|
|
2138
|
-
/** Coerce a possibly-undefined value to a number (default 0 when invalid). */
|
|
2139
|
-
function toNumber(v, fallback = 0) {
|
|
2140
|
-
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
2141
|
-
if (typeof v === "string") {
|
|
2142
|
-
const n = Number(v);
|
|
2143
|
-
if (Number.isFinite(n) && v.trim() !== "") return n;
|
|
2144
|
-
}
|
|
2145
|
-
return fallback;
|
|
2146
|
-
}
|
|
2147
|
-
/** Coerce a possibly-undefined value to a string (default ''). */
|
|
2148
|
-
function toStr(v) {
|
|
2149
|
-
if (typeof v === "string") return v;
|
|
2150
|
-
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
2151
|
-
return "";
|
|
2152
|
-
}
|
|
2153
1152
|
/**
|
|
2154
1153
|
* Render the AIGC provider settings page.
|
|
2155
1154
|
* @param props - settings.section runtime share + locale + inject.
|
|
@@ -2235,24 +1234,6 @@ window.__ModuleLoader__.load({
|
|
|
2235
1234
|
setError(err instanceof Error ? err.message : String(err));
|
|
2236
1235
|
}
|
|
2237
1236
|
}, [send, t]);
|
|
2238
|
-
/**
|
|
2239
|
-
* Auto-detect (per docs/product/03-provider-catalog.md §5): send a
|
|
2240
|
-
* prepared message into the current conversation asking the agent to
|
|
2241
|
-
* call `aigc_probe_endpoint` for each endpoint whose response.kind is
|
|
2242
|
-
* not yet set, then save the detected shapes via
|
|
2243
|
-
* `aigc_provider_set_endpoints`. The agent handles the actual probing
|
|
2244
|
-
* + persistence; the client just sends the prompt (same pattern as
|
|
2245
|
-
* the "Initialize" action).
|
|
2246
|
-
*/
|
|
2247
|
-
const autoDetect = (0, react.useCallback)(async (provider) => {
|
|
2248
|
-
const label = provider.name === "" ? provider.id : provider.name;
|
|
2249
|
-
const text = t("row.autoDetectPrompt").replace("{name}", label).replace("{id}", provider.id);
|
|
2250
|
-
try {
|
|
2251
|
-
await send(text);
|
|
2252
|
-
} catch (err) {
|
|
2253
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
2254
|
-
}
|
|
2255
|
-
}, [send, t]);
|
|
2256
1237
|
const patchDraft = (id, patch) => {
|
|
2257
1238
|
setDrafts((prev) => prev.map((d) => d.id === id ? {
|
|
2258
1239
|
...d,
|
|
@@ -2307,8 +1288,7 @@ window.__ModuleLoader__.load({
|
|
|
2307
1288
|
onPatch: (patch) => patchDraft(draft.id, patch),
|
|
2308
1289
|
onSave: () => void update(draft),
|
|
2309
1290
|
onDelete: () => setConfirmDelete(draft.id),
|
|
2310
|
-
onInit: () => void init(draft)
|
|
2311
|
-
onAutoDetect: () => void autoDetect(draft)
|
|
1291
|
+
onInit: () => void init(draft)
|
|
2312
1292
|
}, draft.id)), addingNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderCard, {
|
|
2313
1293
|
draft: newDraft,
|
|
2314
1294
|
expanded: true,
|
|
@@ -2359,7 +1339,7 @@ window.__ModuleLoader__.load({
|
|
|
2359
1339
|
]
|
|
2360
1340
|
});
|
|
2361
1341
|
}
|
|
2362
|
-
function ProviderCard({ draft, expanded, isNew, isDefault, t, onToggle, onPatch, onSave, onDelete, onCreate, onCancel, onInit
|
|
1342
|
+
function ProviderCard({ draft, expanded, isNew, isDefault, t, onToggle, onPatch, onSave, onDelete, onCreate, onCancel, onInit }) {
|
|
2363
1343
|
const isStub = draft.endpoint === "" || draft.endpoint === "stub://aigc-backend";
|
|
2364
1344
|
const patchAuth = (patch) => {
|
|
2365
1345
|
onPatch({ auth: {
|
|
@@ -2367,60 +1347,6 @@ window.__ModuleLoader__.load({
|
|
|
2367
1347
|
...patch
|
|
2368
1348
|
} });
|
|
2369
1349
|
};
|
|
2370
|
-
/**
|
|
2371
|
-
* Patch one endpoint in the draft's endpoints array (by index).
|
|
2372
|
-
* Replaces the whole array so React sees a new reference and re-renders.
|
|
2373
|
-
*/
|
|
2374
|
-
const patchEndpoint = (index, patch) => {
|
|
2375
|
-
const next = [...draft.endpoints ?? []];
|
|
2376
|
-
const existing = next[index];
|
|
2377
|
-
if (existing === void 0) return;
|
|
2378
|
-
next[index] = {
|
|
2379
|
-
...existing,
|
|
2380
|
-
...patch
|
|
2381
|
-
};
|
|
2382
|
-
onPatch({ endpoints: next });
|
|
2383
|
-
};
|
|
2384
|
-
/** Append a fresh blank endpoint to the endpoints array. */
|
|
2385
|
-
const addEndpoint = () => {
|
|
2386
|
-
onPatch({ endpoints: [...draft.endpoints ?? [], emptyEndpoint()] });
|
|
2387
|
-
};
|
|
2388
|
-
/** Remove the endpoint at one index. */
|
|
2389
|
-
const removeEndpoint = (index) => {
|
|
2390
|
-
const next = [...draft.endpoints ?? []];
|
|
2391
|
-
next.splice(index, 1);
|
|
2392
|
-
onPatch({ endpoints: next });
|
|
2393
|
-
};
|
|
2394
|
-
/**
|
|
2395
|
-
* Patch one parameter on one endpoint (by endpoint index + param index).
|
|
2396
|
-
*/
|
|
2397
|
-
const patchParam = (epIndex, paramIndex, patch) => {
|
|
2398
|
-
const ep = (draft.endpoints ?? [])[epIndex];
|
|
2399
|
-
if (ep === void 0) return;
|
|
2400
|
-
const params = [...ep.params ?? []];
|
|
2401
|
-
const existing = params[paramIndex];
|
|
2402
|
-
if (existing === void 0) return;
|
|
2403
|
-
params[paramIndex] = {
|
|
2404
|
-
...existing,
|
|
2405
|
-
...patch
|
|
2406
|
-
};
|
|
2407
|
-
patchEndpoint(epIndex, { params });
|
|
2408
|
-
};
|
|
2409
|
-
/** Append a fresh blank parameter to one endpoint. */
|
|
2410
|
-
const addParam = (epIndex) => {
|
|
2411
|
-
const ep = (draft.endpoints ?? [])[epIndex];
|
|
2412
|
-
if (ep === void 0) return;
|
|
2413
|
-
const params = [...ep.params ?? [], emptyParam()];
|
|
2414
|
-
patchEndpoint(epIndex, { params });
|
|
2415
|
-
};
|
|
2416
|
-
/** Remove the parameter at one index on one endpoint. */
|
|
2417
|
-
const removeParam = (epIndex, paramIndex) => {
|
|
2418
|
-
const ep = (draft.endpoints ?? [])[epIndex];
|
|
2419
|
-
if (ep === void 0) return;
|
|
2420
|
-
const params = [...ep.params ?? []];
|
|
2421
|
-
params.splice(paramIndex, 1);
|
|
2422
|
-
patchEndpoint(epIndex, { params });
|
|
2423
|
-
};
|
|
2424
1350
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2425
1351
|
className: SettingsPage_module_css_default.rowCard,
|
|
2426
1352
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2488,13 +1414,6 @@ window.__ModuleLoader__.load({
|
|
|
2488
1414
|
onClick: onInit,
|
|
2489
1415
|
children: t("row.init")
|
|
2490
1416
|
}),
|
|
2491
|
-
!isStub && onAutoDetect !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2492
|
-
type: "button",
|
|
2493
|
-
className: SettingsPage_module_css_default.secondaryButton,
|
|
2494
|
-
onClick: onAutoDetect,
|
|
2495
|
-
title: t("row.autoDetectTitle"),
|
|
2496
|
-
children: t("row.autoDetect")
|
|
2497
|
-
}),
|
|
2498
1417
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2499
1418
|
type: "button",
|
|
2500
1419
|
className: SettingsPage_module_css_default.secondaryButton,
|
|
@@ -2635,162 +1554,6 @@ window.__ModuleLoader__.load({
|
|
|
2635
1554
|
})
|
|
2636
1555
|
]
|
|
2637
1556
|
}),
|
|
2638
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2639
|
-
className: SettingsPage_module_css_default.fieldRow,
|
|
2640
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2641
|
-
className: SettingsPage_module_css_default.field,
|
|
2642
|
-
children: [
|
|
2643
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2644
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2645
|
-
children: t("row.priority")
|
|
2646
|
-
}),
|
|
2647
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2648
|
-
className: SettingsPage_module_css_default.input,
|
|
2649
|
-
type: "number",
|
|
2650
|
-
min: 0,
|
|
2651
|
-
step: 1,
|
|
2652
|
-
value: toNumber(draft.priority, 100),
|
|
2653
|
-
onChange: (e) => onPatch({ priority: toNumber(e.target.value, 100) })
|
|
2654
|
-
}),
|
|
2655
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2656
|
-
className: SettingsPage_module_css_default.desc,
|
|
2657
|
-
children: t("row.priorityDesc")
|
|
2658
|
-
})
|
|
2659
|
-
]
|
|
2660
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2661
|
-
className: SettingsPage_module_css_default.field,
|
|
2662
|
-
children: [
|
|
2663
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2664
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2665
|
-
children: t("row.qualityHint")
|
|
2666
|
-
}),
|
|
2667
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2668
|
-
className: SettingsPage_module_css_default.select,
|
|
2669
|
-
value: draft.qualityHint ?? "balanced",
|
|
2670
|
-
onChange: (e) => onPatch({ qualityHint: e.target.value }),
|
|
2671
|
-
children: RUNTIME_QUALITY_HINTS.map((q) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2672
|
-
value: q,
|
|
2673
|
-
children: q
|
|
2674
|
-
}, q))
|
|
2675
|
-
}),
|
|
2676
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2677
|
-
className: SettingsPage_module_css_default.desc,
|
|
2678
|
-
children: t("row.qualityHintDesc")
|
|
2679
|
-
})
|
|
2680
|
-
]
|
|
2681
|
-
})]
|
|
2682
|
-
}),
|
|
2683
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2684
|
-
className: SettingsPage_module_css_default.fieldRow,
|
|
2685
|
-
children: [
|
|
2686
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2687
|
-
className: SettingsPage_module_css_default.field,
|
|
2688
|
-
children: [
|
|
2689
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2690
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2691
|
-
children: t("row.costPerCall")
|
|
2692
|
-
}),
|
|
2693
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2694
|
-
className: SettingsPage_module_css_default.input,
|
|
2695
|
-
type: "number",
|
|
2696
|
-
min: 0,
|
|
2697
|
-
step: 1e-4,
|
|
2698
|
-
value: toNumber(draft.costPerCall, 0),
|
|
2699
|
-
onChange: (e) => onPatch({ costPerCall: toNumber(e.target.value, 0) })
|
|
2700
|
-
}),
|
|
2701
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2702
|
-
className: SettingsPage_module_css_default.desc,
|
|
2703
|
-
children: t("row.costPerCallDesc")
|
|
2704
|
-
})
|
|
2705
|
-
]
|
|
2706
|
-
}),
|
|
2707
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2708
|
-
className: SettingsPage_module_css_default.field,
|
|
2709
|
-
children: [
|
|
2710
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2711
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2712
|
-
children: t("row.costPerKiloToken")
|
|
2713
|
-
}),
|
|
2714
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2715
|
-
className: SettingsPage_module_css_default.input,
|
|
2716
|
-
type: "number",
|
|
2717
|
-
min: 0,
|
|
2718
|
-
step: 1e-4,
|
|
2719
|
-
value: toNumber(draft.costPerKiloToken, 0),
|
|
2720
|
-
onChange: (e) => onPatch({ costPerKiloToken: toNumber(e.target.value, 0) })
|
|
2721
|
-
}),
|
|
2722
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2723
|
-
className: SettingsPage_module_css_default.desc,
|
|
2724
|
-
children: t("row.costPerKiloTokenDesc")
|
|
2725
|
-
})
|
|
2726
|
-
]
|
|
2727
|
-
}),
|
|
2728
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2729
|
-
className: SettingsPage_module_css_default.field,
|
|
2730
|
-
children: [
|
|
2731
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2732
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2733
|
-
children: t("row.costPerSecond")
|
|
2734
|
-
}),
|
|
2735
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2736
|
-
className: SettingsPage_module_css_default.input,
|
|
2737
|
-
type: "number",
|
|
2738
|
-
min: 0,
|
|
2739
|
-
step: 1e-4,
|
|
2740
|
-
value: toNumber(draft.costPerSecond, 0),
|
|
2741
|
-
onChange: (e) => onPatch({ costPerSecond: toNumber(e.target.value, 0) })
|
|
2742
|
-
}),
|
|
2743
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2744
|
-
className: SettingsPage_module_css_default.desc,
|
|
2745
|
-
children: t("row.costPerSecondDesc")
|
|
2746
|
-
})
|
|
2747
|
-
]
|
|
2748
|
-
})
|
|
2749
|
-
]
|
|
2750
|
-
}),
|
|
2751
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2752
|
-
className: SettingsPage_module_css_default.field,
|
|
2753
|
-
children: [
|
|
2754
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2755
|
-
className: SettingsPage_module_css_default.fieldLabelRow,
|
|
2756
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2757
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2758
|
-
children: t("row.endpoints")
|
|
2759
|
-
}), !isStub && onAutoDetect !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2760
|
-
type: "button",
|
|
2761
|
-
className: SettingsPage_module_css_default.endpointsAutoDetectButton,
|
|
2762
|
-
onClick: onAutoDetect,
|
|
2763
|
-
title: t("row.autoDetectTitle"),
|
|
2764
|
-
children: t("row.autoDetect")
|
|
2765
|
-
})]
|
|
2766
|
-
}),
|
|
2767
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2768
|
-
className: SettingsPage_module_css_default.desc,
|
|
2769
|
-
children: t("row.endpointsDesc")
|
|
2770
|
-
}),
|
|
2771
|
-
(draft.endpoints ?? []).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2772
|
-
className: SettingsPage_module_css_default.endpointsEmpty,
|
|
2773
|
-
children: t("row.endpointsEmpty")
|
|
2774
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2775
|
-
className: SettingsPage_module_css_default.endpointsList,
|
|
2776
|
-
children: (draft.endpoints ?? []).map((ep, epIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EndpointCard, {
|
|
2777
|
-
endpoint: ep,
|
|
2778
|
-
t,
|
|
2779
|
-
onPatch: (patch) => patchEndpoint(epIndex, patch),
|
|
2780
|
-
onRemove: () => removeEndpoint(epIndex),
|
|
2781
|
-
onAddParam: () => addParam(epIndex),
|
|
2782
|
-
onPatchParam: (paramIndex, patch) => patchParam(epIndex, paramIndex, patch),
|
|
2783
|
-
onRemoveParam: (paramIndex) => removeParam(epIndex, paramIndex)
|
|
2784
|
-
}, epIndex))
|
|
2785
|
-
}),
|
|
2786
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2787
|
-
type: "button",
|
|
2788
|
-
className: SettingsPage_module_css_default.addEndpointButton,
|
|
2789
|
-
onClick: addEndpoint,
|
|
2790
|
-
children: t("row.addEndpoint")
|
|
2791
|
-
})
|
|
2792
|
-
]
|
|
2793
|
-
}),
|
|
2794
1557
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2795
1558
|
className: SettingsPage_module_css_default.field,
|
|
2796
1559
|
children: [
|
|
@@ -2819,220 +1582,12 @@ window.__ModuleLoader__.load({
|
|
|
2819
1582
|
})]
|
|
2820
1583
|
});
|
|
2821
1584
|
}
|
|
2822
|
-
/**
|
|
2823
|
-
* One endpoint in the catalog editor: path / method / capability /
|
|
2824
|
-
* response.kind / response.path + acceptsCanvasRef + notes + parameter
|
|
2825
|
-
* list. Per docs/product/03-provider-catalog.md §5.
|
|
2826
|
-
*
|
|
2827
|
-
* No collapsible state — the card is always expanded so the user can
|
|
2828
|
-
* see all fields. The parameter list is a simple grid (name / type /
|
|
2829
|
-
* required / default) with add/remove buttons.
|
|
2830
|
-
*/
|
|
2831
|
-
function EndpointCard({ endpoint, t, onPatch, onRemove, onAddParam, onPatchParam, onRemoveParam }) {
|
|
2832
|
-
const response = endpoint.response ?? {
|
|
2833
|
-
kind: "json_text",
|
|
2834
|
-
path: ""
|
|
2835
|
-
};
|
|
2836
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2837
|
-
className: SettingsPage_module_css_default.endpointCard,
|
|
2838
|
-
children: [
|
|
2839
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2840
|
-
className: SettingsPage_module_css_default.endpointHead,
|
|
2841
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2842
|
-
className: SettingsPage_module_css_default.endpointHeadLabel,
|
|
2843
|
-
children: [
|
|
2844
|
-
endpoint.method,
|
|
2845
|
-
" ",
|
|
2846
|
-
endpoint.path === "" ? "<path>" : endpoint.path,
|
|
2847
|
-
endpoint.capability !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
|
|
2848
|
-
className: SettingsPage_module_css_default.endpointCapabilityBadge,
|
|
2849
|
-
children: endpoint.capability
|
|
2850
|
-
})
|
|
2851
|
-
]
|
|
2852
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2853
|
-
type: "button",
|
|
2854
|
-
className: SettingsPage_module_css_default.endpointRemoveButton,
|
|
2855
|
-
onClick: onRemove,
|
|
2856
|
-
"aria-label": t("row.removeEndpoint"),
|
|
2857
|
-
children: "×"
|
|
2858
|
-
})]
|
|
2859
|
-
}),
|
|
2860
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2861
|
-
className: SettingsPage_module_css_default.fieldRow,
|
|
2862
|
-
children: [
|
|
2863
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2864
|
-
className: SettingsPage_module_css_default.field,
|
|
2865
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2866
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2867
|
-
children: t("row.endpointPath")
|
|
2868
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2869
|
-
className: SettingsPage_module_css_default.input,
|
|
2870
|
-
value: endpoint.path,
|
|
2871
|
-
placeholder: "/v1/images/generations",
|
|
2872
|
-
onChange: (e) => onPatch({ path: e.target.value })
|
|
2873
|
-
})]
|
|
2874
|
-
}),
|
|
2875
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2876
|
-
className: SettingsPage_module_css_default.field,
|
|
2877
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2878
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2879
|
-
children: t("row.endpointMethod")
|
|
2880
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2881
|
-
className: SettingsPage_module_css_default.select,
|
|
2882
|
-
value: endpoint.method,
|
|
2883
|
-
onChange: (e) => onPatch({ method: e.target.value }),
|
|
2884
|
-
children: RUNTIME_HTTP_METHODS.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2885
|
-
value: m,
|
|
2886
|
-
children: m
|
|
2887
|
-
}, m))
|
|
2888
|
-
})]
|
|
2889
|
-
}),
|
|
2890
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2891
|
-
className: SettingsPage_module_css_default.field,
|
|
2892
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2893
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2894
|
-
children: t("row.endpointCapability")
|
|
2895
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2896
|
-
className: SettingsPage_module_css_default.select,
|
|
2897
|
-
value: endpoint.capability,
|
|
2898
|
-
onChange: (e) => onPatch({ capability: e.target.value }),
|
|
2899
|
-
children: RUNTIME_CAPABILITIES.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2900
|
-
value: c,
|
|
2901
|
-
children: c
|
|
2902
|
-
}, c))
|
|
2903
|
-
})]
|
|
2904
|
-
})
|
|
2905
|
-
]
|
|
2906
|
-
}),
|
|
2907
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2908
|
-
className: SettingsPage_module_css_default.fieldRow,
|
|
2909
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2910
|
-
className: SettingsPage_module_css_default.field,
|
|
2911
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2912
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2913
|
-
children: t("row.endpointResponseKind")
|
|
2914
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2915
|
-
className: SettingsPage_module_css_default.select,
|
|
2916
|
-
value: response.kind,
|
|
2917
|
-
onChange: (e) => onPatch({ response: {
|
|
2918
|
-
kind: e.target.value,
|
|
2919
|
-
path: response.path
|
|
2920
|
-
} }),
|
|
2921
|
-
children: RUNTIME_RESPONSE_KINDS.map((k) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2922
|
-
value: k,
|
|
2923
|
-
children: k
|
|
2924
|
-
}, k))
|
|
2925
|
-
})]
|
|
2926
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2927
|
-
className: SettingsPage_module_css_default.field,
|
|
2928
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2929
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2930
|
-
children: t("row.endpointResponsePath")
|
|
2931
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2932
|
-
className: SettingsPage_module_css_default.input,
|
|
2933
|
-
value: toStr(response.path),
|
|
2934
|
-
placeholder: "data[0].b64_json",
|
|
2935
|
-
onChange: (e) => onPatch({ response: {
|
|
2936
|
-
kind: response.kind,
|
|
2937
|
-
path: e.target.value
|
|
2938
|
-
} })
|
|
2939
|
-
})]
|
|
2940
|
-
})]
|
|
2941
|
-
}),
|
|
2942
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2943
|
-
className: SettingsPage_module_css_default.fieldRow,
|
|
2944
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2945
|
-
type: "checkbox",
|
|
2946
|
-
checked: endpoint.acceptsCanvasRef === true,
|
|
2947
|
-
onChange: (e) => onPatch({ acceptsCanvasRef: e.target.checked })
|
|
2948
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2949
|
-
className: SettingsPage_module_css_default.desc,
|
|
2950
|
-
children: t("row.endpointAcceptsCanvasRef")
|
|
2951
|
-
})]
|
|
2952
|
-
}),
|
|
2953
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2954
|
-
className: SettingsPage_module_css_default.field,
|
|
2955
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2956
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2957
|
-
children: t("row.endpointNotes")
|
|
2958
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2959
|
-
className: SettingsPage_module_css_default.input,
|
|
2960
|
-
value: toStr(endpoint.notes),
|
|
2961
|
-
placeholder: "size must be 1024x1024 or 1792x1024",
|
|
2962
|
-
onChange: (e) => onPatch({ notes: e.target.value })
|
|
2963
|
-
})]
|
|
2964
|
-
}),
|
|
2965
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2966
|
-
className: SettingsPage_module_css_default.field,
|
|
2967
|
-
children: [
|
|
2968
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2969
|
-
className: SettingsPage_module_css_default.fieldLabel,
|
|
2970
|
-
children: t("row.endpointParams")
|
|
2971
|
-
}),
|
|
2972
|
-
(endpoint.params ?? []).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2973
|
-
className: SettingsPage_module_css_default.endpointsEmpty,
|
|
2974
|
-
children: t("row.endpointParamsEmpty")
|
|
2975
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2976
|
-
className: SettingsPage_module_css_default.paramsTable,
|
|
2977
|
-
children: (endpoint.params ?? []).map((param, paramIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2978
|
-
className: SettingsPage_module_css_default.paramRow,
|
|
2979
|
-
children: [
|
|
2980
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2981
|
-
className: SettingsPage_module_css_default.input,
|
|
2982
|
-
value: param.name,
|
|
2983
|
-
placeholder: t("row.endpointParamName"),
|
|
2984
|
-
onChange: (e) => onPatchParam(paramIndex, { name: e.target.value })
|
|
2985
|
-
}),
|
|
2986
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2987
|
-
className: SettingsPage_module_css_default.select,
|
|
2988
|
-
value: param.type,
|
|
2989
|
-
onChange: (e) => onPatchParam(paramIndex, { type: e.target.value }),
|
|
2990
|
-
children: RUNTIME_PARAM_TYPES.map((tp) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2991
|
-
value: tp,
|
|
2992
|
-
children: tp
|
|
2993
|
-
}, tp))
|
|
2994
|
-
}),
|
|
2995
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2996
|
-
className: SettingsPage_module_css_default.paramRequired,
|
|
2997
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2998
|
-
type: "checkbox",
|
|
2999
|
-
checked: param.required,
|
|
3000
|
-
onChange: (e) => onPatchParam(paramIndex, { required: e.target.checked })
|
|
3001
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.endpointParamRequired") })]
|
|
3002
|
-
}),
|
|
3003
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3004
|
-
className: SettingsPage_module_css_default.input,
|
|
3005
|
-
value: toStr(param.default),
|
|
3006
|
-
placeholder: t("row.endpointParamDefault"),
|
|
3007
|
-
onChange: (e) => onPatchParam(paramIndex, { default: e.target.value })
|
|
3008
|
-
}),
|
|
3009
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3010
|
-
type: "button",
|
|
3011
|
-
className: SettingsPage_module_css_default.paramRemoveButton,
|
|
3012
|
-
onClick: () => onRemoveParam(paramIndex),
|
|
3013
|
-
"aria-label": t("row.endpointRemoveParam"),
|
|
3014
|
-
children: "×"
|
|
3015
|
-
})
|
|
3016
|
-
]
|
|
3017
|
-
}, paramIndex))
|
|
3018
|
-
}),
|
|
3019
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3020
|
-
type: "button",
|
|
3021
|
-
className: SettingsPage_module_css_default.addEndpointButton,
|
|
3022
|
-
onClick: onAddParam,
|
|
3023
|
-
children: t("row.endpointAddParam")
|
|
3024
|
-
})
|
|
3025
|
-
]
|
|
3026
|
-
})
|
|
3027
|
-
]
|
|
3028
|
-
});
|
|
3029
|
-
}
|
|
3030
1585
|
//#endregion
|
|
3031
1586
|
//#region src/client/locales.ts
|
|
3032
1587
|
/**
|
|
3033
1588
|
* i18n dictionaries for the AIGC canvas plugin.
|
|
3034
1589
|
*
|
|
3035
|
-
* @module @
|
|
1590
|
+
* @module @dsh-external/dsh-aigc-canvas/client/locales
|
|
3036
1591
|
*/
|
|
3037
1592
|
const NS = "dsh-aigc-canvas";
|
|
3038
1593
|
const zh = {
|
|
@@ -3065,28 +1620,6 @@ window.__ModuleLoader__.load({
|
|
|
3065
1620
|
deleteElement: "删除元素",
|
|
3066
1621
|
dropHint: "拖放文件到画布",
|
|
3067
1622
|
uploading: "上传中…",
|
|
3068
|
-
menuRegenerate: "重新生成...",
|
|
3069
|
-
menuUseAsReference: "用作参考...",
|
|
3070
|
-
menuSendToChat: "发到对话",
|
|
3071
|
-
menuDownload: "下载",
|
|
3072
|
-
menuPromoteToLibrary: "提升到资产库...",
|
|
3073
|
-
menuMarkWinner: "标记为 winner",
|
|
3074
|
-
menuMarkRejected: "标记为否决",
|
|
3075
|
-
menuArchive: "归档",
|
|
3076
|
-
menuSeparator: "─────────────",
|
|
3077
|
-
toolbarGenerate: "+ 生成",
|
|
3078
|
-
toolbarEditSelected: "✂ 编辑选中",
|
|
3079
|
-
toolbarRunWorkflow: "▶ 运行工作流",
|
|
3080
|
-
toolbarGenerateTitle: "打开快速生成弹窗(t2i/t2v/tts)",
|
|
3081
|
-
toolbarEditSelectedTitle: "对选中元素执行 ffmpeg 操作",
|
|
3082
|
-
toolbarRunWorkflowTitle: "打开 pipeline 模板选择器",
|
|
3083
|
-
toolbarNoSelection: "请先在画布上选中一个元素",
|
|
3084
|
-
noticeRegenerate: "请用 aigc_reroll 重新生成元素 {filePath}",
|
|
3085
|
-
noticeUseAsReference: "请把以下元素用作后续生成的参考: {filePath}",
|
|
3086
|
-
noticeSendToChat: "请使用这个元素作为参考: [filePath: {filePath}, kind: {kind}, title: {title}]",
|
|
3087
|
-
noticeGenerate: "请帮我生成一个新的 AIGC 素材(先调用 aigc_get_provider_info 查看可用供应商,再调用 aigc_http_request 发起生成,最后用 aigc_canvas_place 把产物放到画布上)。",
|
|
3088
|
-
noticeEditSelected: "请用 aigc_media_edit(ffmpeg)对选中元素进行编辑: {filePath}(kind: {kind}, title: {title})",
|
|
3089
|
-
noticeRunWorkflow: "请列出可用的 pipeline 模板并运行其中一个(如果只有一个模板就直接运行它)。",
|
|
3090
1623
|
settingsNav: "AIGC 画布",
|
|
3091
1624
|
settingsTitle: "AIGC 供应商",
|
|
3092
1625
|
settingsIntro: "配置一个或多个 AIGC 供应商。每个供应商可独立设置名称、API 地址、密钥、鉴权方式和调用说明。模型通过 aigc_get_provider_info 读取供应商列表,用 aigc_http_request 调用 API(自动携带 endpoint 和 apiKey),生成的文件用 aigc_canvas_place 放到画布上。",
|
|
@@ -3126,75 +1659,7 @@ window.__ModuleLoader__.load({
|
|
|
3126
1659
|
"badge.builtin": "内置",
|
|
3127
1660
|
"badge.stub": "stub 模式",
|
|
3128
1661
|
"badge.real": "真实 API",
|
|
3129
|
-
"badge.default": "默认"
|
|
3130
|
-
logButton: "日志",
|
|
3131
|
-
logTitle: "请求日志",
|
|
3132
|
-
logClear: "清空",
|
|
3133
|
-
logEmpty: "暂无请求记录。",
|
|
3134
|
-
logLoading: "加载中…",
|
|
3135
|
-
logError: "错误",
|
|
3136
|
-
logRequestBody: "请求体",
|
|
3137
|
-
logRequestHeaders: "请求头",
|
|
3138
|
-
logResponseBody: "响应预览",
|
|
3139
|
-
logProducedFile: "产物文件",
|
|
3140
|
-
logLocate: "在画布上定位",
|
|
3141
|
-
winner: "优胜",
|
|
3142
|
-
statusFilter: "状态",
|
|
3143
|
-
statusReady: "就绪",
|
|
3144
|
-
statusDraft: "草稿",
|
|
3145
|
-
statusRejected: "否决",
|
|
3146
|
-
statusArchived: "归档",
|
|
3147
|
-
compareButton: "对比",
|
|
3148
|
-
compareTitle: "对比视图",
|
|
3149
|
-
compareClose: "关闭",
|
|
3150
|
-
compareSelectWinner: "选为 winner",
|
|
3151
|
-
compareRejectAll: "全部否决",
|
|
3152
|
-
compareCancelSelection: "取消选择",
|
|
3153
|
-
compareSeed: "seed",
|
|
3154
|
-
compareCost: "成本",
|
|
3155
|
-
compareDuration: "耗时",
|
|
3156
|
-
comparePrompt: "提示词",
|
|
3157
|
-
compareNoMedia: "该元素无可显示的媒体",
|
|
3158
|
-
compareNotEnough: "请选择 2-4 个元素进行对比",
|
|
3159
|
-
compareTooMany: "最多只能同时对比 4 个元素",
|
|
3160
|
-
compareClearSelection: "清除选择",
|
|
3161
|
-
compareNSelected: "已选 {n} 个",
|
|
3162
|
-
"row.priority": "优先级",
|
|
3163
|
-
"row.qualityHint": "质量",
|
|
3164
|
-
"row.costPerCall": "单次成本 ($)",
|
|
3165
|
-
"row.costPerKiloToken": "千 token 成本 ($)",
|
|
3166
|
-
"row.costPerSecond": "每秒成本 ($)",
|
|
3167
|
-
"row.priorityDesc": "数字越小优先级越高(默认 100)。多 provider 同 capability 时按此排序",
|
|
3168
|
-
"row.qualityHintDesc": "fast / balanced / quality,供 Agent 选择快速或高质量 provider",
|
|
3169
|
-
"row.costPerCallDesc": "单次调用成本(美元),用于成本追踪",
|
|
3170
|
-
"row.costPerKiloTokenDesc": "按 token 计费时(chat / transcribe)的千 token 成本",
|
|
3171
|
-
"row.costPerSecondDesc": "按秒计费时(t2v / tts)的每秒成本",
|
|
3172
|
-
"row.endpoints": "Endpoints",
|
|
3173
|
-
"row.endpointsDesc": "结构化能力表。Agent 通过 aigc_get_provider_info 读取,无需解析自然语言",
|
|
3174
|
-
"row.endpointsEmpty": "暂无 endpoint。点击下方\"自动探测\"或\"+ 添加 endpoint\"",
|
|
3175
|
-
"row.addEndpoint": "+ 添加 endpoint",
|
|
3176
|
-
"row.editEndpoint": "编辑 endpoint",
|
|
3177
|
-
"row.removeEndpoint": "删除",
|
|
3178
|
-
"row.endpointPath": "Path",
|
|
3179
|
-
"row.endpointMethod": "Method",
|
|
3180
|
-
"row.endpointCapability": "Capability",
|
|
3181
|
-
"row.endpointResponseKind": "响应类型",
|
|
3182
|
-
"row.endpointResponsePath": "响应字段路径",
|
|
3183
|
-
"row.endpointAcceptsCanvasRef": "支持 $base64 占位符",
|
|
3184
|
-
"row.endpointNotes": "备注",
|
|
3185
|
-
"row.endpointParams": "参数",
|
|
3186
|
-
"row.endpointParamsEmpty": "暂无参数",
|
|
3187
|
-
"row.endpointParamName": "名称",
|
|
3188
|
-
"row.endpointParamType": "类型",
|
|
3189
|
-
"row.endpointParamRequired": "必填",
|
|
3190
|
-
"row.endpointParamDefault": "默认值",
|
|
3191
|
-
"row.endpointAddParam": "+ 添加参数",
|
|
3192
|
-
"row.endpointRemoveParam": "删除参数",
|
|
3193
|
-
"row.endpointCancel": "取消",
|
|
3194
|
-
"row.endpointSave": "保存",
|
|
3195
|
-
"row.autoDetect": "自动探测",
|
|
3196
|
-
"row.autoDetectTitle": "让 Agent 用 aigc_probe_endpoint 自动探测响应格式",
|
|
3197
|
-
"row.autoDetectPrompt": "请帮我自动探测 AIGC 供应商「{name}」(id: {id}) 的 endpoint 响应格式:对每个未配置响应类型的 endpoint 调用 aigc_probe_endpoint(会发送一次最小测试请求,apiKey 自动附加),把探测到的 response.kind + response.path 通过 aigc_provider_set_endpoints 保存到 EndpointSpec。如果该 provider 还没有任何 endpoint,请先用 aigc_http_request 探测常见的 endpoint(如 /v1/images/generations、/v1/videos/generations、/v1/audio/speech),再用 aigc_probe_endpoint 探测响应格式。"
|
|
1662
|
+
"badge.default": "默认"
|
|
3198
1663
|
};
|
|
3199
1664
|
const en = {
|
|
3200
1665
|
tabTitle: "AIGC Canvas",
|
|
@@ -3226,28 +1691,6 @@ window.__ModuleLoader__.load({
|
|
|
3226
1691
|
deleteElement: "Delete element",
|
|
3227
1692
|
dropHint: "Drop files onto canvas",
|
|
3228
1693
|
uploading: "Uploading…",
|
|
3229
|
-
menuRegenerate: "Regenerate...",
|
|
3230
|
-
menuUseAsReference: "Use as reference...",
|
|
3231
|
-
menuSendToChat: "Send to chat",
|
|
3232
|
-
menuDownload: "Download",
|
|
3233
|
-
menuPromoteToLibrary: "Promote to library...",
|
|
3234
|
-
menuMarkWinner: "Mark as winner",
|
|
3235
|
-
menuMarkRejected: "Mark as rejected",
|
|
3236
|
-
menuArchive: "Archive",
|
|
3237
|
-
menuSeparator: "─────────────",
|
|
3238
|
-
toolbarGenerate: "+ Generate",
|
|
3239
|
-
toolbarEditSelected: "✂ Edit selected",
|
|
3240
|
-
toolbarRunWorkflow: "▶ Run workflow",
|
|
3241
|
-
toolbarGenerateTitle: "Open the quick-generate dialog (t2i/t2v/tts)",
|
|
3242
|
-
toolbarEditSelectedTitle: "Run an ffmpeg operation on the selected element",
|
|
3243
|
-
toolbarRunWorkflowTitle: "Open the pipeline template picker",
|
|
3244
|
-
toolbarNoSelection: "Select an element on the canvas first",
|
|
3245
|
-
noticeRegenerate: "Please regenerate the element {filePath} using aigc_reroll",
|
|
3246
|
-
noticeUseAsReference: "Please use the following element as a reference for the next generation: {filePath}",
|
|
3247
|
-
noticeSendToChat: "Please use this element as a reference: [filePath: {filePath}, kind: {kind}, title: {title}]",
|
|
3248
|
-
noticeGenerate: "Please generate a new AIGC asset (call aigc_get_provider_info to list available providers, then aigc_http_request to generate, and finally aigc_canvas_place to put the result on the canvas).",
|
|
3249
|
-
noticeEditSelected: "Please edit the selected element with aigc_media_edit (ffmpeg): {filePath} (kind: {kind}, title: {title})",
|
|
3250
|
-
noticeRunWorkflow: "Please list the available pipeline templates and run one (if there is only one, run it directly).",
|
|
3251
1694
|
settingsNav: "AIGC Canvas",
|
|
3252
1695
|
settingsTitle: "AIGC Providers",
|
|
3253
1696
|
settingsIntro: "Configure one or more AIGC providers. Each provider has its own name, API endpoint, key, auth scheme, and usage instructions. The agent reads the provider list via aigc_get_provider_info, calls the API via aigc_http_request (endpoint + apiKey attached automatically), and places generated files onto the canvas with aigc_canvas_place.",
|
|
@@ -3287,75 +1730,7 @@ window.__ModuleLoader__.load({
|
|
|
3287
1730
|
"badge.builtin": "builtin",
|
|
3288
1731
|
"badge.stub": "stub mode",
|
|
3289
1732
|
"badge.real": "real API",
|
|
3290
|
-
"badge.default": "default"
|
|
3291
|
-
logButton: "Logs",
|
|
3292
|
-
logTitle: "Request Log",
|
|
3293
|
-
logClear: "Clear",
|
|
3294
|
-
logEmpty: "No requests logged yet.",
|
|
3295
|
-
logLoading: "Loading…",
|
|
3296
|
-
logError: "Error",
|
|
3297
|
-
logRequestBody: "Request body",
|
|
3298
|
-
logRequestHeaders: "Request headers",
|
|
3299
|
-
logResponseBody: "Response preview",
|
|
3300
|
-
logProducedFile: "Produced file",
|
|
3301
|
-
logLocate: "Locate on canvas",
|
|
3302
|
-
winner: "Winner",
|
|
3303
|
-
statusFilter: "Status",
|
|
3304
|
-
statusReady: "Ready",
|
|
3305
|
-
statusDraft: "Draft",
|
|
3306
|
-
statusRejected: "Rejected",
|
|
3307
|
-
statusArchived: "Archived",
|
|
3308
|
-
compareButton: "Compare",
|
|
3309
|
-
compareTitle: "Compare view",
|
|
3310
|
-
compareClose: "Close",
|
|
3311
|
-
compareSelectWinner: "Select as winner",
|
|
3312
|
-
compareRejectAll: "Reject all",
|
|
3313
|
-
compareCancelSelection: "Cancel selection",
|
|
3314
|
-
compareSeed: "seed",
|
|
3315
|
-
compareCost: "cost",
|
|
3316
|
-
compareDuration: "duration",
|
|
3317
|
-
comparePrompt: "Prompt",
|
|
3318
|
-
compareNoMedia: "No media to display for this element",
|
|
3319
|
-
compareNotEnough: "Select 2-4 elements to compare",
|
|
3320
|
-
compareTooMany: "You can compare at most 4 elements at once",
|
|
3321
|
-
compareClearSelection: "Clear selection",
|
|
3322
|
-
compareNSelected: "{n} selected",
|
|
3323
|
-
"row.priority": "Priority",
|
|
3324
|
-
"row.qualityHint": "Quality",
|
|
3325
|
-
"row.costPerCall": "Cost per call ($)",
|
|
3326
|
-
"row.costPerKiloToken": "Cost per 1k tokens ($)",
|
|
3327
|
-
"row.costPerSecond": "Cost per second ($)",
|
|
3328
|
-
"row.priorityDesc": "Smaller = higher priority (default 100). When multiple providers serve the same capability, they are sorted by this",
|
|
3329
|
-
"row.qualityHintDesc": "fast / balanced / quality — lets the agent pick fast vs. quality providers",
|
|
3330
|
-
"row.costPerCallDesc": "Cost per call in USD (for cost tracking)",
|
|
3331
|
-
"row.costPerKiloTokenDesc": "Per-1k-token cost in USD (for chat / transcribe cost tracking)",
|
|
3332
|
-
"row.costPerSecondDesc": "Per-second cost in USD (for t2v / tts cost tracking)",
|
|
3333
|
-
"row.endpoints": "Endpoints",
|
|
3334
|
-
"row.endpointsDesc": "Structured capability catalog. The agent reads it via aigc_get_provider_info — no natural-language parsing needed",
|
|
3335
|
-
"row.endpointsEmpty": "No endpoints yet. Click \"Auto-detect\" or \"+ Add endpoint\" below",
|
|
3336
|
-
"row.addEndpoint": "+ Add endpoint",
|
|
3337
|
-
"row.editEndpoint": "Edit endpoint",
|
|
3338
|
-
"row.removeEndpoint": "Remove",
|
|
3339
|
-
"row.endpointPath": "Path",
|
|
3340
|
-
"row.endpointMethod": "Method",
|
|
3341
|
-
"row.endpointCapability": "Capability",
|
|
3342
|
-
"row.endpointResponseKind": "Response kind",
|
|
3343
|
-
"row.endpointResponsePath": "Response field path",
|
|
3344
|
-
"row.endpointAcceptsCanvasRef": "Accepts $base64 placeholder",
|
|
3345
|
-
"row.endpointNotes": "Notes",
|
|
3346
|
-
"row.endpointParams": "Parameters",
|
|
3347
|
-
"row.endpointParamsEmpty": "No parameters",
|
|
3348
|
-
"row.endpointParamName": "Name",
|
|
3349
|
-
"row.endpointParamType": "Type",
|
|
3350
|
-
"row.endpointParamRequired": "Required",
|
|
3351
|
-
"row.endpointParamDefault": "Default",
|
|
3352
|
-
"row.endpointAddParam": "+ Add parameter",
|
|
3353
|
-
"row.endpointRemoveParam": "Remove parameter",
|
|
3354
|
-
"row.endpointCancel": "Cancel",
|
|
3355
|
-
"row.endpointSave": "Save",
|
|
3356
|
-
"row.autoDetect": "Auto-detect",
|
|
3357
|
-
"row.autoDetectTitle": "Ask the agent to auto-detect the response shape via aigc_probe_endpoint",
|
|
3358
|
-
"row.autoDetectPrompt": "Please auto-detect the response shape for the AIGC provider \"{name}\" (id: {id}): for every endpoint whose response.kind is not yet set, call aigc_probe_endpoint (it sends ONE minimal test request — apiKey is attached automatically) and save the detected response.kind + response.path into the EndpointSpec via aigc_provider_set_endpoints. If this provider has no endpoints at all yet, first probe common endpoints with aigc_http_request (e.g. /v1/images/generations, /v1/videos/generations, /v1/audio/speech), then probe the response shape with aigc_probe_endpoint."
|
|
1733
|
+
"badge.default": "default"
|
|
3359
1734
|
};
|
|
3360
1735
|
//#endregion
|
|
3361
1736
|
//#region src/client/index.tsx
|