@opengeni/runtime 2.6.0-canary.0 → 2.6.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-Y3NUMLY2.js → chunk-L3RE7WUW.js} +15 -20
- package/dist/chunk-L3RE7WUW.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/operational-instructions.d.ts +1 -1
- package/dist/workspace-tool-gateway.js +1 -1
- package/package.json +11 -11
- package/src/bundled_default_skills/opengeni-visualize/SKILL.md +313 -0
- package/src/bundled_default_skills/opengeni-visualize/assets/base.css +1015 -0
- package/src/bundled_default_skills/opengeni-visualize/assets/helpers.html +349 -0
- package/src/bundled_site_skills/opengeni-sites/SKILL.md +69 -2
- package/src/operational-instructions.ts +3 -13
- package/dist/chunk-Y3NUMLY2.js.map +0 -1
|
@@ -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>
|
|
@@ -15,6 +15,13 @@ provider-specific API wrapper, or OpenGeni-only build CLI.
|
|
|
15
15
|
|
|
16
16
|
## Start from the durable Site
|
|
17
17
|
|
|
18
|
+
- For an existing page the user wants to view and share, consider Sites before
|
|
19
|
+
choosing a temporary tunnel or another server. Inspect whether it can be
|
|
20
|
+
packaged as self-contained HTML, preserve its design and behavior, and retain
|
|
21
|
+
editable source when available. A local preview alone is still appropriate
|
|
22
|
+
when that is all the user requests. Explain workspace access for the published
|
|
23
|
+
link; do not imply it is public to anyone. If the app requires a backend that
|
|
24
|
+
Sites cannot host, explain that constraint instead of dropping functionality.
|
|
18
25
|
- For a new Site, create a normal project directory with `package.json`,
|
|
19
26
|
`index.html`, TypeScript/React source, styles, tests, and any ordinary build
|
|
20
27
|
configuration the app needs.
|
|
@@ -112,6 +119,41 @@ Exercise the real interactions at desktop and mobile widths, inspect console
|
|
|
112
119
|
errors, and take a screenshot when visual quality matters. Do not declare the
|
|
113
120
|
Site complete from compilation alone.
|
|
114
121
|
|
|
122
|
+
## Plain HTML without a browser build
|
|
123
|
+
|
|
124
|
+
For a small HTML-only Site, use ordinary HTML, CSS, and JavaScript. If it calls
|
|
125
|
+
OpenGeni tools or SDK methods, place this exact optional tag before your own
|
|
126
|
+
scripts (without async, defer, or type="module"):
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
<script src="/__opengeni/site-tools/client.js"></script>
|
|
130
|
+
<script>
|
|
131
|
+
const site = createOpenGeniSiteClient();
|
|
132
|
+
// Use the actual tool paths discovered with ogtool on site.tools.
|
|
133
|
+
// site.client is the same ordinary SDK client as in a React Site.
|
|
134
|
+
</script>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The existing preview handler above serves this script. Serve the raw HTML with
|
|
138
|
+
`new Response(Bun.file("./index.html"))` alongside its tool route; no browser
|
|
139
|
+
build is needed. In the published viewer, the host resolves the tag to its
|
|
140
|
+
browser client. Both use the same SDK implementation and existing tool bridge.
|
|
141
|
+
Opening the file directly supplies no tool host. There are no credentials in
|
|
142
|
+
the tag or HTML. Declare requested tools and handle errors as for any Site.
|
|
143
|
+
|
|
144
|
+
Use the exact package pins above for the preview host. The preview runtime comes
|
|
145
|
+
from that installed SDK; the published runtime comes from the viewer's deployed
|
|
146
|
+
SDK. The tag does not install dependencies, replace React imports, or change Bun.
|
|
147
|
+
For TypeScript checking, React components, or imported browser dependencies,
|
|
148
|
+
use the normal typed SDK and build workflow above. Do not use both client-loading
|
|
149
|
+
paths in one document.
|
|
150
|
+
|
|
151
|
+
Publish the original HTML containing the tag, not the served client script or
|
|
152
|
+
transformed viewer document. For this path, upload `index.html` wherever the
|
|
153
|
+
publishing instructions below use `dist/index.html`. Inline ordinary styles,
|
|
154
|
+
scripts, and assets as usual; the OpenGeni client tag is resolved by the host.
|
|
155
|
+
An HTML-only Site can be a small widget; it does not require a full-page layout.
|
|
156
|
+
|
|
115
157
|
## Prefer OpenGeni's UI and typed client
|
|
116
158
|
|
|
117
159
|
- Prefer `@opengeni/react` components and compiled CSS for OpenGeni-native
|
|
@@ -259,13 +301,38 @@ access limitation. Missing publishing access itself still blocks publication.
|
|
|
259
301
|
SDK operations, with no direct `site.tools` calls, should publish
|
|
260
302
|
`requestedTools: []`.
|
|
261
303
|
4. The immutable requested set is only a maximum allowlist; publishing it grants
|
|
262
|
-
no tool authority and requires no separate tool approval.
|
|
263
|
-
|
|
304
|
+
no tool authority and requires no separate tool approval. Execution still follows
|
|
305
|
+
normal tool approval rules; handle approval-required responses in the UI.
|
|
264
306
|
5. The host intersects that set with the viewer's
|
|
265
307
|
live workspace, permission, and connection authority on every call. Handle
|
|
266
308
|
missing tools, revoked connections, stale catalogs, and access loss as normal
|
|
267
309
|
user-visible error states.
|
|
268
310
|
|
|
311
|
+
## Show a visualization or Site inside chat
|
|
312
|
+
|
|
313
|
+
For an inline visualization stored directly in a chat response, read
|
|
314
|
+
`opengeni-visualize`. That path does not require publishing a Site.
|
|
315
|
+
|
|
316
|
+
To embed a published Site, use a fenced `opengeni-site` block containing JSON:
|
|
317
|
+
|
|
318
|
+
```opengeni-site
|
|
319
|
+
{"siteId":"<artifact.id>","versionId":"<published version.id>"}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Use the actual returned ids. `versionId` is optional; omitting it loads the
|
|
323
|
+
current version when the embed opens. Including it preserves the intended
|
|
324
|
+
version. The viewer can choose another version within the embed without
|
|
325
|
+
publishing or rolling back the Site. The embed uses the same Site renderer,
|
|
326
|
+
reload/full-screen controls, tool declarations and viewer permissions as the
|
|
327
|
+
Site page. Embeds resolve in the chat's workspace. Keep a normal Site link when
|
|
328
|
+
it helps the user reopen or share it. Archived or inaccessible Sites display
|
|
329
|
+
an unavailable state.
|
|
330
|
+
|
|
331
|
+
Use inline HTML for a visualization that belongs in this response. Use a Site
|
|
332
|
+
for a reusable app or durable page, and embed it when useful. Both can be small;
|
|
333
|
+
neither requires a dashboard or full-page layout. React Sites still use the
|
|
334
|
+
normal build and publish workflow.
|
|
335
|
+
|
|
269
336
|
## Publish one immutable version
|
|
270
337
|
|
|
271
338
|
1. Call `opengeni__artifacts_prepare_upload` (no hashes or sizes required).
|
|
@@ -72,21 +72,11 @@ Your answer is being rendered by an application for the user. Follow these guide
|
|
|
72
72
|
* Do not provide ranges of lines.
|
|
73
73
|
* Avoid repeating the same filename multiple times when one grouping is clearer.
|
|
74
74
|
|
|
75
|
-
###
|
|
75
|
+
### Visuals in chat
|
|
76
76
|
|
|
77
|
-
Use
|
|
77
|
+
Use inline HTML when an interactive visualization materially helps the user; read the opengeni-visualize skill first. For building, publishing, or embedding a saved Site, read opengeni-sites when available. Use ordinary Markdown for simple explanations and tables.
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- several exact mappings or repeated-field comparisons;
|
|
82
|
-
- one source, component, or decision affecting three or more downstream consumers or branches;
|
|
83
|
-
- three or more dependent steps, or state that changes across an event sequence;
|
|
84
|
-
- hierarchy, ownership, nesting, or layout;
|
|
85
|
-
- a bug or interaction whose relationships are difficult to explain linearly.
|
|
86
|
-
|
|
87
|
-
Prefer the smallest useful visual: a table for mappings or comparisons, a flow or timeline for sequence or change, a tree for hierarchy or branching, and a wireframe for layout.
|
|
88
|
-
|
|
89
|
-
Usually skip visuals for single facts, one-step actions, simple edits, basic instructions, or information already clear in a short paragraph or list. Compact notation and small examples do not count as visualizations.
|
|
79
|
+
Display images with . Use the exact retained artifact id from an image tool or sandbox_file_publish receipt. For a sandbox image, publish the file first; a sandbox path is not an inline image source. Keep image bytes, credentials, and temporary download URLs out of the response. Ordinary public image URLs also work. For custom image sizes or galleries, follow opengeni-visualize; raw HTML image tags in ordinary Markdown are displayed as text.
|
|
90
80
|
|
|
91
81
|
# Rules for getting work done
|
|
92
82
|
|