@opengeni/react 5.0.5-canary.3 → 5.2.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +1391 -2
- package/dist/artifacts.js.map +1 -1
- package/dist/chunk-2TKI3WEG.js +73 -0
- package/dist/chunk-2TKI3WEG.js.map +1 -0
- package/dist/{chunk-V3WX3TQG.js → chunk-45XEUVTK.js} +2 -2
- package/dist/{chunk-M2HNWZYR.js → chunk-IHQII5WO.js} +69 -18
- package/dist/chunk-IHQII5WO.js.map +1 -0
- package/dist/{chunk-HUMIRRYI.js → chunk-IYCR4343.js} +19 -19
- package/dist/{chunk-HUMIRRYI.js.map → chunk-IYCR4343.js.map} +1 -1
- package/dist/{chunk-6J56T3MG.js → chunk-NYESIADM.js} +14 -5
- package/dist/chunk-NYESIADM.js.map +1 -0
- package/dist/{chunk-7S2KWUJD.js → chunk-QXJCNVSD.js} +2283 -2215
- package/dist/chunk-QXJCNVSD.js.map +1 -0
- package/dist/{chunk-BV5A4OTE.js → chunk-T5FRXMC3.js} +21 -2
- package/dist/{chunk-BV5A4OTE.js.map → chunk-T5FRXMC3.js.map} +1 -1
- package/dist/{chunk-BRFMGY4L.js → chunk-UGMKPUEM.js} +28 -3
- package/dist/chunk-UGMKPUEM.js.map +1 -0
- package/dist/components/artifacts/inline-html-document.d.ts +2 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +2 -0
- package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
- package/dist/components/markdown.d.ts +13 -1
- package/dist/components/message-timeline.d.ts +4 -1
- package/dist/components/session-conversation.d.ts +3 -0
- package/dist/components/user-message-body.d.ts +10 -1
- package/dist/composer.js +2 -2
- package/dist/{computer-viewer-7TEZC6Y2.js → computer-viewer-AA4JH2AP.js} +2 -2
- package/dist/connect.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +1 -1
- package/dist/{platform-activity-row-27LKAWZD.js → platform-activity-row-UW4UVVM4.js} +6 -3
- package/dist/platform-activity-row-UW4UVVM4.js.map +1 -0
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +6 -5
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +2 -2
- package/dist/sites-ui.d.ts +2 -1
- package/dist/sites.js +12 -4
- package/dist/sites.js.map +1 -1
- package/dist/timeline/activity-rail.d.ts +1 -0
- package/dist/timeline/platform-activity-row.d.ts +1 -1
- package/dist/timeline/retained-image.d.ts +16 -0
- package/dist/timeline/rolling-activity.d.ts +9 -0
- package/dist/timeline/shared.d.ts +4 -1
- package/dist/timeline/turn-summary.d.ts +4 -1
- package/package.json +4 -4
- package/src/artifacts.ts +2 -0
- package/src/components/artifacts/inline-html-document.ts +24 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +32 -2
- package/src/components/artifacts/visualization-assets/base.css +1015 -0
- package/src/components/artifacts/visualization-assets/helpers.html +349 -0
- package/src/components/artifacts/visualization-assets.gen.ts +3 -0
- package/src/components/copy-button.tsx +7 -2
- package/src/components/markdown.tsx +111 -34
- package/src/components/message-timeline.tsx +61 -4
- package/src/components/session-conversation.tsx +5 -0
- package/src/components/user-message-body.tsx +20 -2
- package/src/hooks/use-composer.ts +15 -2
- package/src/hooks/use-computer-session.ts +36 -1
- package/src/index.ts +10 -2
- package/src/session-ui.ts +4 -1
- package/src/sites-ui.tsx +23 -6
- package/src/timeline/activity-rail.tsx +20 -3
- package/src/timeline/platform-activity-row.tsx +5 -2
- package/src/timeline/retained-image.ts +91 -0
- package/src/timeline/rolling-activity.tsx +83 -0
- package/src/timeline/shared.tsx +33 -12
- package/src/timeline/tool-renderers.tsx +7 -91
- package/src/timeline/turn-summary.tsx +21 -15
- package/styles/compiled.css +122 -0
- package/styles/index.css +49 -0
- package/dist/chunk-6J56T3MG.js.map +0 -1
- package/dist/chunk-7S2KWUJD.js.map +0 -1
- package/dist/chunk-BRFMGY4L.js.map +0 -1
- package/dist/chunk-M2HNWZYR.js.map +0 -1
- package/dist/platform-activity-row-27LKAWZD.js.map +0 -1
- /package/dist/{chunk-V3WX3TQG.js.map → chunk-45XEUVTK.js.map} +0 -0
- /package/dist/{computer-viewer-7TEZC6Y2.js.map → computer-viewer-AA4JH2AP.js.map} +0 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
<!--__INLINE_VISUALIZATION_FRAGMENT__-->
|
|
2
|
+
<script src="https://unpkg.com/@floating-ui/core@1.7.3/dist/floating-ui.core.umd.min.js"></script>
|
|
3
|
+
<script src="https://unpkg.com/@floating-ui/dom@1.7.4/dist/floating-ui.dom.umd.min.js"></script>
|
|
4
|
+
<script>
|
|
5
|
+
(() => {
|
|
6
|
+
if (window.FloatingUIDOM == null) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const { autoUpdate, computePosition, flip, offset, shift, size } = window.FloatingUIDOM;
|
|
10
|
+
const tooltipPlacements = new Set(["top", "right", "bottom", "left"]);
|
|
11
|
+
const tooltipId = `codex-visualization-tooltip-${
|
|
12
|
+
window.crypto?.randomUUID?.() ?? Date.now().toString(36)
|
|
13
|
+
}`;
|
|
14
|
+
const tooltipGap = 5;
|
|
15
|
+
const hoverDelay = 700;
|
|
16
|
+
const skipDelay = 300;
|
|
17
|
+
let activeTrigger = null;
|
|
18
|
+
let pendingTrigger = null;
|
|
19
|
+
let tooltip = null;
|
|
20
|
+
let openTimeout = null;
|
|
21
|
+
let autoUpdateCleanup = null;
|
|
22
|
+
let positionRequest = 0;
|
|
23
|
+
let skipDelayUntil = 0;
|
|
24
|
+
let lastPointerWasTouch = false;
|
|
25
|
+
let pinnedByTouch = false;
|
|
26
|
+
|
|
27
|
+
const getTrigger = (target) =>
|
|
28
|
+
target?.nodeType === Node.ELEMENT_NODE ? target.closest("[data-tooltip]") : null;
|
|
29
|
+
const containsTarget = (trigger, target) =>
|
|
30
|
+
target?.nodeType != null && trigger.contains(target);
|
|
31
|
+
const getContent = (trigger) => trigger.getAttribute("data-tooltip")?.trim() ?? "";
|
|
32
|
+
const getPlacement = (trigger) => {
|
|
33
|
+
const placement = trigger.getAttribute("data-tooltip-placement");
|
|
34
|
+
return tooltipPlacements.has(placement) ? placement : "top";
|
|
35
|
+
};
|
|
36
|
+
const getTooltip = () => {
|
|
37
|
+
if (tooltip == null) {
|
|
38
|
+
tooltip = document.createElement("div");
|
|
39
|
+
tooltip.id = tooltipId;
|
|
40
|
+
tooltip.className = "tooltip";
|
|
41
|
+
tooltip.setAttribute("role", "tooltip");
|
|
42
|
+
}
|
|
43
|
+
return tooltip;
|
|
44
|
+
};
|
|
45
|
+
const setDescribedBy = (trigger, described) => {
|
|
46
|
+
const ids = new Set(
|
|
47
|
+
(trigger.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean),
|
|
48
|
+
);
|
|
49
|
+
if (described) {
|
|
50
|
+
ids.add(tooltipId);
|
|
51
|
+
} else {
|
|
52
|
+
ids.delete(tooltipId);
|
|
53
|
+
}
|
|
54
|
+
if (ids.size === 0) {
|
|
55
|
+
trigger.removeAttribute("aria-describedby");
|
|
56
|
+
} else {
|
|
57
|
+
trigger.setAttribute("aria-describedby", [...ids].join(" "));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const clearOpenTimeout = () => {
|
|
61
|
+
if (openTimeout != null) {
|
|
62
|
+
window.clearTimeout(openTimeout);
|
|
63
|
+
openTimeout = null;
|
|
64
|
+
}
|
|
65
|
+
pendingTrigger = null;
|
|
66
|
+
};
|
|
67
|
+
const closeTooltip = (activateSkipDelay = true) => {
|
|
68
|
+
clearOpenTimeout();
|
|
69
|
+
pinnedByTouch = false;
|
|
70
|
+
positionRequest += 1;
|
|
71
|
+
autoUpdateCleanup?.();
|
|
72
|
+
autoUpdateCleanup = null;
|
|
73
|
+
if (activeTrigger == null) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
setDescribedBy(activeTrigger, false);
|
|
77
|
+
activeTrigger = null;
|
|
78
|
+
tooltip?.remove();
|
|
79
|
+
if (activateSkipDelay) {
|
|
80
|
+
skipDelayUntil = Date.now() + skipDelay;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const updatePosition = (trigger, floating) => {
|
|
84
|
+
const request = ++positionRequest;
|
|
85
|
+
void computePosition(trigger, floating, {
|
|
86
|
+
middleware: [
|
|
87
|
+
offset(tooltipGap),
|
|
88
|
+
flip({ padding: tooltipGap }),
|
|
89
|
+
shift({ padding: tooltipGap }),
|
|
90
|
+
size({
|
|
91
|
+
padding: tooltipGap,
|
|
92
|
+
apply({ availableHeight, availableWidth, elements }) {
|
|
93
|
+
elements.floating.style.setProperty(
|
|
94
|
+
"--tooltip-available-width",
|
|
95
|
+
`${Math.max(0, availableWidth)}px`,
|
|
96
|
+
);
|
|
97
|
+
elements.floating.style.setProperty(
|
|
98
|
+
"--tooltip-available-height",
|
|
99
|
+
`${Math.max(0, availableHeight)}px`,
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
],
|
|
104
|
+
placement: getPlacement(trigger),
|
|
105
|
+
strategy: "fixed",
|
|
106
|
+
})
|
|
107
|
+
.then(({ x, y }) => {
|
|
108
|
+
if (activeTrigger !== trigger || request !== positionRequest) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const scale = window.devicePixelRatio || 1;
|
|
112
|
+
floating.style.transform = `translate(${Math.round(x * scale) / scale}px, ${Math.round(y * scale) / scale}px)`;
|
|
113
|
+
floating.style.visibility = "visible";
|
|
114
|
+
})
|
|
115
|
+
.catch(() => {
|
|
116
|
+
if (activeTrigger === trigger && request === positionRequest) {
|
|
117
|
+
closeTooltip(false);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
const openTooltip = (trigger) => {
|
|
122
|
+
const content = getContent(trigger);
|
|
123
|
+
if (!trigger.isConnected || content.length === 0) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
clearOpenTimeout();
|
|
127
|
+
if (activeTrigger === trigger) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
closeTooltip();
|
|
131
|
+
const floating = getTooltip();
|
|
132
|
+
floating.textContent = content;
|
|
133
|
+
floating.style.visibility = "hidden";
|
|
134
|
+
floating.style.transform = "translate(0, 0)";
|
|
135
|
+
document.body.appendChild(floating);
|
|
136
|
+
activeTrigger = trigger;
|
|
137
|
+
setDescribedBy(trigger, true);
|
|
138
|
+
autoUpdateCleanup = autoUpdate(trigger, floating, () => {
|
|
139
|
+
updatePosition(trigger, floating);
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
const requestOpen = (trigger, immediate = false) => {
|
|
143
|
+
if (
|
|
144
|
+
getContent(trigger).length === 0 ||
|
|
145
|
+
activeTrigger === trigger ||
|
|
146
|
+
pendingTrigger === trigger
|
|
147
|
+
) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
clearOpenTimeout();
|
|
151
|
+
pendingTrigger = trigger;
|
|
152
|
+
const delay =
|
|
153
|
+
immediate || activeTrigger != null || Date.now() < skipDelayUntil ? 0 : hoverDelay;
|
|
154
|
+
if (delay === 0) {
|
|
155
|
+
openTooltip(trigger);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
openTimeout = window.setTimeout(() => {
|
|
159
|
+
openTimeout = null;
|
|
160
|
+
pendingTrigger = null;
|
|
161
|
+
openTooltip(trigger);
|
|
162
|
+
}, delay);
|
|
163
|
+
};
|
|
164
|
+
const handlePointerOpen = (event) => {
|
|
165
|
+
if (event.pointerType === "touch") {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const trigger = getTrigger(event.target);
|
|
169
|
+
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
requestOpen(trigger);
|
|
173
|
+
};
|
|
174
|
+
const handlePointer = (event) => {
|
|
175
|
+
lastPointerWasTouch = event.type === "pointerdown" && event.pointerType === "touch";
|
|
176
|
+
};
|
|
177
|
+
const handleTouchClick = (event) => {
|
|
178
|
+
const isTouch = event.pointerType === "touch" || lastPointerWasTouch;
|
|
179
|
+
lastPointerWasTouch = false;
|
|
180
|
+
if (!isTouch || event.detail === 0) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const trigger = getTrigger(event.target);
|
|
184
|
+
if (
|
|
185
|
+
trigger == null ||
|
|
186
|
+
!trigger.isConnected ||
|
|
187
|
+
getContent(trigger).length === 0 ||
|
|
188
|
+
(pinnedByTouch && activeTrigger === trigger)
|
|
189
|
+
) {
|
|
190
|
+
if (pinnedByTouch) {
|
|
191
|
+
closeTooltip();
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
openTooltip(trigger);
|
|
196
|
+
pinnedByTouch = activeTrigger === trigger;
|
|
197
|
+
window.setTimeout(() => {
|
|
198
|
+
if (activeTrigger === trigger && !trigger.isConnected) {
|
|
199
|
+
closeTooltip(false);
|
|
200
|
+
}
|
|
201
|
+
}, 0);
|
|
202
|
+
};
|
|
203
|
+
const handleLeave = (event) => {
|
|
204
|
+
if (pinnedByTouch) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const trigger = getTrigger(event.target);
|
|
208
|
+
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (activeTrigger === trigger || pendingTrigger === trigger) {
|
|
212
|
+
closeTooltip();
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const handleFocusIn = (event) => {
|
|
216
|
+
const trigger = getTrigger(event.target);
|
|
217
|
+
if (trigger?.matches(":focus-visible")) {
|
|
218
|
+
requestOpen(trigger, true);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const handleKeyDown = (event) => {
|
|
222
|
+
if (event.key === "Escape") {
|
|
223
|
+
closeTooltip();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const handleContextMenu = (event) => {
|
|
227
|
+
lastPointerWasTouch = false;
|
|
228
|
+
const trigger = getTrigger(event.target);
|
|
229
|
+
if (activeTrigger === trigger || pendingTrigger === trigger) {
|
|
230
|
+
closeTooltip();
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const destroy = () => {
|
|
234
|
+
closeTooltip(false);
|
|
235
|
+
lastPointerWasTouch = false;
|
|
236
|
+
document.removeEventListener("pointerover", handlePointerOpen);
|
|
237
|
+
document.removeEventListener("pointerdown", handlePointer);
|
|
238
|
+
document.removeEventListener("pointercancel", handlePointer);
|
|
239
|
+
document.removeEventListener("click", handleTouchClick);
|
|
240
|
+
document.removeEventListener("pointerout", handleLeave);
|
|
241
|
+
document.removeEventListener("focusin", handleFocusIn);
|
|
242
|
+
document.removeEventListener("focusout", handleLeave);
|
|
243
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
244
|
+
document.removeEventListener("contextmenu", handleContextMenu);
|
|
245
|
+
tooltip = null;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
document.addEventListener("pointerover", handlePointerOpen);
|
|
249
|
+
document.addEventListener("pointerdown", handlePointer);
|
|
250
|
+
document.addEventListener("pointercancel", handlePointer);
|
|
251
|
+
document.addEventListener("click", handleTouchClick);
|
|
252
|
+
document.addEventListener("pointerout", handleLeave);
|
|
253
|
+
document.addEventListener("focusin", handleFocusIn);
|
|
254
|
+
document.addEventListener("focusout", handleLeave);
|
|
255
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
256
|
+
document.addEventListener("contextmenu", handleContextMenu);
|
|
257
|
+
window.addEventListener("pagehide", destroy, { once: true });
|
|
258
|
+
})();
|
|
259
|
+
</script>
|
|
260
|
+
<script id="codex-visualization-tabs">
|
|
261
|
+
(() => {
|
|
262
|
+
const selector = '.nav[role="tablist"] [role="tab"]';
|
|
263
|
+
const disabled = ':disabled,[aria-disabled="true"]';
|
|
264
|
+
const selectTab = (selected) => {
|
|
265
|
+
const panelId = selected.getAttribute("aria-controls");
|
|
266
|
+
for (const tab of selected.closest('[role="tablist"]').querySelectorAll('[role="tab"]')) {
|
|
267
|
+
const active = tab === selected;
|
|
268
|
+
tab.classList.toggle("active", active);
|
|
269
|
+
tab.setAttribute("aria-selected", String(active));
|
|
270
|
+
const id = tab.getAttribute("aria-controls");
|
|
271
|
+
const panel = id && document.getElementById(id);
|
|
272
|
+
if (panel != null) {
|
|
273
|
+
panel.hidden = id !== panelId;
|
|
274
|
+
if (!panel.hidden && selected.id) {
|
|
275
|
+
panel.setAttribute("aria-labelledby", selected.id);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const interact = (event) => {
|
|
281
|
+
const tab = event.target?.closest?.(selector);
|
|
282
|
+
if (event.defaultPrevented || tab == null || tab.matches(disabled)) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (event.type === "click") {
|
|
286
|
+
selectTab(tab);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const tabs = Array.from(
|
|
290
|
+
tab.closest('[role="tablist"]').querySelectorAll('[role="tab"]'),
|
|
291
|
+
).filter((candidate) => !candidate.matches(disabled));
|
|
292
|
+
const current = tabs.indexOf(tab);
|
|
293
|
+
let next;
|
|
294
|
+
if (event.key === "ArrowRight" || event.key === "ArrowDown") {
|
|
295
|
+
next = (current + 1) % tabs.length;
|
|
296
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
|
|
297
|
+
next = (current + tabs.length - 1) % tabs.length;
|
|
298
|
+
} else if (event.key === "Home") {
|
|
299
|
+
next = 0;
|
|
300
|
+
} else if (event.key === "End") {
|
|
301
|
+
next = tabs.length - 1;
|
|
302
|
+
} else {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
event.preventDefault();
|
|
306
|
+
tabs[next].focus();
|
|
307
|
+
selectTab(tabs[next]);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
for (const list of document.querySelectorAll('.nav[role="tablist"]')) {
|
|
311
|
+
const selected =
|
|
312
|
+
list.querySelector('[role="tab"][aria-selected="true"]') ??
|
|
313
|
+
list.querySelector('[role="tab"].active') ??
|
|
314
|
+
list.querySelector('[role="tab"]');
|
|
315
|
+
if (selected != null) {
|
|
316
|
+
selectTab(selected);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
document.addEventListener("click", interact);
|
|
320
|
+
document.addEventListener("keydown", interact);
|
|
321
|
+
window.addEventListener(
|
|
322
|
+
"pagehide",
|
|
323
|
+
() => {
|
|
324
|
+
document.removeEventListener("click", interact);
|
|
325
|
+
document.removeEventListener("keydown", interact);
|
|
326
|
+
},
|
|
327
|
+
{ once: true },
|
|
328
|
+
);
|
|
329
|
+
})();
|
|
330
|
+
</script>
|
|
331
|
+
<script
|
|
332
|
+
id="codex-visualization-lucide"
|
|
333
|
+
async
|
|
334
|
+
src="https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js"
|
|
335
|
+
></script>
|
|
336
|
+
<script>
|
|
337
|
+
(() => {
|
|
338
|
+
const initialize = () => {
|
|
339
|
+
globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });
|
|
340
|
+
};
|
|
341
|
+
if (globalThis.lucide != null) {
|
|
342
|
+
initialize();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
document
|
|
346
|
+
.getElementById("codex-visualization-lucide")
|
|
347
|
+
?.addEventListener("load", initialize, { once: true });
|
|
348
|
+
})();
|
|
349
|
+
</script>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Generated by scripts/generate-visualization-assets.ts.
|
|
2
|
+
export const VISUALIZATION_CSS = "/* Adapted from Codex visualize 1.0.32; shared with the OpenGeni visualization skill. */\n:root {\n color-scheme: light dark;\n background-color: var(\n --background,\n var(--color-background-primary, light-dark(rgb(255 255 255), rgb(24 24 24)))\n ) !important;\n\n /* Agent-facing contract; keep in sync with SKILL.md. */\n --background: var(\n --color-background-primary,\n light-dark(rgb(255 255 255), rgb(24 24 24))\n );\n --foreground: var(\n --color-text-primary,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --card: color-mix(in oklab, var(--foreground) 5%, var(--background));\n --card-foreground: var(\n --color-text-primary,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --popover: var(\n --color-background-secondary,\n light-dark(rgb(255 255 255), rgb(45 45 45))\n );\n --popover-foreground: var(\n --color-text-primary,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --primary: var(\n --color-text-info,\n light-dark(rgb(51 156 255), rgb(131 195 255))\n );\n --primary-foreground: var(\n --color-text-inverse,\n light-dark(rgb(255 255 255), rgb(13 13 13))\n );\n --secondary: var(\n --color-background-secondary,\n light-dark(rgb(255 255 255 / 96%), rgb(54 54 54 / 96%))\n );\n --secondary-foreground: var(\n --color-text-primary,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --muted: color-mix(in srgb, var(--foreground) 10%, transparent);\n --muted-foreground: var(\n --color-text-secondary,\n light-dark(rgb(26 28 31 / 49.4%), rgb(255 255 255 / 49.8%))\n );\n --accent: var(\n --color-background-info,\n light-dark(rgb(229 242 255), rgb(13 39 63))\n );\n --accent-foreground: var(\n --color-text-info,\n light-dark(rgb(51 156 255), rgb(131 195 255))\n );\n --destructive: var(\n --color-text-warning,\n light-dark(rgb(226 85 7), rgb(255 133 73))\n );\n --border: var(\n --color-border-secondary,\n light-dark(rgb(26 28 31 / 8%), rgb(255 255 255 / 8.2%))\n );\n --input: var(\n --color-border-primary,\n light-dark(\n rgb(26 28 31 / 11.8%),\n color-mix(in oklab, rgb(0 0 0) 10%, transparent)\n )\n );\n --ring: var(\n --color-ring-primary,\n light-dark(rgb(51 156 255), rgb(131 195 255 / 76%))\n );\n --font-size-base: var(--font-text-md-size, 14px);\n --blue: light-dark(rgb(51 156 255), rgb(51 156 255));\n --orange: light-dark(rgb(226 85 7), rgb(251 106 34));\n --green: light-dark(rgb(0 162 64), rgb(64 201 119));\n --red: light-dark(rgb(224 46 42), rgb(255 103 100));\n --purple: light-dark(rgb(146 79 247), rgb(173 123 249));\n --yellow: light-dark(rgb(255 195 0), rgb(255 210 64));\n --viz-series-1: var(--primary);\n --viz-series-2: light-dark(rgb(243 136 59), rgb(245 154 86));\n --viz-series-3: light-dark(rgb(93 201 119), rgb(116 213 139));\n --viz-series-4: light-dark(rgb(235 119 177), rgb(240 143 192));\n --viz-series-5: light-dark(rgb(155 121 236), rgb(170 145 239));\n --viz-series-6: light-dark(rgb(58 185 177), rgb(90 203 194));\n\n /* Internal implementation variables; not part of the agent contract. */\n --font-sans: -apple-system, system-ui, \"Segoe UI\", sans-serif;\n --font-mono:\n ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\",\n monospace;\n --font-size-normal: max(11px, var(--font-size-base));\n --font-size-tooltip: calc(var(--font-size-base) - 1px);\n --font-size-small: max(11px, calc(var(--font-size-base) - 2px));\n --font-size-h1: calc(var(--font-size-normal) * 1.7142857143);\n --font-size-h2: calc(var(--font-size-normal) * 1.4285714286);\n --font-size-h3: calc(var(--font-size-normal) * 1.2857142857);\n --font-weight-normal: 430;\n --font-weight-medium: 500;\n --icon-size: 16px;\n --line-height-normal: calc(var(--font-size-normal) * 1.5);\n --line-height-tooltip: calc(var(--font-size-tooltip) * 1.4285714286);\n --line-height-small: calc(var(--font-size-small) + 4px);\n --corner-shape: round;\n --radius: var(--border-radius-lg, 10px);\n --radius-sm: calc(var(--radius) * 0.6);\n --radius-md: calc(var(--radius) * 0.8);\n --radius-lg: var(--radius);\n --radius-2xl: calc(var(--radius) * 1.6);\n --radius-full: 9999px;\n --shadow-sm: 0 1px 2px -1px rgb(0 0 0 / 8%);\n --checkmark-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 17 17'%3E%3Cpath d='M 12.8961 3.64101C13.1297 3.41418 13.4984 3.37523 13.7779 3.56581C14.0571 3.75635 14.1554 4.11331 14.0299 4.41347L13.9615 4.53847L7.71151 13.7045C7.59411 13.8767 7.4063 13.9877 7.19881 14.0072C6.99136 14.0267 6.78564 13.9533 6.63826 13.806L2.88826 10.056L2.79842 9.9457C2.6192 9.67407 2.64927 9.30496 2.88826 9.06581C3.12738 8.82669 3.49647 8.79676 3.76815 8.97597L3.8785 9.06581L7.03084 12.2182L12.8053 3.74941L12.8961 3.64101Z'/%3E%3C/svg%3E\");\n\n /* Legacy aliases; not part of the current agent contract. */\n --viz-bg: transparent;\n --viz-panel: var(--card);\n --viz-border: var(--border);\n --viz-text: var(--foreground);\n --viz-muted: var(--muted-foreground);\n --viz-accent: var(--primary);\n --viz-accent-text: var(--primary-foreground);\n --viz-accent-bg: var(--accent);\n --viz-font-size: var(--font-size-base);\n --viz-warning: var(--destructive);\n}\n\n:root[data-theme=\"light\"] {\n color-scheme: light;\n}\n\n:root[data-theme=\"dark\"] {\n color-scheme: dark;\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml > body {\n /* Preserve MCP aliases for older inline renderers without creating :root cycles. */\n --color-background-primary: var(\n --background,\n light-dark(rgb(255 255 255), rgb(24 24 24))\n );\n --color-text-primary: var(\n --foreground,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --color-border-secondary: var(\n --border,\n light-dark(rgb(26 28 31 / 8%), rgb(255 255 255 / 8.2%))\n );\n\n margin: 0;\n padding: var(--visualize-paint-gutter, 16px);\n color: var(--foreground);\n background: transparent !important;\n font-family: var(--font-sans) !important;\n font-size: var(--font-size-normal);\n font-weight: var(--font-weight-normal);\n line-height: var(--line-height-normal);\n}\n\na {\n color: color-mix(in srgb, var(--viz-accent) 80%, var(--viz-text) 20%);\n cursor: pointer;\n font-weight: var(--font-weight-medium, 500);\n text-decoration: none;\n text-underline-offset: 2px;\n}\n\na:is(:hover, :focus-visible) {\n text-decoration-line: underline;\n text-decoration-style: dashed;\n text-decoration-thickness: 0.5px;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n margin-block: 0;\n}\n\nh1 {\n font-size: var(--font-size-h1);\n font-weight: var(--font-weight-medium);\n line-height: 1.25;\n}\n\nh2 {\n font-size: var(--font-size-h2);\n font-weight: var(--font-weight-medium);\n line-height: 1.25;\n}\n\nh3,\nh4,\nh5,\nh6 {\n font-size: var(--font-size-h3);\n font-weight: var(--font-weight-medium);\n line-height: 1.3;\n}\n\nb,\nstrong,\nth {\n font-weight: var(--font-weight-medium);\n}\n\ncode:not(pre code) {\n display: inline;\n padding: 1px 6px;\n border-radius: var(--radius-sm);\n corner-shape: var(--corner-shape);\n background: var(--muted);\n font-family: var(--font-mono);\n font-size: 0.92em;\n overflow-wrap: anywhere;\n -webkit-box-decoration-break: clone;\n box-decoration-break: clone;\n word-break: break-word;\n}\n\n.table-responsive {\n width: 100%;\n overflow-x: auto;\n scrollbar-width: thin;\n}\n\n.table {\n width: 100%;\n border-collapse: collapse;\n color: var(--foreground);\n font: inherit;\n text-align: start;\n}\n\n.table :is(th, td) {\n padding-block: 10px;\n padding-inline: 0 24px;\n overflow-wrap: anywhere;\n border-bottom: 1px solid var(--border);\n text-align: start;\n vertical-align: top;\n}\n\n.table-responsive > .table :is(th, td) {\n overflow-wrap: break-word;\n}\n\n.table :is(th, td):last-child {\n padding-inline-end: 0;\n}\n\n.table :is(caption, thead th) {\n font-weight: 600;\n}\n\n.table thead th {\n padding-block: 8px;\n border-bottom-color: color-mix(in srgb, var(--foreground) 16%, transparent);\n}\n\n.table tbody tr:last-child :is(th, td) {\n border-bottom: 0;\n}\n\n.table.table-sm :is(th, td) {\n padding-block: 6px;\n}\n\n.table.table-sm :is(th, td):not(:last-child) {\n padding-inline-end: 16px;\n}\n\n.table :is(.text-end, [align=\"right\"]) {\n text-align: end;\n font-variant-numeric: tabular-nums;\n}\n\n.table :is(.text-center, [align=\"center\"]) {\n text-align: center;\n}\n\n.table .text-nowrap {\n white-space: nowrap;\n}\n\n#widget {\n display: flex;\n flex-direction: column;\n gap: 12px;\n width: 100%;\n padding: 0;\n background: transparent !important;\n}\n\n.card {\n min-width: 0;\n padding: 12px;\n overflow: hidden;\n overflow-wrap: break-word;\n border-radius: var(--radius-2xl);\n corner-shape: var(--corner-shape);\n color: var(--card-foreground);\n background: var(--card);\n}\n\n#widget > :not(.card, .progress) {\n width: 100% !important;\n max-width: none !important;\n margin: 0 !important;\n padding: 0 !important;\n border: 0 !important;\n border-radius: 0 !important;\n background: transparent !important;\n box-shadow: none !important;\n}\n\n.tooltip {\n position: fixed;\n z-index: 50;\n top: 0;\n left: 0;\n width: max-content;\n max-width: min(\n 20rem,\n var(--tooltip-available-width, calc(100vw - 10px)),\n calc(100vw - 10px)\n );\n max-height: min(\n var(--tooltip-available-height, calc(100vh - 10px)),\n calc(100vh - 10px)\n );\n padding: 4px 8px;\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n corner-shape: var(--corner-shape);\n color: var(--popover-foreground);\n background: var(--popover);\n box-shadow: none;\n font-size: var(--font-size-tooltip);\n line-height: var(--line-height-tooltip);\n overflow-wrap: break-word;\n white-space: normal;\n pointer-events: none;\n user-select: none;\n}\n\n.viz-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(max(180px, 24%), 1fr));\n gap: 10px;\n}\n\n.viz-stat {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.viz-stat-value {\n font-size: var(--font-size-h2);\n font-weight: var(--font-weight-medium);\n line-height: 1.25;\n}\n\n.viz-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n}\n\nhr {\n width: 100%;\n height: 1px;\n margin-block: 6px;\n border: 0;\n background: var(--border);\n}\n\n.nav {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 4px;\n}\n\n.progress {\n display: flex;\n height: 8px;\n margin-block: calc((var(--line-height-normal, 1.5em) - 8px) / 2);\n overflow: hidden;\n border: 0;\n border-radius: var(--radius-full);\n background: var(--muted);\n}\n\n.progress-bar:only-child {\n border-radius: var(--radius-full);\n}\n\n.progress-bar {\n height: 100%;\n flex-shrink: 0;\n border: 0;\n background: var(--viz-series-1);\n}\n\n.viz-badge {\n padding: 3px 8px;\n border-radius: var(--radius-full);\n color: var(--accent-foreground);\n background: var(--accent);\n font-weight: var(--font-weight-medium);\n}\n\nsmall,\n.text-small,\n.viz-badge {\n font-size: var(--font-size-small);\n line-height: var(--line-height-small);\n}\n\n.text-muted {\n color: var(--muted-foreground);\n}\n\n.tabular-nums {\n font-variant-numeric: tabular-nums;\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.viz-controls {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n\n.viz-controls > .form-label {\n display: grid;\n min-width: min(100%, 260px);\n flex: 1 1 280px;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: baseline;\n gap: 2px 12px;\n margin-bottom: 0;\n}\n\n.viz-controls > .form-label > :is(.form-control, .form-range, .form-select) {\n grid-column: 1 / -1;\n}\n\n.btn,\n.nav-link,\n.form-check-input,\n.form-control,\n.form-range,\n.form-select {\n font: inherit;\n}\n\n.btn,\n.nav-link {\n appearance: button;\n display: inline-flex;\n inline-size: fit-content;\n max-inline-size: 100%;\n min-height: 28px;\n align-items: center;\n justify-content: center;\n gap: 4px;\n margin: 0;\n padding: 0 8px;\n -webkit-app-region: no-drag;\n border: 1px solid var(--input);\n border-radius: var(--radius-lg);\n corner-shape: var(--corner-shape);\n color: var(--secondary-foreground);\n background: var(--secondary);\n cursor: var(--cursor-interaction, pointer);\n text-align: center;\n text-decoration: none;\n white-space: nowrap;\n user-select: none;\n -webkit-font-smoothing: antialiased;\n}\n\n.btn:is(.btn-block, .viz-tile) {\n inline-size: 100%;\n}\n\n.btn.viz-tile {\n min-width: 0;\n overflow-wrap: anywhere;\n white-space: normal;\n}\n\na.btn {\n cursor: pointer;\n text-decoration: none;\n}\n\n.btn:not(:disabled):hover,\n.nav-link:not(:disabled):not([aria-disabled=\"true\"]):hover {\n background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));\n}\n\n.btn-primary {\n border-color: transparent;\n color: var(--primary-foreground);\n background: var(--foreground);\n background-clip: padding-box;\n}\n\n.btn-primary .text-muted {\n color: color-mix(in srgb, var(--primary-foreground) 50%, transparent);\n}\n\n.btn-primary:not(:disabled):hover {\n background: color-mix(in srgb, var(--foreground) 80%, transparent);\n background-clip: padding-box;\n}\n\n.btn-ghost {\n border-color: transparent;\n color: var(--muted-foreground);\n background: transparent;\n}\n\n.btn-ghost:not(:disabled):hover {\n color: var(--foreground);\n background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));\n}\n\n.nav-pills .nav-link {\n min-width: 0;\n border-color: transparent;\n color: var(--muted-foreground);\n background: transparent;\n}\n\n.nav-justified .nav-link {\n flex: 1 1 0;\n overflow-wrap: anywhere;\n white-space: normal;\n}\n\n.nav-pills .nav-link.active {\n color: var(--foreground);\n background: var(--muted);\n}\n\n.btn:disabled,\n.nav-link:disabled,\n.nav-link[aria-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n[data-lucide] {\n stroke-width: 1.6;\n}\n\n.form-label {\n display: block;\n margin-bottom: 6px;\n color: var(--foreground);\n}\n\n.form-control {\n display: block;\n width: 100%;\n min-height: 28px;\n padding: 0 8px;\n outline: none;\n border: 1px solid var(--input);\n border-radius: var(--radius-lg);\n corner-shape: var(--corner-shape);\n color: var(--foreground);\n background: var(--secondary);\n}\n\n.form-control::placeholder {\n color: var(--muted-foreground);\n}\n\n.form-control[type=\"file\"] {\n padding: 0;\n overflow: hidden;\n cursor: var(--cursor-interaction, pointer);\n}\n\n.form-control[type=\"file\"]::file-selector-button {\n min-height: 26px;\n margin-right: 8px;\n padding: 0 8px;\n border: 0;\n border-right: 1px solid var(--input);\n color: var(--secondary-foreground);\n background: var(--secondary);\n cursor: inherit;\n font: inherit;\n}\n\n.form-control[type=\"file\"]:not(:disabled):hover::file-selector-button {\n background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));\n}\n\n.form-control-color[type=\"color\"] {\n width: 40px;\n height: 28px;\n padding: 3px;\n cursor: var(--cursor-interaction, pointer);\n}\n\n.form-control-color[type=\"color\"]::-webkit-color-swatch-wrapper {\n padding: 0;\n}\n\n.form-control-color[type=\"color\"]::-webkit-color-swatch {\n border: 0;\n border-radius: calc(var(--radius-lg) - 4px);\n corner-shape: var(--corner-shape);\n}\n\ntextarea.form-control {\n height: auto;\n min-height: 72px;\n padding: 8px 10px;\n resize: vertical;\n}\n\n.form-control:focus-visible {\n border-color: var(--ring);\n box-shadow: inset 0 0 0 1px var(--ring);\n}\n\n.form-control:disabled,\n.form-select:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.form-select {\n appearance: none;\n display: block;\n width: 100%;\n min-height: 28px;\n margin: 0;\n padding: 0 32px 0 8px;\n outline: none;\n border: 1px solid var(--input);\n border-radius: var(--radius-lg);\n corner-shape: var(--corner-shape);\n color: var(--foreground);\n background-color: var(--secondary);\n background-image:\n linear-gradient(45deg, transparent 50%, var(--muted-foreground) 50%),\n linear-gradient(135deg, var(--muted-foreground) 50%, transparent 50%);\n background-position:\n calc(100% - 14px) 50%,\n calc(100% - 10px) 50%;\n background-repeat: no-repeat;\n background-size: 4px 4px;\n cursor: var(--cursor-interaction, default);\n}\n\n.form-select:not(:disabled):hover {\n background-color: color-mix(in srgb, var(--foreground) 6%, var(--secondary));\n}\n\n.form-select:focus-visible {\n border-color: var(--ring);\n box-shadow: inset 0 0 0 1px var(--ring);\n}\n\n.form-check {\n display: flex;\n min-height: 20px;\n align-items: center;\n gap: 6px;\n}\n\n.form-check-input {\n appearance: none;\n width: 14px;\n height: 14px;\n flex: 0 0 auto;\n margin: 0;\n border: 1px solid var(--input);\n color: var(--primary-foreground);\n background-color: transparent;\n cursor: var(--cursor-interaction, default);\n transition:\n background-color 150ms,\n border-color 150ms,\n box-shadow 150ms;\n}\n\n.form-check:not(.form-switch) .form-check-input[type=\"checkbox\"] {\n border-color: var(--input);\n border-radius: var(--radius-sm);\n corner-shape: var(--corner-shape);\n background-color: var(--secondary);\n box-shadow: var(--shadow-sm);\n}\n\n.form-check:not(.form-switch)\n .form-check-input:not(:disabled):not(:checked):hover {\n background-color: var(--card);\n}\n\n.form-check:not(.form-switch) .form-check-input[type=\"checkbox\"]:checked {\n border-color: var(--primary);\n background-color: var(--primary);\n}\n\n.form-check:not(.form-switch)\n .form-check-input[type=\"checkbox\"]:checked::before {\n display: block;\n width: 100%;\n height: 100%;\n background: var(--primary-foreground);\n content: \"\";\n mask: var(--checkmark-image) center / 12px 12px no-repeat;\n}\n\n.form-check-input[type=\"radio\"] {\n width: 14px;\n height: 14px;\n border-radius: var(--radius-full);\n}\n\n.form-check-input[type=\"radio\"]:checked {\n border: 2px solid var(--primary);\n background:\n radial-gradient(circle, var(--primary-foreground) 0 2.5px, transparent 3px),\n var(--primary);\n}\n\n.form-check:not(.form-switch) .form-check-input:focus-visible {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n}\n\n.form-check:not(.form-switch) .form-check-input:disabled {\n cursor: not-allowed;\n pointer-events: none;\n}\n\n.form-check:not(.form-switch) .form-check-input:disabled + .form-check-label {\n cursor: not-allowed;\n}\n\n.form-switch .form-check-input:disabled,\n.form-switch .form-check-input:disabled + .form-check-label {\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.form-check-label {\n color: var(--foreground);\n cursor: var(--cursor-interaction, default);\n}\n\n.form-switch .form-check-input {\n position: relative;\n width: 32px;\n height: 20px;\n border: 0;\n border-radius: var(--radius-full);\n background: var(--muted);\n box-shadow: none;\n transition: background-color 200ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n.form-switch .form-check-input::before {\n position: absolute;\n top: 50%;\n left: 0;\n width: 16px;\n height: 16px;\n box-sizing: border-box;\n border: 1px solid light-dark(var(--primary-foreground), var(--foreground));\n border-radius: var(--radius-full);\n background: light-dark(var(--primary-foreground), var(--foreground));\n box-shadow: var(--shadow-sm);\n content: \"\";\n transform: translate(2px, -50%);\n transition: transform 200ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n.form-switch .form-check-input:checked {\n background: var(--primary);\n}\n\n.form-switch .form-check-input:checked::before {\n transform: translate(14px, -50%);\n}\n\n.form-switch .form-check-input:focus-visible {\n box-shadow: 0 0 0 2px var(--ring);\n}\n\n.form-range {\n appearance: none;\n display: block;\n width: 100%;\n height: 28px;\n flex: 1;\n margin: 0;\n padding: 0;\n outline: none;\n border: 0;\n accent-color: var(--primary);\n background: linear-gradient(\n color-mix(in srgb, var(--foreground) 7%, transparent),\n color-mix(in srgb, var(--foreground) 7%, transparent)\n )\n center / 100% 2px no-repeat;\n}\n\n.form-range::-webkit-slider-runnable-track {\n height: 28px;\n background: transparent;\n}\n\n.form-range::-webkit-slider-thumb {\n appearance: none;\n width: 20px;\n height: 20px;\n margin-top: 4px;\n border: 1px solid var(--border);\n border-radius: var(--radius-full);\n background: light-dark(var(--primary-foreground), var(--foreground));\n}\n\n.form-range:focus-visible::-webkit-slider-thumb {\n border-color: var(--ring);\n box-shadow: inset 0 0 0 1px var(--ring);\n}\n\n.form-range::-moz-range-track {\n height: 28px;\n background: transparent;\n}\n\n.form-range::-moz-range-thumb {\n width: 20px;\n height: 20px;\n border: 1px solid var(--border);\n border-radius: var(--radius-full);\n background: light-dark(var(--primary-foreground), var(--foreground));\n}\n\n.form-range:focus-visible::-moz-range-thumb {\n border-color: var(--ring);\n box-shadow: inset 0 0 0 1px var(--ring);\n}\n\n.form-range:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.btn:not(.btn-primary, .viz-tile):is(\n [aria-pressed=\"true\"],\n [aria-selected=\"true\"],\n .is-selected\n) {\n border-color: var(--primary);\n color: var(--primary-foreground);\n background: var(--primary);\n}\n\n.btn.viz-tile:is([aria-pressed=\"true\"], [aria-selected=\"true\"], .is-selected) {\n border-color: var(--primary);\n box-shadow: inset 0 0 0 1px var(--primary);\n}\n\n.btn:focus-visible,\n.nav-link:focus-visible {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n}\n\n@media (pointer: coarse) {\n .btn,\n .nav-link,\n .form-control,\n .form-select {\n min-width: 44px;\n min-height: 44px;\n }\n\n /* Keep small-screen WebKit from zooming when a text field receives focus. */\n .form-control,\n .form-select {\n font-size: max(16px, var(--font-size-normal));\n }\n\n .form-control[type=\"file\"]::file-selector-button {\n min-width: 44px;\n min-height: 44px;\n }\n\n .form-control-color[type=\"color\"] {\n width: 44px;\n height: 44px;\n }\n\n .form-control:is(\n [type=\"date\"],\n [type=\"datetime-local\"],\n [type=\"month\"],\n [type=\"time\"],\n [type=\"week\"]\n ) {\n min-width: 0;\n max-width: 100%;\n padding-block: 8px;\n }\n\n .form-check {\n min-height: 44px;\n padding-inline: 15px;\n }\n\n .form-check-input {\n position: relative;\n }\n\n .form-check-input::after {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 44px;\n height: 44px;\n content: \"\";\n transform: translate(-50%, -50%);\n }\n\n .form-check-label {\n display: inline-flex;\n min-width: 44px;\n min-height: 44px;\n align-items: center;\n }\n\n .form-range {\n height: 44px;\n touch-action: pan-y;\n }\n\n .form-range::-webkit-slider-runnable-track {\n height: 44px;\n }\n\n .form-range::-moz-range-track {\n height: 44px;\n }\n\n .form-range::-webkit-slider-thumb {\n margin-top: 12px;\n }\n}\n\nsvg {\n display: block;\n max-width: 100%;\n height: auto;\n}\n\n#widget > svg {\n width: 100%;\n}\n\n/* Model-authored chart rules can otherwise stretch icons after these styles load. */\nsvg.lucide {\n display: block;\n width: var(--icon-size) !important;\n height: var(--icon-size) !important;\n flex: none;\n margin: 0 !important;\n stroke-width: 1.6;\n}\n\n.text-warning,\n.text-destructive {\n color: var(--destructive);\n}\n";
|
|
3
|
+
export const VISUALIZATION_HELPERS = "\n<script src=\"https://unpkg.com/@floating-ui/core@1.7.3/dist/floating-ui.core.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@floating-ui/dom@1.7.4/dist/floating-ui.dom.umd.min.js\"></script>\n<script>\n (() => {\n if (window.FloatingUIDOM == null) {\n return;\n }\n const { autoUpdate, computePosition, flip, offset, shift, size } = window.FloatingUIDOM;\n const tooltipPlacements = new Set([\"top\", \"right\", \"bottom\", \"left\"]);\n const tooltipId = `codex-visualization-tooltip-${\n window.crypto?.randomUUID?.() ?? Date.now().toString(36)\n }`;\n const tooltipGap = 5;\n const hoverDelay = 700;\n const skipDelay = 300;\n let activeTrigger = null;\n let pendingTrigger = null;\n let tooltip = null;\n let openTimeout = null;\n let autoUpdateCleanup = null;\n let positionRequest = 0;\n let skipDelayUntil = 0;\n let lastPointerWasTouch = false;\n let pinnedByTouch = false;\n\n const getTrigger = (target) =>\n target?.nodeType === Node.ELEMENT_NODE ? target.closest(\"[data-tooltip]\") : null;\n const containsTarget = (trigger, target) =>\n target?.nodeType != null && trigger.contains(target);\n const getContent = (trigger) => trigger.getAttribute(\"data-tooltip\")?.trim() ?? \"\";\n const getPlacement = (trigger) => {\n const placement = trigger.getAttribute(\"data-tooltip-placement\");\n return tooltipPlacements.has(placement) ? placement : \"top\";\n };\n const getTooltip = () => {\n if (tooltip == null) {\n tooltip = document.createElement(\"div\");\n tooltip.id = tooltipId;\n tooltip.className = \"tooltip\";\n tooltip.setAttribute(\"role\", \"tooltip\");\n }\n return tooltip;\n };\n const setDescribedBy = (trigger, described) => {\n const ids = new Set(\n (trigger.getAttribute(\"aria-describedby\") ?? \"\").split(/\\s+/).filter(Boolean),\n );\n if (described) {\n ids.add(tooltipId);\n } else {\n ids.delete(tooltipId);\n }\n if (ids.size === 0) {\n trigger.removeAttribute(\"aria-describedby\");\n } else {\n trigger.setAttribute(\"aria-describedby\", [...ids].join(\" \"));\n }\n };\n const clearOpenTimeout = () => {\n if (openTimeout != null) {\n window.clearTimeout(openTimeout);\n openTimeout = null;\n }\n pendingTrigger = null;\n };\n const closeTooltip = (activateSkipDelay = true) => {\n clearOpenTimeout();\n pinnedByTouch = false;\n positionRequest += 1;\n autoUpdateCleanup?.();\n autoUpdateCleanup = null;\n if (activeTrigger == null) {\n return;\n }\n setDescribedBy(activeTrigger, false);\n activeTrigger = null;\n tooltip?.remove();\n if (activateSkipDelay) {\n skipDelayUntil = Date.now() + skipDelay;\n }\n };\n const updatePosition = (trigger, floating) => {\n const request = ++positionRequest;\n void computePosition(trigger, floating, {\n middleware: [\n offset(tooltipGap),\n flip({ padding: tooltipGap }),\n shift({ padding: tooltipGap }),\n size({\n padding: tooltipGap,\n apply({ availableHeight, availableWidth, elements }) {\n elements.floating.style.setProperty(\n \"--tooltip-available-width\",\n `${Math.max(0, availableWidth)}px`,\n );\n elements.floating.style.setProperty(\n \"--tooltip-available-height\",\n `${Math.max(0, availableHeight)}px`,\n );\n },\n }),\n ],\n placement: getPlacement(trigger),\n strategy: \"fixed\",\n })\n .then(({ x, y }) => {\n if (activeTrigger !== trigger || request !== positionRequest) {\n return;\n }\n const scale = window.devicePixelRatio || 1;\n floating.style.transform = `translate(${Math.round(x * scale) / scale}px, ${Math.round(y * scale) / scale}px)`;\n floating.style.visibility = \"visible\";\n })\n .catch(() => {\n if (activeTrigger === trigger && request === positionRequest) {\n closeTooltip(false);\n }\n });\n };\n const openTooltip = (trigger) => {\n const content = getContent(trigger);\n if (!trigger.isConnected || content.length === 0) {\n return;\n }\n clearOpenTimeout();\n if (activeTrigger === trigger) {\n return;\n }\n closeTooltip();\n const floating = getTooltip();\n floating.textContent = content;\n floating.style.visibility = \"hidden\";\n floating.style.transform = \"translate(0, 0)\";\n document.body.appendChild(floating);\n activeTrigger = trigger;\n setDescribedBy(trigger, true);\n autoUpdateCleanup = autoUpdate(trigger, floating, () => {\n updatePosition(trigger, floating);\n });\n };\n const requestOpen = (trigger, immediate = false) => {\n if (\n getContent(trigger).length === 0 ||\n activeTrigger === trigger ||\n pendingTrigger === trigger\n ) {\n return;\n }\n clearOpenTimeout();\n pendingTrigger = trigger;\n const delay =\n immediate || activeTrigger != null || Date.now() < skipDelayUntil ? 0 : hoverDelay;\n if (delay === 0) {\n openTooltip(trigger);\n return;\n }\n openTimeout = window.setTimeout(() => {\n openTimeout = null;\n pendingTrigger = null;\n openTooltip(trigger);\n }, delay);\n };\n const handlePointerOpen = (event) => {\n if (event.pointerType === \"touch\") {\n return;\n }\n const trigger = getTrigger(event.target);\n if (trigger == null || containsTarget(trigger, event.relatedTarget)) {\n return;\n }\n requestOpen(trigger);\n };\n const handlePointer = (event) => {\n lastPointerWasTouch = event.type === \"pointerdown\" && event.pointerType === \"touch\";\n };\n const handleTouchClick = (event) => {\n const isTouch = event.pointerType === \"touch\" || lastPointerWasTouch;\n lastPointerWasTouch = false;\n if (!isTouch || event.detail === 0) {\n return;\n }\n const trigger = getTrigger(event.target);\n if (\n trigger == null ||\n !trigger.isConnected ||\n getContent(trigger).length === 0 ||\n (pinnedByTouch && activeTrigger === trigger)\n ) {\n if (pinnedByTouch) {\n closeTooltip();\n }\n return;\n }\n openTooltip(trigger);\n pinnedByTouch = activeTrigger === trigger;\n window.setTimeout(() => {\n if (activeTrigger === trigger && !trigger.isConnected) {\n closeTooltip(false);\n }\n }, 0);\n };\n const handleLeave = (event) => {\n if (pinnedByTouch) {\n return;\n }\n const trigger = getTrigger(event.target);\n if (trigger == null || containsTarget(trigger, event.relatedTarget)) {\n return;\n }\n if (activeTrigger === trigger || pendingTrigger === trigger) {\n closeTooltip();\n }\n };\n const handleFocusIn = (event) => {\n const trigger = getTrigger(event.target);\n if (trigger?.matches(\":focus-visible\")) {\n requestOpen(trigger, true);\n }\n };\n const handleKeyDown = (event) => {\n if (event.key === \"Escape\") {\n closeTooltip();\n }\n };\n const handleContextMenu = (event) => {\n lastPointerWasTouch = false;\n const trigger = getTrigger(event.target);\n if (activeTrigger === trigger || pendingTrigger === trigger) {\n closeTooltip();\n }\n };\n const destroy = () => {\n closeTooltip(false);\n lastPointerWasTouch = false;\n document.removeEventListener(\"pointerover\", handlePointerOpen);\n document.removeEventListener(\"pointerdown\", handlePointer);\n document.removeEventListener(\"pointercancel\", handlePointer);\n document.removeEventListener(\"click\", handleTouchClick);\n document.removeEventListener(\"pointerout\", handleLeave);\n document.removeEventListener(\"focusin\", handleFocusIn);\n document.removeEventListener(\"focusout\", handleLeave);\n document.removeEventListener(\"keydown\", handleKeyDown);\n document.removeEventListener(\"contextmenu\", handleContextMenu);\n tooltip = null;\n };\n\n document.addEventListener(\"pointerover\", handlePointerOpen);\n document.addEventListener(\"pointerdown\", handlePointer);\n document.addEventListener(\"pointercancel\", handlePointer);\n document.addEventListener(\"click\", handleTouchClick);\n document.addEventListener(\"pointerout\", handleLeave);\n document.addEventListener(\"focusin\", handleFocusIn);\n document.addEventListener(\"focusout\", handleLeave);\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"contextmenu\", handleContextMenu);\n window.addEventListener(\"pagehide\", destroy, { once: true });\n })();\n</script>\n<script id=\"codex-visualization-tabs\">\n (() => {\n const selector = '.nav[role=\"tablist\"] [role=\"tab\"]';\n const disabled = ':disabled,[aria-disabled=\"true\"]';\n const selectTab = (selected) => {\n const panelId = selected.getAttribute(\"aria-controls\");\n for (const tab of selected.closest('[role=\"tablist\"]').querySelectorAll('[role=\"tab\"]')) {\n const active = tab === selected;\n tab.classList.toggle(\"active\", active);\n tab.setAttribute(\"aria-selected\", String(active));\n const id = tab.getAttribute(\"aria-controls\");\n const panel = id && document.getElementById(id);\n if (panel != null) {\n panel.hidden = id !== panelId;\n if (!panel.hidden && selected.id) {\n panel.setAttribute(\"aria-labelledby\", selected.id);\n }\n }\n }\n };\n const interact = (event) => {\n const tab = event.target?.closest?.(selector);\n if (event.defaultPrevented || tab == null || tab.matches(disabled)) {\n return;\n }\n if (event.type === \"click\") {\n selectTab(tab);\n return;\n }\n const tabs = Array.from(\n tab.closest('[role=\"tablist\"]').querySelectorAll('[role=\"tab\"]'),\n ).filter((candidate) => !candidate.matches(disabled));\n const current = tabs.indexOf(tab);\n let next;\n if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n next = (current + 1) % tabs.length;\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n next = (current + tabs.length - 1) % tabs.length;\n } else if (event.key === \"Home\") {\n next = 0;\n } else if (event.key === \"End\") {\n next = tabs.length - 1;\n } else {\n return;\n }\n event.preventDefault();\n tabs[next].focus();\n selectTab(tabs[next]);\n };\n\n for (const list of document.querySelectorAll('.nav[role=\"tablist\"]')) {\n const selected =\n list.querySelector('[role=\"tab\"][aria-selected=\"true\"]') ??\n list.querySelector('[role=\"tab\"].active') ??\n list.querySelector('[role=\"tab\"]');\n if (selected != null) {\n selectTab(selected);\n }\n }\n document.addEventListener(\"click\", interact);\n document.addEventListener(\"keydown\", interact);\n window.addEventListener(\n \"pagehide\",\n () => {\n document.removeEventListener(\"click\", interact);\n document.removeEventListener(\"keydown\", interact);\n },\n { once: true },\n );\n })();\n</script>\n<script\n id=\"codex-visualization-lucide\"\n async\n src=\"https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js\"\n></script>\n<script>\n (() => {\n const initialize = () => {\n globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });\n };\n if (globalThis.lucide != null) {\n initialize();\n return;\n }\n document\n .getElementById(\"codex-visualization-lucide\")\n ?.addEventListener(\"load\", initialize, { once: true });\n })();\n</script>\n";
|
|
@@ -142,11 +142,16 @@ export function CopyHoverFrame({
|
|
|
142
142
|
<div
|
|
143
143
|
data-og-annotation-chrome=""
|
|
144
144
|
className={cn(
|
|
145
|
-
"
|
|
145
|
+
"flex h-6 items-center gap-1.5 pointer-coarse:h-11",
|
|
146
146
|
align === "end" ? "justify-end" : "justify-start",
|
|
147
147
|
)}
|
|
148
148
|
>
|
|
149
|
-
<CopyButton
|
|
149
|
+
<CopyButton
|
|
150
|
+
text={copyText}
|
|
151
|
+
label={label}
|
|
152
|
+
reveal="group-hover"
|
|
153
|
+
className="size-6 pointer-coarse:size-11"
|
|
154
|
+
/>
|
|
150
155
|
{trailing}
|
|
151
156
|
</div>
|
|
152
157
|
</div>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Children,
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
3
5
|
isValidElement,
|
|
4
6
|
memo,
|
|
5
7
|
useEffect,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
import ReactMarkdown, {
|
|
14
16
|
defaultUrlTransform,
|
|
15
17
|
type Components,
|
|
18
|
+
type ExtraProps,
|
|
16
19
|
type UrlTransform,
|
|
17
20
|
} from "react-markdown";
|
|
18
21
|
import remarkGfm from "remark-gfm";
|
|
@@ -40,7 +43,17 @@ import { TooltipProvider } from "./tooltip";
|
|
|
40
43
|
* the tokens land. Consumers who want a different renderer can still pass
|
|
41
44
|
* `renderMessageText` to `MessageTimeline` to override this entirely.
|
|
42
45
|
*/
|
|
46
|
+
export type MarkdownInteractiveBlock = { kind: "html" | "site"; content: string };
|
|
47
|
+
const InteractiveContext = createContext<{
|
|
48
|
+
source: string;
|
|
49
|
+
render?: (block: MarkdownInteractiveBlock) => ReactNode;
|
|
50
|
+
renderImage?: (image: { src: string; alt: string }) => ReactNode;
|
|
51
|
+
}>({ source: "" });
|
|
52
|
+
|
|
43
53
|
export type MarkdownProps = {
|
|
54
|
+
renderImage?: ((image: { src: string; alt: string }) => ReactNode) | undefined;
|
|
55
|
+
/** Host opt-in for assistant-authored interactive fences. */
|
|
56
|
+
renderInteractiveBlock?: ((block: MarkdownInteractiveBlock) => ReactNode) | undefined;
|
|
44
57
|
children: string;
|
|
45
58
|
className?: string | undefined;
|
|
46
59
|
/**
|
|
@@ -171,7 +184,7 @@ const baseComponents: Components = {
|
|
|
171
184
|
</code>
|
|
172
185
|
),
|
|
173
186
|
// Fenced code — quiet mono block (no card / no nested vertical scroll).
|
|
174
|
-
pre:
|
|
187
|
+
pre: InteractiveCodeBlock,
|
|
175
188
|
// Tables stay unboxed (hairline rules); hover reveals a TSV copy control.
|
|
176
189
|
table: ({ children, ...props }) => <MarkdownTable {...props}>{children}</MarkdownTable>,
|
|
177
190
|
thead: ({ children, ...props }) => <thead {...props}>{children}</thead>,
|
|
@@ -191,19 +204,7 @@ const baseComponents: Components = {
|
|
|
191
204
|
{children}
|
|
192
205
|
</td>
|
|
193
206
|
),
|
|
194
|
-
img:
|
|
195
|
-
src ? (
|
|
196
|
-
<img
|
|
197
|
-
alt={alt ?? ""}
|
|
198
|
-
src={src}
|
|
199
|
-
className="my-3 max-w-full rounded-og-md border border-og-border"
|
|
200
|
-
{...props}
|
|
201
|
-
/>
|
|
202
|
-
) : alt ? (
|
|
203
|
-
<span className="text-og-fg-subtle" aria-disabled="true">
|
|
204
|
-
{alt}
|
|
205
|
-
</span>
|
|
206
|
-
) : null,
|
|
207
|
+
img: MarkdownImage,
|
|
207
208
|
};
|
|
208
209
|
|
|
209
210
|
const MARKDOWN_LINK_CLASS =
|
|
@@ -343,8 +344,30 @@ export function sandboxFilePathFromHref(href: string | undefined): string | null
|
|
|
343
344
|
return sandboxFileLocationFromHref(href)?.path ?? null;
|
|
344
345
|
}
|
|
345
346
|
|
|
347
|
+
export function retainedImageId(src: string): string | null {
|
|
348
|
+
return (
|
|
349
|
+
/^artifact:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i
|
|
350
|
+
.exec(src)?.[1]
|
|
351
|
+
?.toLowerCase() ?? null
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
function MarkdownImage({ src, alt }: ComponentPropsWithoutRef<"img">) {
|
|
355
|
+
const { renderImage } = useContext(InteractiveContext);
|
|
356
|
+
if (!src) return <span>{alt ?? "Image unavailable"}</span>;
|
|
357
|
+
if (retainedImageId(src))
|
|
358
|
+
return renderImage ? (
|
|
359
|
+
<>{renderImage({ src, alt: alt ?? "" })}</>
|
|
360
|
+
) : (
|
|
361
|
+
<span>{alt || "Image"} (preview unavailable)</span>
|
|
362
|
+
);
|
|
363
|
+
return <img src={src} alt={alt ?? ""} loading="lazy" className="my-3 max-w-full rounded-og-md" />;
|
|
364
|
+
}
|
|
365
|
+
|
|
346
366
|
const markdownUrlTransform: UrlTransform = (url, key, node) =>
|
|
347
|
-
key === "href" && node.tagName === "a" && isSandboxHref(url)
|
|
367
|
+
(key === "href" && node.tagName === "a" && isSandboxHref(url)) ||
|
|
368
|
+
(key === "src" && node.tagName === "img" && retainedImageId(url))
|
|
369
|
+
? url
|
|
370
|
+
: defaultUrlTransform(url);
|
|
348
371
|
|
|
349
372
|
/** How long after the stream ends the reveal pipeline stays for trailing animations. */
|
|
350
373
|
/** Trailing ink window after stream end — keep ≥ {@link INK_FADE_MS}. */
|
|
@@ -382,6 +405,42 @@ function fenceLanguage(children: ReactNode): string | null {
|
|
|
382
405
|
return found;
|
|
383
406
|
}
|
|
384
407
|
|
|
408
|
+
function InteractiveCodeBlock({ children, node }: ComponentPropsWithoutRef<"pre"> & ExtraProps) {
|
|
409
|
+
const { source, render } = useContext(InteractiveContext);
|
|
410
|
+
const language = fenceLanguage(children);
|
|
411
|
+
if (render && (language === "opengeni-html" || language === "opengeni-site")) {
|
|
412
|
+
const start = node?.position?.start.offset;
|
|
413
|
+
const end = node?.position?.end.offset;
|
|
414
|
+
const original =
|
|
415
|
+
start !== undefined && end !== undefined && end <= source.length
|
|
416
|
+
? source.slice(start, end)
|
|
417
|
+
: "";
|
|
418
|
+
const lines = original.trimEnd().split("\n");
|
|
419
|
+
const opening = lines[0]?.match(/^ {0,3}([\x60]{3,}|~{3,})/);
|
|
420
|
+
|
|
421
|
+
const closing = lines
|
|
422
|
+
.at(-1)
|
|
423
|
+
?.replace(/^\s*(?:>\s*)+/, "")
|
|
424
|
+
.trim();
|
|
425
|
+
const complete =
|
|
426
|
+
opening &&
|
|
427
|
+
lines.length > 1 &&
|
|
428
|
+
closing &&
|
|
429
|
+
closing.length >= opening[1]!.length &&
|
|
430
|
+
[...closing].every((c) => c === opening[1]![0]);
|
|
431
|
+
if (!complete) return <p role="status">Preparing preview…</p>;
|
|
432
|
+
return (
|
|
433
|
+
<>
|
|
434
|
+
{render({
|
|
435
|
+
kind: language === "opengeni-html" ? "html" : "site",
|
|
436
|
+
content: nodeText(children).replace(/\n$/, ""),
|
|
437
|
+
})}
|
|
438
|
+
</>
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
return <MarkdownCodeBlock>{children}</MarkdownCodeBlock>;
|
|
442
|
+
}
|
|
443
|
+
|
|
385
444
|
function MarkdownCodeBlock({ children }: { children?: ReactNode }) {
|
|
386
445
|
const code = nodeText(children).replace(/\n$/, "");
|
|
387
446
|
const language = fenceLanguage(children);
|
|
@@ -462,7 +521,14 @@ function MarkdownTable({ children, className, ...props }: ComponentPropsWithoutR
|
|
|
462
521
|
);
|
|
463
522
|
}
|
|
464
523
|
|
|
465
|
-
function MarkdownImpl({
|
|
524
|
+
function MarkdownImpl({
|
|
525
|
+
children,
|
|
526
|
+
className,
|
|
527
|
+
streaming = false,
|
|
528
|
+
onSandboxFile,
|
|
529
|
+
renderInteractiveBlock,
|
|
530
|
+
renderImage,
|
|
531
|
+
}: MarkdownProps) {
|
|
466
532
|
// Tip-ink engine for THIS body: created on the first streaming render, kept
|
|
467
533
|
// through a short linger after the stream ends (so the last age window can
|
|
468
534
|
// finish), then dropped so settled bodies pay zero cost. Observing during
|
|
@@ -553,28 +619,39 @@ function MarkdownImpl({ children, className, streaming = false, onSandboxFile }:
|
|
|
553
619
|
const parseText = streaming || revealActive ? softenStreamingMarkdown(children) : children;
|
|
554
620
|
const components = useMemo(() => markdownComponents(onSandboxFile), [onSandboxFile]);
|
|
555
621
|
|
|
622
|
+
const interactiveContext = useMemo(
|
|
623
|
+
() => ({
|
|
624
|
+
source: children,
|
|
625
|
+
...(renderInteractiveBlock ? { render: renderInteractiveBlock } : {}),
|
|
626
|
+
...(renderImage ? { renderImage } : {}),
|
|
627
|
+
}),
|
|
628
|
+
[children, renderInteractiveBlock, renderImage],
|
|
629
|
+
);
|
|
630
|
+
|
|
556
631
|
// `min-w-0` lets the prose shrink inside flex parents (message bubbles) so
|
|
557
632
|
// long links and code blocks wrap/scroll instead of forcing overflow.
|
|
558
633
|
return (
|
|
559
|
-
<
|
|
560
|
-
<
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
<ReactMarkdown
|
|
569
|
-
remarkPlugins={[remarkGfm]}
|
|
570
|
-
rehypePlugins={rehypePlugins}
|
|
571
|
-
components={components}
|
|
572
|
-
urlTransform={markdownUrlTransform}
|
|
634
|
+
<InteractiveContext.Provider value={interactiveContext}>
|
|
635
|
+
<TooltipProvider delayDuration={400}>
|
|
636
|
+
<div
|
|
637
|
+
ref={bodyRef}
|
|
638
|
+
className={cn(
|
|
639
|
+
"og-markdown-body min-w-0 break-words",
|
|
640
|
+
settling && "og-markdown-settle",
|
|
641
|
+
className,
|
|
642
|
+
)}
|
|
573
643
|
>
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
644
|
+
<ReactMarkdown
|
|
645
|
+
remarkPlugins={[remarkGfm]}
|
|
646
|
+
rehypePlugins={rehypePlugins}
|
|
647
|
+
components={components}
|
|
648
|
+
urlTransform={markdownUrlTransform}
|
|
649
|
+
>
|
|
650
|
+
{parseText}
|
|
651
|
+
</ReactMarkdown>
|
|
652
|
+
</div>
|
|
653
|
+
</TooltipProvider>
|
|
654
|
+
</InteractiveContext.Provider>
|
|
578
655
|
);
|
|
579
656
|
}
|
|
580
657
|
|