@artooi/ag-ui-web-component 0.34.0 → 0.35.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/CHANGELOG.md +517 -1
- package/README.md +243 -13
- package/dist/ag-ui-web-component.bundle.js +614 -96
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +76 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +37 -2
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/dom/animations.d.ts +14 -0
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/highlight_overlay.d.ts +47 -0
- package/dist/dom/highlight_overlay.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1886 -320
- package/dist/index.js.map +4 -4
- package/dist/tools/chat_surface_tools.d.ts +96 -0
- package/dist/tools/chat_surface_tools.d.ts.map +1 -0
- package/dist/tools/page_action_tools.d.ts +2 -0
- package/dist/tools/page_action_tools.d.ts.map +1 -1
- package/dist/ui/clamp_launcher.d.ts +10 -5
- package/dist/ui/clamp_launcher.d.ts.map +1 -1
- package/dist/ui/clamp_panel.d.ts +2 -2
- package/dist/ui/clamp_panel.d.ts.map +1 -1
- package/dist/ui/launcher_drag.d.ts +2 -2
- package/dist/ui/launcher_drag.d.ts.map +1 -1
- package/dist/ui/launcher_placement.d.ts +14 -1
- package/dist/ui/launcher_placement.d.ts.map +1 -1
- package/dist/ui/panel_drag.d.ts.map +1 -1
- package/dist/ui/place_widget.d.ts +9 -1
- package/dist/ui/place_widget.d.ts.map +1 -1
- package/dist/ui/run_notice.d.ts +14 -3
- package/dist/ui/run_notice.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts +21 -0
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +16 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +82 -3
- package/src/core/ag_ui_chat.ts +965 -55
- package/src/dom/animations.ts +30 -0
- package/src/dom/highlight_overlay.ts +256 -0
- package/src/index.ts +12 -0
- package/src/tools/chat_surface_tools.ts +207 -0
- package/src/tools/page_action_tools.ts +2 -0
- package/src/ui/clamp_launcher.ts +25 -7
- package/src/ui/clamp_panel.ts +10 -4
- package/src/ui/launcher_drag.ts +11 -2
- package/src/ui/launcher_placement.ts +34 -8
- package/src/ui/panel_drag.ts +4 -0
- package/src/ui/place_widget.ts +11 -3
- package/src/ui/run_notice.ts +32 -3
- package/src/ui/styles.ts +563 -45
- package/src/ui/thread_drawer.ts +138 -8
- package/src/ui/ui_strings.ts +24 -0
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -59,6 +59,8 @@ var ICON_SEND = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path
|
|
|
59
59
|
var ICON_STOP = `<svg class="glyph glyph--solid" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2.5"/></svg>`;
|
|
60
60
|
var ICON_ATTACH = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M17 8.5V15a5 5 0 0 1-10 0V7a3 3 0 0 1 6 0v7.5a1 1 0 0 1-2 0V8.5"/></svg>`;
|
|
61
61
|
var ICON_VOICE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4a3 3 0 0 1 3 3v5a3 3 0 0 1-6 0V7a3 3 0 0 1 3-3z"/><path d="M5 11v1a7 7 0 0 0 14 0v-1"/><path d="M12 19v3"/></svg>`;
|
|
62
|
+
var ICON_MOON = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5z"/></svg>`;
|
|
63
|
+
var ICON_SUN = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>`;
|
|
62
64
|
var ICON_LAUNCHER = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H9l-5 4z"/></svg>`;
|
|
63
65
|
var ICON_COPY = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V6a2 2 0 0 1 2-2h8"/></svg>`;
|
|
64
66
|
var ICON_RETRY = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 12a8 8 0 1 1-2.5-5.8"/><path d="M20 4v4h-4"/></svg>`;
|
|
@@ -71,6 +73,10 @@ var ICON_FILE_TEXT = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true">
|
|
|
71
73
|
var CHART_ACTIVITY_TYPE = "chart";
|
|
72
74
|
var EDGE_MARGIN = 24;
|
|
73
75
|
var ANNOUNCE_CLEAR_MS = 150;
|
|
76
|
+
var HIGHLIGHT_OVERLAY_Z_INDEX = 2147483001;
|
|
77
|
+
var THREADS_DOCK_MIN_WIDTH = 900;
|
|
78
|
+
var PASTE_ATTACH_CHARS = 5e3;
|
|
79
|
+
var SCREEN_EDGE_MARGIN = 8;
|
|
74
80
|
|
|
75
81
|
// src/core/ag_ui_chat.ts
|
|
76
82
|
import { randomUUID as randomUUID5 } from "@ag-ui/client";
|
|
@@ -107,6 +113,95 @@ function parseSkills(value) {
|
|
|
107
113
|
return value.filter(isSkill);
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
// src/tools/chat_surface_tools.ts
|
|
117
|
+
var CHAT_CORNERS = ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
118
|
+
function isChatCorner(value) {
|
|
119
|
+
return CHAT_CORNERS.includes(value);
|
|
120
|
+
}
|
|
121
|
+
function createChatSurfaceTools(surface) {
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
name: "read_chat_surface",
|
|
125
|
+
description: "Describe the chat panel you are speaking from: its placement, whether it is collapsed, whether it can be moved, and the box it occupies. Read this before moving or minimising yourself, since a full-screen panel has nowhere to move to. Read-only.",
|
|
126
|
+
parameters: {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {},
|
|
129
|
+
required: [],
|
|
130
|
+
[X_SUMMARY_KEY]: "Read the chat's own position"
|
|
131
|
+
},
|
|
132
|
+
handler: () => surface.describeSurface()
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "move_chat",
|
|
136
|
+
description: "Move your own panel to a corner, to uncover something the user needs to see. `corner` is top-left, top-right, bottom-left or bottom-right. Answers `moved: false` with a reason when the placement owns its position or the panel fills the screen; check `read_chat_surface` first, and prefer minimise_chat when there is nowhere to move to.",
|
|
137
|
+
parameters: {
|
|
138
|
+
type: "object",
|
|
139
|
+
properties: {
|
|
140
|
+
corner: {
|
|
141
|
+
type: "string",
|
|
142
|
+
enum: [...CHAT_CORNERS]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
required: ["corner"],
|
|
146
|
+
[X_SUMMARY_KEY]: "Move the chat out of the way"
|
|
147
|
+
},
|
|
148
|
+
handler: (args) => {
|
|
149
|
+
const asked = String(args["corner"] ?? "");
|
|
150
|
+
const report = surface.describeSurface();
|
|
151
|
+
if (!isChatCorner(asked)) {
|
|
152
|
+
return {
|
|
153
|
+
moved: false,
|
|
154
|
+
reason: `"${asked}" is not a corner; use one of ${CHAT_CORNERS.join(", ")}`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (!report.movable) {
|
|
158
|
+
return {
|
|
159
|
+
moved: false,
|
|
160
|
+
reason: report.fullBleed ? "the panel fills the screen, so there is nowhere to move it to" : report.draggable === false ? "this page has turned off moving the panel" : `the "${report.placement ?? ""}" placement owns the panel's position`,
|
|
161
|
+
suggestion: report.collapsible ? "minimise_chat" : null
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return { moved: surface.moveTo(asked, { announce: true }), corner: asked };
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "minimise_chat",
|
|
169
|
+
description: "Collapse your own panel to its launcher, so the user can see the whole page. The launcher stays visible and reopens it. Answers `minimised: false` when the placement has no collapsed state, which is the case for a full-page chat.",
|
|
170
|
+
parameters: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {},
|
|
173
|
+
required: [],
|
|
174
|
+
[X_SUMMARY_KEY]: "Minimise the chat"
|
|
175
|
+
},
|
|
176
|
+
handler: () => {
|
|
177
|
+
const report = surface.describeSurface();
|
|
178
|
+
if (!report.collapsible) {
|
|
179
|
+
return {
|
|
180
|
+
minimised: false,
|
|
181
|
+
reason: "this placement has no collapsed state, so there is no launcher to return to"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
surface.setCollapsed(true, { announce: true });
|
|
185
|
+
return { minimised: true };
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "restore_chat",
|
|
190
|
+
description: "Open your own panel again after minimising it.",
|
|
191
|
+
parameters: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {},
|
|
194
|
+
required: [],
|
|
195
|
+
[X_SUMMARY_KEY]: "Restore the chat"
|
|
196
|
+
},
|
|
197
|
+
handler: () => {
|
|
198
|
+
surface.setCollapsed(false);
|
|
199
|
+
return { restored: true };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
|
|
110
205
|
// src/tools/client_tool_registry.ts
|
|
111
206
|
var ClientToolRegistry = class {
|
|
112
207
|
#tools = /* @__PURE__ */ new Map();
|
|
@@ -150,6 +245,133 @@ function isNavigates(parameters) {
|
|
|
150
245
|
return parameters[X_NAVIGATES_KEY] === true;
|
|
151
246
|
}
|
|
152
247
|
|
|
248
|
+
// src/dom/highlight_overlay.ts
|
|
249
|
+
var ACCENT = "#4f46e5";
|
|
250
|
+
function tokenOn(target, name, fallback) {
|
|
251
|
+
const value = window.getComputedStyle(target).getPropertyValue(name).trim();
|
|
252
|
+
return value === "" ? fallback : value;
|
|
253
|
+
}
|
|
254
|
+
var DEFAULT_PADDING_PX = 4;
|
|
255
|
+
function showHighlightOverlay(target, options = {}) {
|
|
256
|
+
const root = document.createElement("div");
|
|
257
|
+
root.setAttribute("data-ag-ui-highlight", "");
|
|
258
|
+
root.setAttribute("aria-hidden", "true");
|
|
259
|
+
root.style.cssText = [
|
|
260
|
+
"position: fixed",
|
|
261
|
+
"inset: 0",
|
|
262
|
+
"pointer-events: none",
|
|
263
|
+
// Above the widget's own default, because the overlay's whole job is to
|
|
264
|
+
// point at something on the page the widget is sitting over. A host whose
|
|
265
|
+
// chrome stacks higher still can say so.
|
|
266
|
+
`z-index: ${tokenOn(target, "--ag-ui-highlight-z-index", String(HIGHLIGHT_OVERLAY_Z_INDEX))}`
|
|
267
|
+
].join(";");
|
|
268
|
+
const scrim = document.createElement("div");
|
|
269
|
+
const ring = document.createElement("div");
|
|
270
|
+
root.className = "ag-ui-highlight";
|
|
271
|
+
scrim.className = "ag-ui-highlight-scrim";
|
|
272
|
+
ring.className = "ag-ui-highlight-ring";
|
|
273
|
+
if (options.scrim === true) {
|
|
274
|
+
root.append(scrim);
|
|
275
|
+
}
|
|
276
|
+
root.append(ring);
|
|
277
|
+
const ringWidth = options.ringWidth ?? Number.parseFloat(tokenOn(target, "--ag-ui-highlight-ring-width", "3"));
|
|
278
|
+
const flowMs = options.flowMs ?? Number.parseFloat(tokenOn(target, "--ag-ui-highlight-flow-ms", "2400"));
|
|
279
|
+
const paint = () => {
|
|
280
|
+
const box = target.getBoundingClientRect();
|
|
281
|
+
const pad = options.padding ?? DEFAULT_PADDING_PX;
|
|
282
|
+
const radius = options.radius ?? Number.parseFloat(getComputedStyle(target).borderRadius);
|
|
283
|
+
const left = box.left - pad;
|
|
284
|
+
const top = box.top - pad;
|
|
285
|
+
const width = box.width + pad * 2;
|
|
286
|
+
const height = box.height + pad * 2;
|
|
287
|
+
if (options.scrim === true) {
|
|
288
|
+
scrim.style.cssText = [
|
|
289
|
+
"position: absolute",
|
|
290
|
+
"inset: 0",
|
|
291
|
+
`background: ${tokenOn(target, "--ag-ui-highlight-scrim", "rgba(15, 15, 25, 0.45)")}`,
|
|
292
|
+
`clip-path: path(evenodd, '${holePath(left, top, width, height, radius + pad)}')`
|
|
293
|
+
].join(";");
|
|
294
|
+
}
|
|
295
|
+
ring.style.cssText = [
|
|
296
|
+
"position: absolute",
|
|
297
|
+
`left: ${left}px`,
|
|
298
|
+
`top: ${top}px`,
|
|
299
|
+
`width: ${width}px`,
|
|
300
|
+
`height: ${height}px`,
|
|
301
|
+
`border-radius: ${radius + pad}px`,
|
|
302
|
+
`border: ${ringWidth}px solid transparent`,
|
|
303
|
+
"box-sizing: border-box",
|
|
304
|
+
ringPaint(target, options)
|
|
305
|
+
].join(";");
|
|
306
|
+
};
|
|
307
|
+
paint();
|
|
308
|
+
let flow = null;
|
|
309
|
+
if (options.gradient === true && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
310
|
+
flow = ring.animate([{ backgroundPosition: "100% 0" }, { backgroundPosition: "-100% 0" }], {
|
|
311
|
+
duration: flowMs,
|
|
312
|
+
iterations: Number.POSITIVE_INFINITY,
|
|
313
|
+
easing: "linear"
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const listen = { capture: true, passive: true };
|
|
317
|
+
window.addEventListener("scroll", paint, listen);
|
|
318
|
+
window.addEventListener("resize", paint, listen);
|
|
319
|
+
document.body.appendChild(root);
|
|
320
|
+
return () => {
|
|
321
|
+
window.removeEventListener("scroll", paint, listen);
|
|
322
|
+
window.removeEventListener("resize", paint, listen);
|
|
323
|
+
flow?.cancel();
|
|
324
|
+
root.remove();
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function safeColor(value) {
|
|
328
|
+
if (value === void 0) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
return CSS.supports("color", value) ? value : null;
|
|
332
|
+
}
|
|
333
|
+
function ringPaint(target, options) {
|
|
334
|
+
const color = safeColor(options.color) ?? tokenOn(target, "--ag-ui-accent", ACCENT);
|
|
335
|
+
if (options.gradient !== true) {
|
|
336
|
+
return `border-color: ${color}`;
|
|
337
|
+
}
|
|
338
|
+
const image = tokenOn(
|
|
339
|
+
target,
|
|
340
|
+
"--ag-ui-highlight-gradient",
|
|
341
|
+
`linear-gradient(115deg, transparent 20%, ${color} 50%, transparent 80%)`
|
|
342
|
+
);
|
|
343
|
+
const mask = "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)";
|
|
344
|
+
return [
|
|
345
|
+
`background-image: ${image}`,
|
|
346
|
+
"background-origin: border-box",
|
|
347
|
+
"background-size: 300% 100%",
|
|
348
|
+
`-webkit-mask: ${mask}`,
|
|
349
|
+
`mask: ${mask}`,
|
|
350
|
+
"-webkit-mask-composite: xor",
|
|
351
|
+
"mask-composite: exclude",
|
|
352
|
+
"background-position: 50% 0"
|
|
353
|
+
].join(";");
|
|
354
|
+
}
|
|
355
|
+
function holePath(x2, y2, width, height, radius) {
|
|
356
|
+
const r = Math.max(0, Math.min(radius, width / 2, height / 2));
|
|
357
|
+
const right = x2 + width;
|
|
358
|
+
const bottom = y2 + height;
|
|
359
|
+
const outer = `M 0 0 H ${window.innerWidth} V ${window.innerHeight} H 0 Z`;
|
|
360
|
+
const hole = [
|
|
361
|
+
`M ${x2 + r} ${y2}`,
|
|
362
|
+
`H ${right - r}`,
|
|
363
|
+
`A ${r} ${r} 0 0 1 ${right} ${y2 + r}`,
|
|
364
|
+
`V ${bottom - r}`,
|
|
365
|
+
`A ${r} ${r} 0 0 1 ${right - r} ${bottom}`,
|
|
366
|
+
`H ${x2 + r}`,
|
|
367
|
+
`A ${r} ${r} 0 0 1 ${x2} ${bottom - r}`,
|
|
368
|
+
`V ${y2 + r}`,
|
|
369
|
+
`A ${r} ${r} 0 0 1 ${x2 + r} ${y2}`,
|
|
370
|
+
"Z"
|
|
371
|
+
].join(" ");
|
|
372
|
+
return `${outer} ${hole}`;
|
|
373
|
+
}
|
|
374
|
+
|
|
153
375
|
// src/dom/native_setter.ts
|
|
154
376
|
function prototypeSetter(proto, prop) {
|
|
155
377
|
const setter = Object.getOwnPropertyDescriptor(proto, prop).set;
|
|
@@ -173,7 +395,7 @@ function setNativeChecked(el2, checked) {
|
|
|
173
395
|
}
|
|
174
396
|
|
|
175
397
|
// src/dom/animations.ts
|
|
176
|
-
var
|
|
398
|
+
var ACCENT2 = "#4f46e5";
|
|
177
399
|
var ACCENT_HALO = "rgba(79, 70, 229, 0.4)";
|
|
178
400
|
var ACCENT_PROPERTY = "--ag-ui-accent";
|
|
179
401
|
function delay(ms) {
|
|
@@ -214,7 +436,7 @@ async function highlightThenClick(el2, options = {}) {
|
|
|
214
436
|
const highlightMs = options.highlightMs ?? 280;
|
|
215
437
|
const previousOutline = el2.style.outline;
|
|
216
438
|
const previousOffset = el2.style.outlineOffset;
|
|
217
|
-
el2.style.outline = `2px solid ${accentColor(el2,
|
|
439
|
+
el2.style.outline = `2px solid ${accentColor(el2, ACCENT2)}`;
|
|
218
440
|
el2.style.outlineOffset = "2px";
|
|
219
441
|
await delay(highlightMs);
|
|
220
442
|
el2.style.outline = previousOutline;
|
|
@@ -261,10 +483,21 @@ async function flashRing(el2, options, focusByDefault) {
|
|
|
261
483
|
if (flashMs <= 0) {
|
|
262
484
|
return;
|
|
263
485
|
}
|
|
486
|
+
if (options.scrim === true || options.gradient === true) {
|
|
487
|
+
const dismiss = showHighlightOverlay(el2, {
|
|
488
|
+
scrim: options.scrim === true,
|
|
489
|
+
gradient: options.gradient === true,
|
|
490
|
+
...options.color === void 0 ? {} : { color: options.color },
|
|
491
|
+
...options.ringPadding === void 0 ? {} : { padding: options.ringPadding }
|
|
492
|
+
});
|
|
493
|
+
await delay(flashMs);
|
|
494
|
+
dismiss();
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
264
497
|
const previousOutline = el2.style.outline;
|
|
265
498
|
const previousOffset = el2.style.outlineOffset;
|
|
266
499
|
const previousTransition = el2.style.transition;
|
|
267
|
-
const color = options.color ?? accentColor(el2,
|
|
500
|
+
const color = options.color ?? accentColor(el2, ACCENT2);
|
|
268
501
|
el2.style.outline = `3px solid ${color}`;
|
|
269
502
|
el2.style.outlineOffset = "2px";
|
|
270
503
|
const fadeMs = prefersReducedMotion() ? 0 : Math.round(flashMs * FLASH_FADE_RATIO);
|
|
@@ -314,7 +547,7 @@ async function selectOption(el2, value, options = {}) {
|
|
|
314
547
|
const highlightMs = options.highlightMs ?? 220;
|
|
315
548
|
const previousOutline = el2.style.outline;
|
|
316
549
|
const previousOffset = el2.style.outlineOffset;
|
|
317
|
-
el2.style.outline = `2px solid ${accentColor(el2,
|
|
550
|
+
el2.style.outline = `2px solid ${accentColor(el2, ACCENT2)}`;
|
|
318
551
|
el2.style.outlineOffset = "2px";
|
|
319
552
|
await motionDelay(highlightMs);
|
|
320
553
|
setNativeValue(el2, option.value);
|
|
@@ -337,7 +570,9 @@ async function toggleControl(el2, checked, options = {}) {
|
|
|
337
570
|
// src/tools/page_action_tools.ts
|
|
338
571
|
var PAGE_ACTIONS = {
|
|
339
572
|
SCROLL: "scroll",
|
|
340
|
-
DRAG: "drag"
|
|
573
|
+
DRAG: "drag",
|
|
574
|
+
/** The widget's own position: see `createChatSurfaceTools`. */
|
|
575
|
+
CHAT: "chat"
|
|
341
576
|
};
|
|
342
577
|
function createPageActionTools(enabled, resolveTarget) {
|
|
343
578
|
const tools = [];
|
|
@@ -547,6 +782,14 @@ function createRouteTools(getRouteMap, getNavigate) {
|
|
|
547
782
|
var DEFAULT_UI_STRINGS = {
|
|
548
783
|
title: "Assistant",
|
|
549
784
|
chatHistory: "Chat history",
|
|
785
|
+
closeHistory: "Close history",
|
|
786
|
+
searchConversations: "Search conversations",
|
|
787
|
+
queued: "Waiting to send",
|
|
788
|
+
removeQueued: 'Do not send "{text}"',
|
|
789
|
+
noMatches: "No conversations match that.",
|
|
790
|
+
chatMoved: "Moved this panel out of the way",
|
|
791
|
+
chatMinimised: "Minimised this panel",
|
|
792
|
+
undo: "Undo",
|
|
550
793
|
newChat: "New chat",
|
|
551
794
|
collapse: "Collapse",
|
|
552
795
|
expand: "Expand",
|
|
@@ -1750,10 +1993,18 @@ var CheckpointMenu = class {
|
|
|
1750
1993
|
};
|
|
1751
1994
|
|
|
1752
1995
|
// src/ui/clamp_launcher.ts
|
|
1753
|
-
function clampLauncher(launcher, viewport) {
|
|
1996
|
+
function clampLauncher(launcher, viewport, margin = SCREEN_EDGE_MARGIN) {
|
|
1997
|
+
const room = Math.min(margin, Math.max(0, (viewport.width - launcher.width) / 2));
|
|
1998
|
+
const vertical = Math.min(margin, Math.max(0, (viewport.height - launcher.height) / 2));
|
|
1754
1999
|
return {
|
|
1755
|
-
left: Math.max(
|
|
1756
|
-
|
|
2000
|
+
left: Math.max(
|
|
2001
|
+
viewport.left + room,
|
|
2002
|
+
Math.min(launcher.left, viewport.left + viewport.width - launcher.width - room)
|
|
2003
|
+
),
|
|
2004
|
+
top: Math.max(
|
|
2005
|
+
viewport.top + vertical,
|
|
2006
|
+
Math.min(launcher.top, viewport.top + viewport.height - launcher.height - vertical)
|
|
2007
|
+
)
|
|
1757
2008
|
};
|
|
1758
2009
|
}
|
|
1759
2010
|
|
|
@@ -1761,8 +2012,14 @@ function clampLauncher(launcher, viewport) {
|
|
|
1761
2012
|
function clampPanel(host, viewport, margin = EDGE_MARGIN) {
|
|
1762
2013
|
const width = host.right - host.left;
|
|
1763
2014
|
const height = host.bottom - host.top;
|
|
1764
|
-
const left = Math.max(
|
|
1765
|
-
|
|
2015
|
+
const left = Math.max(
|
|
2016
|
+
viewport.left + margin,
|
|
2017
|
+
Math.min(host.left, viewport.left + viewport.width - margin - width)
|
|
2018
|
+
);
|
|
2019
|
+
const top = Math.max(
|
|
2020
|
+
viewport.top + margin,
|
|
2021
|
+
Math.min(host.top, viewport.top + viewport.height - margin - height)
|
|
2022
|
+
);
|
|
1766
2023
|
return { left, top, right: left + width, bottom: top + height };
|
|
1767
2024
|
}
|
|
1768
2025
|
|
|
@@ -1967,6 +2224,7 @@ function enableLauncherDrag(launcher, options) {
|
|
|
1967
2224
|
const onUp = (up) => {
|
|
1968
2225
|
window.removeEventListener("pointermove", onMove);
|
|
1969
2226
|
window.removeEventListener("pointerup", onUp);
|
|
2227
|
+
window.removeEventListener("pointercancel", onUp);
|
|
1970
2228
|
if (!dragging) {
|
|
1971
2229
|
return;
|
|
1972
2230
|
}
|
|
@@ -1984,6 +2242,7 @@ function enableLauncherDrag(launcher, options) {
|
|
|
1984
2242
|
};
|
|
1985
2243
|
window.addEventListener("pointermove", onMove);
|
|
1986
2244
|
window.addEventListener("pointerup", onUp);
|
|
2245
|
+
window.addEventListener("pointercancel", onUp);
|
|
1987
2246
|
});
|
|
1988
2247
|
let pending = null;
|
|
1989
2248
|
const settle = () => {
|
|
@@ -2023,12 +2282,12 @@ function enableLauncherDrag(launcher, options) {
|
|
|
2023
2282
|
}
|
|
2024
2283
|
|
|
2025
2284
|
// src/ui/place_widget.ts
|
|
2026
|
-
function placeWidget(host, launcher, corner,
|
|
2285
|
+
function placeWidget(host, launcher, corner, screen) {
|
|
2027
2286
|
return {
|
|
2028
2287
|
hostInset: inset({
|
|
2029
2288
|
top: corner.y === "top" ? host.top : null,
|
|
2030
|
-
right: corner.x === "right" ?
|
|
2031
|
-
bottom: corner.y === "bottom" ?
|
|
2289
|
+
right: corner.x === "right" ? screen.width - host.right : null,
|
|
2290
|
+
bottom: corner.y === "bottom" ? screen.height - host.bottom : null,
|
|
2032
2291
|
left: corner.x === "left" ? host.left : null
|
|
2033
2292
|
}),
|
|
2034
2293
|
launcherInset: inset({
|
|
@@ -2045,11 +2304,11 @@ function inset(sides) {
|
|
|
2045
2304
|
}
|
|
2046
2305
|
|
|
2047
2306
|
// src/ui/launcher_placement.ts
|
|
2048
|
-
function launcherPlacement(launcher, panel, viewport, margin =
|
|
2049
|
-
const roomRunningRight = viewport.width - launcher.left;
|
|
2050
|
-
const roomRunningLeft = launcher.left + launcher.width;
|
|
2051
|
-
const roomRunningDown = viewport.height - launcher.top;
|
|
2052
|
-
const roomRunningUp = launcher.top + launcher.height;
|
|
2307
|
+
function launcherPlacement(launcher, panel, viewport, screen, margin = SCREEN_EDGE_MARGIN) {
|
|
2308
|
+
const roomRunningRight = viewport.left + viewport.width - launcher.left;
|
|
2309
|
+
const roomRunningLeft = launcher.left + launcher.width - viewport.left;
|
|
2310
|
+
const roomRunningDown = viewport.top + viewport.height - launcher.top;
|
|
2311
|
+
const roomRunningUp = launcher.top + launcher.height - viewport.top;
|
|
2053
2312
|
const corner = {
|
|
2054
2313
|
x: roomRunningRight >= roomRunningLeft ? "left" : "right",
|
|
2055
2314
|
y: roomRunningDown >= roomRunningUp ? "top" : "bottom"
|
|
@@ -2066,7 +2325,7 @@ function launcherPlacement(launcher, panel, viewport, margin = EDGE_MARGIN) {
|
|
|
2066
2325
|
viewport,
|
|
2067
2326
|
margin
|
|
2068
2327
|
);
|
|
2069
|
-
return { corner, ...placeWidget(host, launcher, corner,
|
|
2328
|
+
return { corner, ...placeWidget(host, launcher, corner, screen) };
|
|
2070
2329
|
}
|
|
2071
2330
|
|
|
2072
2331
|
// src/ui/message_actions.ts
|
|
@@ -2470,6 +2729,7 @@ function enablePanelDrag(handle, options) {
|
|
|
2470
2729
|
const onUp = (up) => {
|
|
2471
2730
|
window.removeEventListener("pointermove", onMove);
|
|
2472
2731
|
window.removeEventListener("pointerup", onUp);
|
|
2732
|
+
window.removeEventListener("pointercancel", onUp);
|
|
2473
2733
|
if (!dragging) {
|
|
2474
2734
|
return;
|
|
2475
2735
|
}
|
|
@@ -2479,6 +2739,7 @@ function enablePanelDrag(handle, options) {
|
|
|
2479
2739
|
event.preventDefault();
|
|
2480
2740
|
window.addEventListener("pointermove", onMove);
|
|
2481
2741
|
window.addEventListener("pointerup", onUp);
|
|
2742
|
+
window.addEventListener("pointercancel", onUp);
|
|
2482
2743
|
});
|
|
2483
2744
|
}
|
|
2484
2745
|
function onControl(event, handle) {
|
|
@@ -4222,111 +4483,111 @@ function createDOMPurify() {
|
|
|
4222
4483
|
}
|
|
4223
4484
|
var purify = createDOMPurify();
|
|
4224
4485
|
|
|
4225
|
-
// node_modules/.pnpm/marked@18.0.
|
|
4226
|
-
function
|
|
4486
|
+
// node_modules/.pnpm/marked@18.0.11/node_modules/marked/lib/marked.esm.js
|
|
4487
|
+
function A() {
|
|
4227
4488
|
return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
|
|
4228
4489
|
}
|
|
4229
|
-
var R =
|
|
4490
|
+
var R = A();
|
|
4230
4491
|
function j(l3) {
|
|
4231
4492
|
R = l3;
|
|
4232
4493
|
}
|
|
4233
4494
|
var z = { exec: () => null };
|
|
4234
|
-
function
|
|
4495
|
+
function I(l3) {
|
|
4235
4496
|
let e = [];
|
|
4236
4497
|
return (t) => {
|
|
4237
4498
|
let n = Math.max(0, Math.min(3, t - 1)), s = e[n];
|
|
4238
4499
|
return s || (s = l3(n), e[n] = s), s;
|
|
4239
4500
|
};
|
|
4240
4501
|
}
|
|
4241
|
-
function
|
|
4502
|
+
function k(l3, e = "") {
|
|
4242
4503
|
let t = typeof l3 == "string" ? l3 : l3.source, n = { replace: (s, r) => {
|
|
4243
4504
|
let i = typeof r == "string" ? r : r.source;
|
|
4244
4505
|
return i = i.replace(m.caret, "$1"), t = t.replace(s, i), n;
|
|
4245
4506
|
}, getRegex: () => new RegExp(t, e) };
|
|
4246
4507
|
return n;
|
|
4247
4508
|
}
|
|
4248
|
-
var
|
|
4509
|
+
var Oe = ((l3 = "") => {
|
|
4249
4510
|
try {
|
|
4250
4511
|
return !!new RegExp("(?<=1)(?<!1)" + l3);
|
|
4251
4512
|
} catch {
|
|
4252
4513
|
return false;
|
|
4253
4514
|
}
|
|
4254
4515
|
})();
|
|
4255
|
-
var m = { codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, outputLinkReplace: /\\([\[\]])/g, indentCodeCompensation: /^(\s+)(?:```)/, beginningSpace: /^\s+/, endingHash: /#$/, startingSpaceChar: /^ /, endingSpaceChar: / $/, nonSpaceChar: /[^ ]/, newLineCharGlobal: /\n/g, tabCharGlobal: /\t/g, multipleSpaceGlobal: /\s+/g, blankLine: /^[ \t]*$/, doubleBlankLine: /\n[ \t]*\n[ \t]*$/, blockquoteStart: /^ {0,3}>/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] +\S/, listReplaceTask: /^\[[ xX]\] +/, listTaskCheckbox: /\[[ xX]\]/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^<a /i, endATag: /^<\/a>/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^</, endAngleBracket: />$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex:
|
|
4256
|
-
var
|
|
4516
|
+
var m = { codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, outputLinkReplace: /\\([\[\]])/g, indentCodeCompensation: /^(\s+)(?:```)/, beginningSpace: /^\s+/, endingHash: /#$/, startingSpaceChar: /^ /, endingSpaceChar: / $/, nonSpaceChar: /[^ ]/, newLineCharGlobal: /\n/g, tabCharGlobal: /\t/g, multipleSpaceGlobal: /\s+/g, blankLine: /^[ \t]*$/, doubleBlankLine: /\n[ \t]*\n[ \t]*$/, blockquoteStart: /^ {0,3}>/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] +\S/, listReplaceTask: /^\[[ xX]\] +/, listTaskCheckbox: /\[[ xX]\]/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^<a /i, endATag: /^<\/a>/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^</, endAngleBracket: />$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: I((l3) => new RegExp(`^ {0,${l3}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`)), hrRegex: I((l3) => new RegExp(`^ {0,${l3}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`)), fencesBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}(?:\`\`\`|~~~)`)), headingBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}#`)), htmlBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}<(?:[a-z].*>|!--)`, "i")), blockquoteBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}>`)) };
|
|
4517
|
+
var Te = /^(?:[ \t]*(?:\n|$))+/;
|
|
4257
4518
|
var we = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
|
|
4258
4519
|
var ye = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
|
|
4259
4520
|
var q = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
|
|
4260
4521
|
var Pe = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
|
|
4261
4522
|
var U = / {0,3}(?:[*+-]|\d{1,9}[.)])/;
|
|
4262
4523
|
var oe = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/;
|
|
4263
|
-
var ae =
|
|
4264
|
-
var Se =
|
|
4524
|
+
var ae = k(oe).replace(/bull/g, U).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex();
|
|
4525
|
+
var Se = k(oe).replace(/bull/g, U).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex();
|
|
4265
4526
|
var K = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table|[ \t]+\n)[^\n]+)*)/;
|
|
4266
4527
|
var _e = /^[^\n]+/;
|
|
4267
4528
|
var W = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/;
|
|
4268
|
-
var $e =
|
|
4269
|
-
var Le =
|
|
4529
|
+
var $e = k(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", W).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
|
|
4530
|
+
var Le = k(/^(bull)([ \t][^\n]*?)?(?:\n|$)/).replace(/bull/g, U).getRegex();
|
|
4270
4531
|
var Q = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
|
|
4271
4532
|
var X = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
|
|
4272
|
-
var
|
|
4273
|
-
var le = (l3) =>
|
|
4533
|
+
var Ee = k("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n*|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>[^\\n]*\\n*|$)|<![A-Z][\\s\\S]*?(?:>[^\\n]*\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>[^\\n]*\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", X).replace("tag", Q).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
|
|
4534
|
+
var le = (l3) => k(K).replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", l3).replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex();
|
|
4274
4535
|
var ze = le(/ {0,3}(?:[*+-]|1[.)])[ \t]+[^ \t\n]/);
|
|
4275
|
-
var
|
|
4276
|
-
var
|
|
4277
|
-
var J = { blockquote:
|
|
4278
|
-
var se =
|
|
4279
|
-
var
|
|
4280
|
-
var
|
|
4536
|
+
var Me = le(/ {0,3}(?:[*+-]|\d{1,9}[.)])(?:[ \t]|\n|$)/);
|
|
4537
|
+
var Ae = k(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", Me).getRegex();
|
|
4538
|
+
var J = { blockquote: Ae, code: we, def: $e, fences: ye, heading: Pe, hr: q, html: Ee, lheading: ae, list: Le, newline: Te, paragraph: ze, table: z, text: _e };
|
|
4539
|
+
var se = k("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex();
|
|
4540
|
+
var Ie = { ...J, lheading: Se, table: se, paragraph: k(K).replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", se).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex() };
|
|
4541
|
+
var Ce = { ...J, html: k(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", X).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: z, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: k(K).replace("hr", q).replace("heading", ` *#{1,6} *[^
|
|
4281
4542
|
]`).replace("lheading", ae).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() };
|
|
4282
4543
|
var Be = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
|
|
4283
4544
|
var De = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
|
|
4284
|
-
var
|
|
4545
|
+
var ue = /^( {2,}|\\)\n(?!\s*$)/;
|
|
4285
4546
|
var qe = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
|
|
4286
4547
|
var _ = /[\p{P}\p{S}]/u;
|
|
4287
|
-
var
|
|
4548
|
+
var C = /[\s\p{P}\p{S}]/u;
|
|
4288
4549
|
var v = /[^\s\p{P}\p{S}]/u;
|
|
4289
|
-
var ve =
|
|
4550
|
+
var ve = k(/^((?![*_])punctSpace)/, "u").replace(/punctSpace/g, C).getRegex();
|
|
4290
4551
|
var He = /[\p{Pi}\p{Ps}"']/u;
|
|
4291
|
-
var
|
|
4552
|
+
var pe = /(?!~)[\p{P}\p{S}]/u;
|
|
4292
4553
|
var Ze = /(?!~)[\s\p{P}\p{S}]/u;
|
|
4293
4554
|
var Ge = /(?:[^\s\p{P}\p{S}]|~)/u;
|
|
4294
|
-
var Qe =
|
|
4555
|
+
var Qe = k(/link|precode-code|html/, "g").replace("link", /\[(?:[^\[\]`]|(?<a>`+)[^`]+\k<a>(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-", Oe ? "(?<!`)()" : "(^^|[^`])").replace("code", /(?<b>`+)[^`]+\k<b>(?!`)/).replace("html", /<(?! )[^<>]*?>/).getRegex();
|
|
4295
4556
|
var ce = /^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/;
|
|
4296
|
-
var Ne =
|
|
4297
|
-
var je =
|
|
4557
|
+
var Ne = k(ce, "u").replace(/punct/g, _).getRegex();
|
|
4558
|
+
var je = k(ce, "u").replace(/punct/g, pe).getRegex();
|
|
4298
4559
|
var Fe = /^(?:\*+(?:((?!\*)(?!openQuote)punct)|([^\s*]))?)|^_+(?:((?!_)(?!openQuote)punct)|([^\s_]))?/;
|
|
4299
|
-
var Ue =
|
|
4560
|
+
var Ue = k(Fe, "u").replace(/openQuote/g, He).replace(/punct/g, _).getRegex();
|
|
4300
4561
|
var he = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)";
|
|
4301
|
-
var Ke =
|
|
4302
|
-
var We =
|
|
4562
|
+
var Ke = k(he, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4563
|
+
var We = k(he, "gu").replace(/notPunctSpace/g, Ge).replace(/punctSpace/g, Ze).replace(/punct/g, pe).getRegex();
|
|
4303
4564
|
var Xe = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)[\\s](\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|(?:(?!\\*)punct|notPunctSpace)(\\*+)(?!\\*)(?=notPunctSpace)";
|
|
4304
|
-
var Je =
|
|
4305
|
-
var Ve =
|
|
4565
|
+
var Je = k(Xe, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4566
|
+
var Ve = k("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4306
4567
|
var Ye = "^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)[\\s](_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)|(?:(?!_)punct|notPunctSpace)(_+)(?!_)(?=notPunctSpace)";
|
|
4307
|
-
var et =
|
|
4308
|
-
var tt =
|
|
4568
|
+
var et = k(Ye, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4569
|
+
var tt = k(/^~~?(?:((?!~)punct)|[^\s~])/, "u").replace(/punct/g, _).getRegex();
|
|
4309
4570
|
var nt = "^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)";
|
|
4310
|
-
var rt =
|
|
4311
|
-
var st =
|
|
4312
|
-
var it =
|
|
4313
|
-
var ot =
|
|
4314
|
-
var at =
|
|
4571
|
+
var rt = k(nt, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
|
|
4572
|
+
var st = k(/\\(punct)/, "gu").replace(/punct/g, _).getRegex();
|
|
4573
|
+
var it = k(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
|
|
4574
|
+
var ot = k(X).replace("(?:-->|$)", "-->").getRegex();
|
|
4575
|
+
var at = k("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment", ot).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
|
|
4315
4576
|
var G = /(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/;
|
|
4316
|
-
var lt =
|
|
4317
|
-
var
|
|
4318
|
-
var
|
|
4319
|
-
var
|
|
4577
|
+
var lt = k(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label", G).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]+|(?=\))/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
|
|
4578
|
+
var ke = k(/^!?\[(label)\]\[(ref)\]/).replace("label", G).replace("ref", W).getRegex();
|
|
4579
|
+
var de = k(/^!?\[(ref)\](?:\[\])?/).replace("ref", W).getRegex();
|
|
4580
|
+
var ut = k("reflink|nolink(?!\\()", "g").replace("reflink", ke).replace("nolink", de).getRegex();
|
|
4320
4581
|
var ie = /[hH][tT][tT][pP][sS]?|[fF][tT][pP]/;
|
|
4321
|
-
var V = { _backpedal: z, anyPunctuation: st, autolink: it, blockSkip: Qe, br:
|
|
4322
|
-
var
|
|
4323
|
-
var F = { ...V, emStrongRDelimAst: We, emStrongLDelim: je, delLDelim: tt, delRDelim: rt, url:
|
|
4324
|
-
var ct = { ...F, br:
|
|
4325
|
-
var H = { normal: J, gfm:
|
|
4326
|
-
var B = { normal: V, gfm: F, breaks: ct, pedantic:
|
|
4582
|
+
var V = { _backpedal: z, anyPunctuation: st, autolink: it, blockSkip: Qe, br: ue, code: De, del: z, delLDelim: z, delRDelim: z, emStrongLDelim: Ne, emStrongRDelimAst: Ke, emStrongRDelimUnd: Ve, escape: Be, link: lt, nolink: de, punctuation: ve, reflink: ke, reflinkSearch: ut, tag: at, text: qe, url: z };
|
|
4583
|
+
var pt = { ...V, emStrongLDelim: Ue, emStrongRDelimAst: Je, emStrongRDelimUnd: et, link: k(/^!?\[(label)\]\((.*?)\)/).replace("label", G).getRegex(), reflink: k(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", G).getRegex() };
|
|
4584
|
+
var F = { ...V, emStrongRDelimAst: We, emStrongLDelim: je, delLDelim: tt, delRDelim: rt, url: k(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol", ie).replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(), _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/, text: k(/^(`+|~+|[^`~])(?:(?=[`~])|(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|protocol:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/).replace("protocol", ie).getRegex() };
|
|
4585
|
+
var ct = { ...F, br: k(ue).replace("{2,}", "*").getRegex(), text: k(F.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex() };
|
|
4586
|
+
var H = { normal: J, gfm: Ie, pedantic: Ce };
|
|
4587
|
+
var B = { normal: V, gfm: F, breaks: ct, pedantic: pt };
|
|
4327
4588
|
var ht = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
4328
4589
|
var ge = (l3) => ht[l3];
|
|
4329
|
-
function
|
|
4590
|
+
function T(l3, e) {
|
|
4330
4591
|
if (e) {
|
|
4331
4592
|
if (m.escapeTest.test(l3)) return l3.replace(m.escapeReplace, ge);
|
|
4332
4593
|
} else if (m.escapeTestNoEncode.test(l3)) return l3.replace(m.escapeReplaceNoEncode, ge);
|
|
@@ -4342,9 +4603,9 @@ function Y(l3) {
|
|
|
4342
4603
|
}
|
|
4343
4604
|
function ee(l3, e) {
|
|
4344
4605
|
let t = l3.replace(m.findPipe, (r, i, o) => {
|
|
4345
|
-
let
|
|
4346
|
-
for (; --a >= 0 && o[a] === "\\"; )
|
|
4347
|
-
return
|
|
4606
|
+
let u = false, a = i;
|
|
4607
|
+
for (; --a >= 0 && o[a] === "\\"; ) u = !u;
|
|
4608
|
+
return u ? "|" : " |";
|
|
4348
4609
|
}), n = t.split(m.splitPipe), s = 0;
|
|
4349
4610
|
if (n[0].trim() || n.shift(), n.length > 0 && !n.at(-1)?.trim() && n.pop(), e) if (n.length > e) n.splice(e);
|
|
4350
4611
|
else for (; n.length < e; ) n.push("");
|
|
@@ -4387,12 +4648,21 @@ function me(l3, e = 0) {
|
|
|
4387
4648
|
return n;
|
|
4388
4649
|
}
|
|
4389
4650
|
function xe(l3, e, t, n, s) {
|
|
4390
|
-
let r = e.href, i = e.title || null, o = l3[1].replace(s.other.outputLinkReplace, "$1");
|
|
4651
|
+
let r = e.href, i = e.title || null, o = l3[1].replace(s.other.outputLinkReplace, "$1"), u = l3[0].charAt(0) === "!";
|
|
4391
4652
|
n.state.inLink = true;
|
|
4392
|
-
let
|
|
4393
|
-
|
|
4653
|
+
let a = n.state.linkEmitted, p = n.state.inRawBlock;
|
|
4654
|
+
n.state.linkEmitted = false;
|
|
4655
|
+
let c = n.inlineTokens(o), h = n.state.linkEmitted;
|
|
4656
|
+
if (n.state.linkEmitted = a, n.state.inLink = false, !u) {
|
|
4657
|
+
if (h) {
|
|
4658
|
+
n.state.inRawBlock = p;
|
|
4659
|
+
return;
|
|
4660
|
+
}
|
|
4661
|
+
n.state.linkEmitted = true;
|
|
4662
|
+
}
|
|
4663
|
+
return { type: u ? "image" : "link", raw: t, href: r, title: i, text: o, tokens: c };
|
|
4394
4664
|
}
|
|
4395
|
-
function
|
|
4665
|
+
function kt(l3, e, t) {
|
|
4396
4666
|
let n = l3.match(t.other.indentCodeCompensation);
|
|
4397
4667
|
if (n === null) return e;
|
|
4398
4668
|
let s = n[1];
|
|
@@ -4426,7 +4696,7 @@ var y = class {
|
|
|
4426
4696
|
fences(e) {
|
|
4427
4697
|
let t = this.rules.block.fences.exec(e);
|
|
4428
4698
|
if (t) {
|
|
4429
|
-
let n = t[0], s =
|
|
4699
|
+
let n = t[0], s = kt(n, t[3] || "", this.rules);
|
|
4430
4700
|
return { type: "code", raw: n, lang: t[2] ? t[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : t[2], text: s };
|
|
4431
4701
|
}
|
|
4432
4702
|
}
|
|
@@ -4454,33 +4724,33 @@ var y = class {
|
|
|
4454
4724
|
`).split(`
|
|
4455
4725
|
`), s = "", r = "", i = [];
|
|
4456
4726
|
for (; n.length > 0; ) {
|
|
4457
|
-
let o = false,
|
|
4458
|
-
for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a]))
|
|
4459
|
-
else if (!o)
|
|
4727
|
+
let o = false, u = [], a;
|
|
4728
|
+
for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a])) u.push(n[a]), o = true;
|
|
4729
|
+
else if (!o) u.push(n[a]);
|
|
4460
4730
|
else break;
|
|
4461
4731
|
n = n.slice(a);
|
|
4462
|
-
let
|
|
4463
|
-
`), c =
|
|
4732
|
+
let p = u.join(`
|
|
4733
|
+
`), c = p.replace(this.rules.other.blockquoteSetextReplace, `
|
|
4464
4734
|
$1`).replace(this.rules.other.blockquoteSetextReplace2, "");
|
|
4465
4735
|
s = s ? `${s}
|
|
4466
|
-
${
|
|
4736
|
+
${p}` : p, r = r ? `${r}
|
|
4467
4737
|
${c}` : c;
|
|
4468
4738
|
let h = this.lexer.state.top;
|
|
4469
4739
|
if (this.lexer.state.top = true, this.lexer.blockTokens(c, i, true), this.lexer.state.top = h, n.length === 0) break;
|
|
4470
|
-
let
|
|
4471
|
-
if (
|
|
4472
|
-
if (
|
|
4473
|
-
let
|
|
4474
|
-
`), w =
|
|
4475
|
-
` + g.replace(this.rules.other.blockquoteSetextReplace2, ""),
|
|
4476
|
-
i[i.length - 1] =
|
|
4477
|
-
${g}`, r = r.substring(0, r.length -
|
|
4740
|
+
let d = i.at(-1);
|
|
4741
|
+
if (d?.type === "code") break;
|
|
4742
|
+
if (d?.type === "blockquote") {
|
|
4743
|
+
let O = d, g = n.join(`
|
|
4744
|
+
`), w = O.raw + `
|
|
4745
|
+
` + g.replace(this.rules.other.blockquoteSetextReplace2, ""), E = this.blockquote(w);
|
|
4746
|
+
i[i.length - 1] = E, s = `${s}
|
|
4747
|
+
${g}`, r = r.substring(0, r.length - O.text.length) + E.text;
|
|
4478
4748
|
break;
|
|
4479
|
-
} else if (
|
|
4480
|
-
let
|
|
4749
|
+
} else if (d?.type === "list") {
|
|
4750
|
+
let O = d, g = O.raw + `
|
|
4481
4751
|
` + n.join(`
|
|
4482
4752
|
`), w = this.list(g);
|
|
4483
|
-
i[i.length - 1] = w, s = s.substring(0, s.length -
|
|
4753
|
+
i[i.length - 1] = w, s = s.substring(0, s.length - d.raw.length) + w.raw, r = r.substring(0, r.length - O.raw.length) + w.raw, n = g.substring(i.at(-1).raw.length).split(`
|
|
4484
4754
|
`);
|
|
4485
4755
|
continue;
|
|
4486
4756
|
}
|
|
@@ -4495,44 +4765,44 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4495
4765
|
n = s ? `\\d{1,9}\\${n.slice(-1)}` : `\\${n}`, this.options.pedantic && (n = s ? n : "[*+-]");
|
|
4496
4766
|
let i = this.rules.other.listItemRegex(n), o = false;
|
|
4497
4767
|
for (; e; ) {
|
|
4498
|
-
let a = false,
|
|
4768
|
+
let a = false, p = "", c = "";
|
|
4499
4769
|
if (!(t = i.exec(e)) || this.rules.block.hr.test(e)) break;
|
|
4500
|
-
|
|
4770
|
+
p = t[0], e = e.substring(p.length);
|
|
4501
4771
|
let h = me(t[2].split(`
|
|
4502
|
-
`, 1)[0], t[1].length),
|
|
4503
|
-
`, 1)[0],
|
|
4504
|
-
if (this.options.pedantic ? (g = 2, c = h.trimStart()) :
|
|
4505
|
-
`, e = e.substring(
|
|
4506
|
-
let w = this.rules.other.nextBulletRegex(g),
|
|
4772
|
+
`, 1)[0], t[1].length), d = e.split(`
|
|
4773
|
+
`, 1)[0], O = !h.trim(), g = 0;
|
|
4774
|
+
if (this.options.pedantic ? (g = 2, c = h.trimStart()) : O ? g = t[1].length + 1 : (g = h.search(this.rules.other.nonSpaceChar), g = g > 4 ? 1 : g, c = h.slice(g), g += t[1].length), O && this.rules.other.blankLine.test(d) && (p += d + `
|
|
4775
|
+
`, e = e.substring(d.length + 1), a = true), !a) {
|
|
4776
|
+
let w = this.rules.other.nextBulletRegex(g), E = this.rules.other.hrRegex(g), ne = this.rules.other.fencesBeginRegex(g), re = this.rules.other.headingBeginRegex(g), be = this.rules.other.htmlBeginRegex(g), Re = this.rules.other.blockquoteBeginRegex(g);
|
|
4507
4777
|
for (; e; ) {
|
|
4508
4778
|
let N = e.split(`
|
|
4509
4779
|
`, 1)[0], D;
|
|
4510
|
-
if (
|
|
4511
|
-
if (D.search(this.rules.other.nonSpaceChar) >= g || !
|
|
4780
|
+
if (d = N, this.options.pedantic ? (d = d.replace(this.rules.other.listReplaceNesting, " "), D = d) : D = d.replace(this.rules.other.tabCharGlobal, " "), ne.test(d) || re.test(d) || be.test(d) || Re.test(d) || w.test(d) || E.test(d)) break;
|
|
4781
|
+
if (D.search(this.rules.other.nonSpaceChar) >= g || !d.trim()) c += `
|
|
4512
4782
|
` + D.slice(g);
|
|
4513
4783
|
else {
|
|
4514
|
-
if (
|
|
4784
|
+
if (O || h.replace(this.rules.other.tabCharGlobal, " ").search(this.rules.other.nonSpaceChar) >= 4 || ne.test(h) || re.test(h) || E.test(h)) break;
|
|
4515
4785
|
c += `
|
|
4516
|
-
` +
|
|
4786
|
+
` + d;
|
|
4517
4787
|
}
|
|
4518
|
-
|
|
4788
|
+
O = !d.trim(), p += N + `
|
|
4519
4789
|
`, e = e.substring(N.length + 1), h = D.slice(g);
|
|
4520
4790
|
}
|
|
4521
4791
|
}
|
|
4522
|
-
r.loose || (o ? r.loose = true : this.rules.other.doubleBlankLine.test(
|
|
4792
|
+
r.loose || (o ? r.loose = true : this.rules.other.doubleBlankLine.test(p) && (o = true)), r.items.push({ type: "list_item", raw: p, task: !!this.options.gfm && this.rules.other.listIsTask.test(c), loose: false, text: c, tokens: [] }), r.raw += p;
|
|
4523
4793
|
}
|
|
4524
|
-
let
|
|
4525
|
-
if (
|
|
4794
|
+
let u = r.items.at(-1);
|
|
4795
|
+
if (u) u.raw = u.raw.trimEnd(), u.text = u.text.trimEnd();
|
|
4526
4796
|
else return;
|
|
4527
4797
|
r.raw = r.raw.trimEnd();
|
|
4528
4798
|
for (let a of r.items) if (this.lexer.state.top = false, a.tokens = this.lexer.blockTokens(a.text, []), !r.loose) {
|
|
4529
|
-
let
|
|
4799
|
+
let p = a.tokens.filter((h) => h.type === "space"), c = p.length > 0 && p.some((h) => this.rules.other.anyLine.test(h.raw));
|
|
4530
4800
|
r.loose = c;
|
|
4531
4801
|
}
|
|
4532
4802
|
for (let a of r.items) {
|
|
4533
|
-
let
|
|
4534
|
-
if (a.task && (
|
|
4535
|
-
a.text = a.text.replace(this.rules.other.listReplaceTask, ""),
|
|
4803
|
+
let p = a.tokens[0];
|
|
4804
|
+
if (a.task && (p?.type === "text" || p?.type === "paragraph")) {
|
|
4805
|
+
a.text = a.text.replace(this.rules.other.listReplaceTask, ""), p.raw = p.raw.replace(this.rules.other.listReplaceTask, ""), p.text = p.text.replace(this.rules.other.listReplaceTask, "");
|
|
4536
4806
|
for (let h = this.lexer.inlineQueue.length - 1; h >= 0; h--) if (this.rules.other.listIsTask.test(this.lexer.inlineQueue[h].src)) {
|
|
4537
4807
|
this.lexer.inlineQueue[h].src = this.lexer.inlineQueue[h].src.replace(this.rules.other.listReplaceTask, "");
|
|
4538
4808
|
break;
|
|
@@ -4546,7 +4816,7 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4546
4816
|
}
|
|
4547
4817
|
if (r.loose) for (let a of r.items) {
|
|
4548
4818
|
a.loose = true;
|
|
4549
|
-
for (let
|
|
4819
|
+
for (let p of a.tokens) p.type === "text" && (p.type = "paragraph");
|
|
4550
4820
|
}
|
|
4551
4821
|
return r;
|
|
4552
4822
|
}
|
|
@@ -4575,7 +4845,7 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4575
4845
|
if (n.length === s.length) {
|
|
4576
4846
|
for (let o of s) this.rules.other.tableAlignRight.test(o) ? i.align.push("right") : this.rules.other.tableAlignCenter.test(o) ? i.align.push("center") : this.rules.other.tableAlignLeft.test(o) ? i.align.push("left") : i.align.push(null);
|
|
4577
4847
|
for (let o = 0; o < n.length; o++) i.header.push({ text: n[o], tokens: this.lexer.inline(n[o]), header: true, align: i.align[o] });
|
|
4578
|
-
for (let o of r) i.rows.push(ee(o, i.header.length).map((
|
|
4848
|
+
for (let o of r) i.rows.push(ee(o, i.header.length).map((u, a) => ({ text: u, tokens: this.lexer.inline(u), header: false, align: i.align[a] })));
|
|
4579
4849
|
return i;
|
|
4580
4850
|
}
|
|
4581
4851
|
}
|
|
@@ -4619,8 +4889,8 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4619
4889
|
let i = fe(t[2], "()");
|
|
4620
4890
|
if (i === -2) return;
|
|
4621
4891
|
if (i > -1) {
|
|
4622
|
-
let
|
|
4623
|
-
t[2] = t[2].substring(0, i), t[0] = t[0].substring(0,
|
|
4892
|
+
let u = (t[0].indexOf("!") === 0 ? 5 : 4) + t[1].length + i;
|
|
4893
|
+
t[2] = t[2].substring(0, i), t[0] = t[0].substring(0, u).trim(), t[3] = "";
|
|
4624
4894
|
}
|
|
4625
4895
|
}
|
|
4626
4896
|
let s = t[2], r = "";
|
|
@@ -4646,25 +4916,25 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4646
4916
|
let s = this.rules.inline.emStrongLDelim.exec(e);
|
|
4647
4917
|
if (!s || !s[1] && !s[2] && !s[3] && !s[4] || s[4] && n.match(this.rules.other.unicodeAlphaNumeric)) return;
|
|
4648
4918
|
if (!(s[1] || s[3] || "") || !n || this.rules.inline.punctuation.exec(n)) {
|
|
4649
|
-
let i = [...s[0]].length - 1, o,
|
|
4650
|
-
for (
|
|
4919
|
+
let i = [...s[0]].length - 1, o, u, a = i, p = 0, c = s[0][0], h = n === c, d = c === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
|
|
4920
|
+
for (d.lastIndex = 0, t = t.slice(-1 * e.length + i); (s = d.exec(t)) !== null; ) {
|
|
4651
4921
|
if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o) continue;
|
|
4652
|
-
if (
|
|
4653
|
-
a +=
|
|
4922
|
+
if (u = [...o].length, s[3] || s[4]) {
|
|
4923
|
+
a += u;
|
|
4654
4924
|
continue;
|
|
4655
4925
|
} else if (s[5] || s[6]) {
|
|
4656
|
-
if (i % 3 && !((i +
|
|
4657
|
-
|
|
4926
|
+
if (i % 3 && !((i + u) % 3)) {
|
|
4927
|
+
p += u;
|
|
4658
4928
|
continue;
|
|
4659
4929
|
}
|
|
4660
4930
|
if (h) break;
|
|
4661
4931
|
}
|
|
4662
|
-
if (a -=
|
|
4663
|
-
|
|
4664
|
-
let
|
|
4665
|
-
if (Math.min(i,
|
|
4666
|
-
let
|
|
4667
|
-
return { type: "em", raw: g, text:
|
|
4932
|
+
if (a -= u, a > 0) continue;
|
|
4933
|
+
u = Math.min(u, u + a + p);
|
|
4934
|
+
let O = [...s[0]][0].length, g = e.slice(0, i + s.index + O + u);
|
|
4935
|
+
if (Math.min(i, u) % 2) {
|
|
4936
|
+
let E = g.slice(1, -1);
|
|
4937
|
+
return { type: "em", raw: g, text: E, tokens: this.lexer.inlineTokens(E) };
|
|
4668
4938
|
}
|
|
4669
4939
|
let w = g.slice(2, -2);
|
|
4670
4940
|
return { type: "strong", raw: g, text: w, tokens: this.lexer.inlineTokens(w) };
|
|
@@ -4686,17 +4956,17 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
|
|
|
4686
4956
|
let s = this.rules.inline.delLDelim.exec(e);
|
|
4687
4957
|
if (!s) return;
|
|
4688
4958
|
if (!(s[1] || "") || !n || this.rules.inline.punctuation.exec(n)) {
|
|
4689
|
-
let i = [...s[0]].length - 1, o,
|
|
4690
|
-
for (
|
|
4691
|
-
if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o || (
|
|
4959
|
+
let i = [...s[0]].length - 1, o, u, a = i, p = this.rules.inline.delRDelim;
|
|
4960
|
+
for (p.lastIndex = 0, t = t.slice(-1 * e.length + i); (s = p.exec(t)) !== null; ) {
|
|
4961
|
+
if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o || (u = [...o].length, u !== i)) continue;
|
|
4692
4962
|
if (s[3] || s[4]) {
|
|
4693
|
-
a +=
|
|
4963
|
+
a += u;
|
|
4694
4964
|
continue;
|
|
4695
4965
|
}
|
|
4696
|
-
if (a -=
|
|
4697
|
-
|
|
4698
|
-
let c = [...s[0]][0].length, h = e.slice(0, i + s.index + c +
|
|
4699
|
-
return { type: "del", raw: h, text:
|
|
4966
|
+
if (a -= u, a > 0) continue;
|
|
4967
|
+
u = Math.min(u, u + a);
|
|
4968
|
+
let c = [...s[0]][0].length, h = e.slice(0, i + s.index + c + u), d = h.slice(i, -i);
|
|
4969
|
+
return { type: "del", raw: h, text: d, tokens: this.lexer.inlineTokens(d) };
|
|
4700
4970
|
}
|
|
4701
4971
|
}
|
|
4702
4972
|
}
|
|
@@ -4737,7 +5007,7 @@ var x = class l {
|
|
|
4737
5007
|
inlineQueue;
|
|
4738
5008
|
tokenizer;
|
|
4739
5009
|
constructor(e) {
|
|
4740
|
-
this.tokens = [], this.tokens.links = /* @__PURE__ */ Object.create(null), this.options = e || R, this.options.tokenizer = this.options.tokenizer || new y(), this.tokenizer = this.options.tokenizer, this.tokenizer.options = this.options, this.tokenizer.lexer = this, this.inlineQueue = [], this.state = { inLink: false, inRawBlock: false, top: true };
|
|
5010
|
+
this.tokens = [], this.tokens.links = /* @__PURE__ */ Object.create(null), this.options = e || R, this.options.tokenizer = this.options.tokenizer || new y(), this.tokenizer = this.options.tokenizer, this.tokenizer.options = this.options, this.tokenizer.lexer = this, this.inlineQueue = [], this.state = { inLink: false, inRawBlock: false, linkEmitted: false, top: true };
|
|
4741
5011
|
let t = { other: m, block: H.normal, inline: B.normal };
|
|
4742
5012
|
this.options.pedantic ? (t.block = H.pedantic, t.inline = B.pedantic) : this.options.gfm && (t.block = H.gfm, this.options.breaks ? t.inline = B.breaks : t.inline = B.gfm), this.tokenizer.rules = t;
|
|
4743
5013
|
}
|
|
@@ -4829,9 +5099,9 @@ var x = class l {
|
|
|
4829
5099
|
}
|
|
4830
5100
|
let i = e;
|
|
4831
5101
|
if (this.options.extensions?.startBlock) {
|
|
4832
|
-
let o = 1 / 0,
|
|
4833
|
-
this.options.extensions.startBlock.forEach((
|
|
4834
|
-
a =
|
|
5102
|
+
let o = 1 / 0, u = e.slice(1), a;
|
|
5103
|
+
this.options.extensions.startBlock.forEach((p) => {
|
|
5104
|
+
a = p.call({ lexer: this }, u), typeof a == "number" && a >= 0 && (o = Math.min(o, a));
|
|
4835
5105
|
}), o < 1 / 0 && o >= 0 && (i = e.substring(0, o + 1));
|
|
4836
5106
|
}
|
|
4837
5107
|
if (this.state.top && (r = this.tokenizer.paragraph(i))) {
|
|
@@ -4861,16 +5131,34 @@ var x = class l {
|
|
|
4861
5131
|
inline(e, t = []) {
|
|
4862
5132
|
return this.inlineQueue.push({ src: e, tokens: t }), t;
|
|
4863
5133
|
}
|
|
5134
|
+
linkInText(e) {
|
|
5135
|
+
if (!e.includes("[")) return false;
|
|
5136
|
+
let t = this.tokenizer.rules.inline.link;
|
|
5137
|
+
for (let n of e.matchAll(this.tokenizer.rules.inline.blockSkip)) if (t.test(n[0]) && e.charAt(n.index - 1) !== "!") return true;
|
|
5138
|
+
for (let n of e.matchAll(this.tokenizer.rules.inline.reflinkSearch)) {
|
|
5139
|
+
let s = n[0], r = s.lastIndexOf("[");
|
|
5140
|
+
if (!(s.charAt(0) === "!" || !Object.hasOwn(this.tokens.links, s.slice(r + 1, -1))) && !(r > 1 && this.linkInText(s.slice(1, r - 1)))) return true;
|
|
5141
|
+
}
|
|
5142
|
+
return false;
|
|
5143
|
+
}
|
|
4864
5144
|
inlineTokens(e, t = []) {
|
|
4865
5145
|
this.tokenizer.lexer = this;
|
|
4866
5146
|
let n = e;
|
|
4867
|
-
if (this.tokens.links) {
|
|
4868
|
-
let o =
|
|
4869
|
-
|
|
5147
|
+
if (this.tokens.links && e.includes("[")) {
|
|
5148
|
+
let o = this.tokenizer.rules.inline.reflinkSearch, u = (a) => {
|
|
5149
|
+
let p = a.lastIndexOf("[");
|
|
5150
|
+
if (!Object.hasOwn(this.tokens.links, a.slice(p + 1, -1))) return a;
|
|
5151
|
+
if (p > 1 && a.charAt(0) !== "!") {
|
|
5152
|
+
let c = a.slice(1, p - 1);
|
|
5153
|
+
if (this.linkInText(c)) return "[" + c.replace(o, u) + "][" + "a".repeat(a.length - p - 2) + "]";
|
|
5154
|
+
}
|
|
5155
|
+
return "[" + "a".repeat(a.length - 2) + "]";
|
|
5156
|
+
};
|
|
5157
|
+
n = n.replace(o, u);
|
|
4870
5158
|
}
|
|
4871
|
-
n = n.replace(this.tokenizer.rules.inline.anyPunctuation, (o) => "+".repeat(o.length)), n = n.replace(this.tokenizer.rules.inline.blockSkip, (o,
|
|
4872
|
-
let
|
|
4873
|
-
return o.slice(0,
|
|
5159
|
+
n = n.replace(this.tokenizer.rules.inline.anyPunctuation, (o) => "+".repeat(o.length)), n = n.replace(this.tokenizer.rules.inline.blockSkip, (o, u, a) => {
|
|
5160
|
+
let p = a ? a.length : 0;
|
|
5161
|
+
return o.slice(0, p) + "[" + "a".repeat(o.length - p - 2) + "]";
|
|
4874
5162
|
}), n = this.options.hooks?.emStrongMask?.call({ lexer: this }, n) ?? n;
|
|
4875
5163
|
let s = false, r = "", i = 1 / 0;
|
|
4876
5164
|
for (; e; ) {
|
|
@@ -4924,14 +5212,14 @@ var x = class l {
|
|
|
4924
5212
|
e = e.substring(o.raw.length), t.push(o);
|
|
4925
5213
|
continue;
|
|
4926
5214
|
}
|
|
4927
|
-
let
|
|
5215
|
+
let u = e;
|
|
4928
5216
|
if (this.options.extensions?.startInline) {
|
|
4929
|
-
let a = 1 / 0,
|
|
5217
|
+
let a = 1 / 0, p = e.slice(1), c;
|
|
4930
5218
|
this.options.extensions.startInline.forEach((h) => {
|
|
4931
|
-
c = h.call({ lexer: this },
|
|
4932
|
-
}), a < 1 / 0 && a >= 0 && (
|
|
5219
|
+
c = h.call({ lexer: this }, p), typeof c == "number" && c >= 0 && (a = Math.min(a, c));
|
|
5220
|
+
}), a < 1 / 0 && a >= 0 && (u = e.substring(0, a + 1));
|
|
4933
5221
|
}
|
|
4934
|
-
if (o = this.tokenizer.inlineText(
|
|
5222
|
+
if (o = this.tokenizer.inlineText(u)) {
|
|
4935
5223
|
e = e.substring(o.raw.length), o.raw.slice(-1) !== "_" && (r = o.raw.slice(-1)), s = true;
|
|
4936
5224
|
let a = t.at(-1);
|
|
4937
5225
|
a?.type === "text" ? (a.raw += o.raw, a.text += o.text) : t.push(o);
|
|
@@ -4962,8 +5250,8 @@ var P = class {
|
|
|
4962
5250
|
code({ text: e, lang: t, escaped: n }) {
|
|
4963
5251
|
let s = (t || "").match(m.notSpaceStart)?.[0], r = e.replace(m.endingNewline, "") + `
|
|
4964
5252
|
`;
|
|
4965
|
-
return s ? '<pre><code class="language-' +
|
|
4966
|
-
` : "<pre><code>" + (n ? r :
|
|
5253
|
+
return s ? '<pre><code class="language-' + T(s) + '">' + (n ? r : T(r, true)) + `</code></pre>
|
|
5254
|
+
` : "<pre><code>" + (n ? r : T(r, true)) + `</code></pre>
|
|
4967
5255
|
`;
|
|
4968
5256
|
}
|
|
4969
5257
|
blockquote({ tokens: e }) {
|
|
@@ -4988,8 +5276,8 @@ ${this.parser.parse(e)}</blockquote>
|
|
|
4988
5276
|
list(e) {
|
|
4989
5277
|
let t = e.ordered, n = e.start, s = "";
|
|
4990
5278
|
for (let o = 0; o < e.items.length; o++) {
|
|
4991
|
-
let
|
|
4992
|
-
s += this.listitem(
|
|
5279
|
+
let u = e.items[o];
|
|
5280
|
+
s += this.listitem(u);
|
|
4993
5281
|
}
|
|
4994
5282
|
let r = t ? "ol" : "ul", i = t && n !== 1 ? ' start="' + n + '"' : "";
|
|
4995
5283
|
return "<" + r + i + `>
|
|
@@ -5041,7 +5329,7 @@ ${e}</tr>
|
|
|
5041
5329
|
return `<em>${this.parser.parseInline(e)}</em>`;
|
|
5042
5330
|
}
|
|
5043
5331
|
codespan({ text: e }) {
|
|
5044
|
-
return `<code>${
|
|
5332
|
+
return `<code>${T(e, true)}</code>`;
|
|
5045
5333
|
}
|
|
5046
5334
|
br(e) {
|
|
5047
5335
|
return "<br>";
|
|
@@ -5054,18 +5342,18 @@ ${e}</tr>
|
|
|
5054
5342
|
if (r === null) return s;
|
|
5055
5343
|
e = r;
|
|
5056
5344
|
let i = '<a href="' + e + '"';
|
|
5057
|
-
return t && (i += ' title="' +
|
|
5345
|
+
return t && (i += ' title="' + T(t) + '"'), i += ">" + s + "</a>", i;
|
|
5058
5346
|
}
|
|
5059
5347
|
image({ href: e, title: t, text: n, tokens: s }) {
|
|
5060
5348
|
s && (n = this.parser.parseInline(s, this.parser.textRenderer));
|
|
5061
5349
|
let r = Y(e);
|
|
5062
|
-
if (r === null) return
|
|
5350
|
+
if (r === null) return T(n);
|
|
5063
5351
|
e = r;
|
|
5064
|
-
let i = `<img src="${e}" alt="${
|
|
5065
|
-
return t && (i += ` title="${
|
|
5352
|
+
let i = `<img src="${e}" alt="${T(n)}"`;
|
|
5353
|
+
return t && (i += ` title="${T(t)}"`), i += ">", i;
|
|
5066
5354
|
}
|
|
5067
5355
|
text(e) {
|
|
5068
|
-
return "tokens" in e && e.tokens ? this.parser.parseInline(e.tokens) : "escaped" in e && e.escaped ? e.text :
|
|
5356
|
+
return "tokens" in e && e.tokens ? this.parser.parseInline(e.tokens) : "escaped" in e && e.escaped ? e.text : T(e.text);
|
|
5069
5357
|
}
|
|
5070
5358
|
};
|
|
5071
5359
|
var L = class {
|
|
@@ -5280,7 +5568,7 @@ var S = class {
|
|
|
5280
5568
|
}
|
|
5281
5569
|
};
|
|
5282
5570
|
var Z = class {
|
|
5283
|
-
defaults =
|
|
5571
|
+
defaults = A();
|
|
5284
5572
|
options = this.setOptions;
|
|
5285
5573
|
parse = this.parseMarkdown(true);
|
|
5286
5574
|
parseInline = this.parseMarkdown(false);
|
|
@@ -5326,8 +5614,8 @@ var Z = class {
|
|
|
5326
5614
|
if ("renderer" in r) {
|
|
5327
5615
|
let i = t.renderers[r.name];
|
|
5328
5616
|
i ? t.renderers[r.name] = function(...o) {
|
|
5329
|
-
let
|
|
5330
|
-
return
|
|
5617
|
+
let u = r.renderer.apply(this, o);
|
|
5618
|
+
return u === false && (u = i.apply(this, o)), u;
|
|
5331
5619
|
} : t.renderers[r.name] = r.renderer;
|
|
5332
5620
|
}
|
|
5333
5621
|
if ("tokenizer" in r) {
|
|
@@ -5341,10 +5629,10 @@ var Z = class {
|
|
|
5341
5629
|
for (let i in n.renderer) {
|
|
5342
5630
|
if (!(i in r)) throw new Error(`renderer '${i}' does not exist`);
|
|
5343
5631
|
if (["options", "parser"].includes(i)) continue;
|
|
5344
|
-
let o = i,
|
|
5345
|
-
r[o] = (...
|
|
5346
|
-
let c =
|
|
5347
|
-
return c === false && (c = a.apply(r,
|
|
5632
|
+
let o = i, u = n.renderer[o], a = r[o];
|
|
5633
|
+
r[o] = (...p) => {
|
|
5634
|
+
let c = u.apply(r, p);
|
|
5635
|
+
return c === false && (c = a.apply(r, p)), c || "";
|
|
5348
5636
|
};
|
|
5349
5637
|
}
|
|
5350
5638
|
s.renderer = r;
|
|
@@ -5354,10 +5642,10 @@ var Z = class {
|
|
|
5354
5642
|
for (let i in n.tokenizer) {
|
|
5355
5643
|
if (!(i in r)) throw new Error(`tokenizer '${i}' does not exist`);
|
|
5356
5644
|
if (["options", "rules", "lexer"].includes(i)) continue;
|
|
5357
|
-
let o = i,
|
|
5358
|
-
r[o] = (...
|
|
5359
|
-
let c =
|
|
5360
|
-
return c === false && (c = a.apply(r,
|
|
5645
|
+
let o = i, u = n.tokenizer[o], a = r[o];
|
|
5646
|
+
r[o] = (...p) => {
|
|
5647
|
+
let c = u.apply(r, p);
|
|
5648
|
+
return c === false && (c = a.apply(r, p)), c;
|
|
5361
5649
|
};
|
|
5362
5650
|
}
|
|
5363
5651
|
s.tokenizer = r;
|
|
@@ -5367,21 +5655,21 @@ var Z = class {
|
|
|
5367
5655
|
for (let i in n.hooks) {
|
|
5368
5656
|
if (!(i in r)) throw new Error(`hook '${i}' does not exist`);
|
|
5369
5657
|
if (["options", "block"].includes(i)) continue;
|
|
5370
|
-
let o = i,
|
|
5371
|
-
S.passThroughHooks.has(i) ? r[o] = (
|
|
5658
|
+
let o = i, u = n.hooks[o], a = r[o];
|
|
5659
|
+
S.passThroughHooks.has(i) ? r[o] = (p) => {
|
|
5372
5660
|
if (this.defaults.async && S.passThroughHooksRespectAsync.has(i)) return (async () => {
|
|
5373
|
-
let h = await
|
|
5661
|
+
let h = await u.call(r, p);
|
|
5374
5662
|
return a.call(r, h);
|
|
5375
5663
|
})();
|
|
5376
|
-
let c =
|
|
5664
|
+
let c = u.call(r, p);
|
|
5377
5665
|
return a.call(r, c);
|
|
5378
|
-
} : r[o] = (...
|
|
5666
|
+
} : r[o] = (...p) => {
|
|
5379
5667
|
if (this.defaults.async) return (async () => {
|
|
5380
|
-
let h = await
|
|
5381
|
-
return h === false && (h = await a.apply(r,
|
|
5668
|
+
let h = await u.apply(r, p);
|
|
5669
|
+
return h === false && (h = await a.apply(r, p)), h;
|
|
5382
5670
|
})();
|
|
5383
|
-
let c =
|
|
5384
|
-
return c === false && (c = a.apply(r,
|
|
5671
|
+
let c = u.apply(r, p);
|
|
5672
|
+
return c === false && (c = a.apply(r, p)), c;
|
|
5385
5673
|
};
|
|
5386
5674
|
}
|
|
5387
5675
|
s.hooks = r;
|
|
@@ -5389,8 +5677,8 @@ var Z = class {
|
|
|
5389
5677
|
if (n.walkTokens) {
|
|
5390
5678
|
let r = this.defaults.walkTokens, i = n.walkTokens;
|
|
5391
5679
|
s.walkTokens = function(o) {
|
|
5392
|
-
let
|
|
5393
|
-
return
|
|
5680
|
+
let u = [];
|
|
5681
|
+
return u.push(i.call(this, o)), r && (u = u.concat(r.call(this, o))), u;
|
|
5394
5682
|
};
|
|
5395
5683
|
}
|
|
5396
5684
|
this.defaults = { ...this.defaults, ...s };
|
|
@@ -5412,10 +5700,10 @@ var Z = class {
|
|
|
5412
5700
|
if (typeof n > "u" || n === null) return o(new Error("marked(): input parameter is undefined or null"));
|
|
5413
5701
|
if (typeof n != "string") return o(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected"));
|
|
5414
5702
|
if (i.hooks && (i.hooks.options = i, i.hooks.block = e), i.async) return (async () => {
|
|
5415
|
-
let
|
|
5703
|
+
let u = i.hooks ? await i.hooks.preprocess(n) : n, p = await (i.hooks ? await i.hooks.provideLexer(e) : e ? x.lex : x.lexInline)(u, i), c = i.hooks ? await i.hooks.processAllTokens(p) : p;
|
|
5416
5704
|
i.walkTokens && await Promise.all(this.walkTokens(c, i.walkTokens));
|
|
5417
|
-
let
|
|
5418
|
-
return i.hooks ? await i.hooks.postprocess(
|
|
5705
|
+
let d = await (i.hooks ? await i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(c, i);
|
|
5706
|
+
return i.hooks ? await i.hooks.postprocess(d) : d;
|
|
5419
5707
|
})().catch(o);
|
|
5420
5708
|
try {
|
|
5421
5709
|
i.hooks && (n = i.hooks.preprocess(n));
|
|
@@ -5423,8 +5711,8 @@ var Z = class {
|
|
|
5423
5711
|
i.hooks && (a = i.hooks.processAllTokens(a)), i.walkTokens && this.walkTokens(a, i.walkTokens);
|
|
5424
5712
|
let c = (i.hooks ? i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(a, i);
|
|
5425
5713
|
return i.hooks && (c = i.hooks.postprocess(c)), c;
|
|
5426
|
-
} catch (
|
|
5427
|
-
return o(
|
|
5714
|
+
} catch (u) {
|
|
5715
|
+
return o(u);
|
|
5428
5716
|
}
|
|
5429
5717
|
};
|
|
5430
5718
|
}
|
|
@@ -5432,7 +5720,7 @@ var Z = class {
|
|
|
5432
5720
|
return (n) => {
|
|
5433
5721
|
if (n.message += `
|
|
5434
5722
|
Please report this to https://github.com/markedjs/marked.`, e) {
|
|
5435
|
-
let s = "<p>An error occurred:</p><pre>" +
|
|
5723
|
+
let s = "<p>An error occurred:</p><pre>" + T(n.message + "", true) + "</pre>";
|
|
5436
5724
|
return t ? Promise.resolve(s) : s;
|
|
5437
5725
|
}
|
|
5438
5726
|
if (t) return Promise.reject(n);
|
|
@@ -5440,23 +5728,23 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
5440
5728
|
};
|
|
5441
5729
|
}
|
|
5442
5730
|
};
|
|
5443
|
-
var
|
|
5731
|
+
var M = new Z();
|
|
5444
5732
|
function f(l3, e) {
|
|
5445
|
-
return
|
|
5733
|
+
return M.parse(l3, e);
|
|
5446
5734
|
}
|
|
5447
5735
|
f.options = f.setOptions = function(l3) {
|
|
5448
|
-
return
|
|
5736
|
+
return M.setOptions(l3), f.defaults = M.defaults, j(f.defaults), f;
|
|
5449
5737
|
};
|
|
5450
|
-
f.getDefaults =
|
|
5738
|
+
f.getDefaults = A;
|
|
5451
5739
|
f.defaults = R;
|
|
5452
|
-
function
|
|
5453
|
-
return
|
|
5740
|
+
function dt(...l3) {
|
|
5741
|
+
return M.use(...l3), f.defaults = M.defaults, j(f.defaults), f;
|
|
5454
5742
|
}
|
|
5455
|
-
f.use =
|
|
5743
|
+
f.use = dt;
|
|
5456
5744
|
f.walkTokens = function(l3, e) {
|
|
5457
|
-
return
|
|
5745
|
+
return M.walkTokens(l3, e);
|
|
5458
5746
|
};
|
|
5459
|
-
f.parseInline =
|
|
5747
|
+
f.parseInline = M.parseInline;
|
|
5460
5748
|
f.Parser = b;
|
|
5461
5749
|
f.parser = b.parse;
|
|
5462
5750
|
f.Renderer = P;
|
|
@@ -5471,7 +5759,7 @@ var rn = f.setOptions;
|
|
|
5471
5759
|
var sn = f.walkTokens;
|
|
5472
5760
|
var on = f.parseInline;
|
|
5473
5761
|
var ln = b.parse;
|
|
5474
|
-
var
|
|
5762
|
+
var un = x.lex;
|
|
5475
5763
|
|
|
5476
5764
|
// src/ui/render_markdown.ts
|
|
5477
5765
|
var parser = new Z({ gfm: true, breaks: true });
|
|
@@ -5682,7 +5970,7 @@ function wrapWords(root) {
|
|
|
5682
5970
|
}
|
|
5683
5971
|
|
|
5684
5972
|
// src/ui/run_notice.ts
|
|
5685
|
-
function renderRunNotice(icon, text4, kind) {
|
|
5973
|
+
function renderRunNotice(icon, text4, kind, undo) {
|
|
5686
5974
|
const notice = document.createElement("div");
|
|
5687
5975
|
notice.className = `run-notice run-notice--${kind}`;
|
|
5688
5976
|
notice.setAttribute("part", `run-notice run-notice-${kind}`);
|
|
@@ -5697,6 +5985,18 @@ function renderRunNotice(icon, text4, kind) {
|
|
|
5697
5985
|
label.setAttribute("part", "run-notice-text");
|
|
5698
5986
|
label.textContent = text4;
|
|
5699
5987
|
notice.append(glyph, label);
|
|
5988
|
+
if (undo !== void 0) {
|
|
5989
|
+
const button2 = document.createElement("button");
|
|
5990
|
+
button2.type = "button";
|
|
5991
|
+
button2.className = "run-notice-undo";
|
|
5992
|
+
button2.setAttribute("part", "run-notice-undo");
|
|
5993
|
+
button2.textContent = undo.label;
|
|
5994
|
+
button2.addEventListener("click", () => {
|
|
5995
|
+
button2.disabled = true;
|
|
5996
|
+
undo.onActivate();
|
|
5997
|
+
});
|
|
5998
|
+
notice.append(button2);
|
|
5999
|
+
}
|
|
5700
6000
|
return notice;
|
|
5701
6001
|
}
|
|
5702
6002
|
|
|
@@ -5954,6 +6254,11 @@ var STYLES = `
|
|
|
5954
6254
|
/* Message action row: the control box and the mark inside it. The box has a
|
|
5955
6255
|
floor of 24px so it stays a reliable target at every density. */
|
|
5956
6256
|
--_action-size: var(--ag-ui-action-size, 28px);
|
|
6257
|
+
/* The header's own controls. Sized rather than left to their contents: a row
|
|
6258
|
+
of buttons each as wide as the glyph inside it comes out uneven, and uneven
|
|
6259
|
+
buttons two pixels apart read as one smudge rather than five controls. */
|
|
6260
|
+
--_header-btn-size: var(--ag-ui-header-btn-size, 30px);
|
|
6261
|
+
--_header-gap: var(--ag-ui-header-gap, 4px);
|
|
5957
6262
|
--_action-icon-size: var(--ag-ui-action-icon-size, 15px);
|
|
5958
6263
|
--_tooltip-bg: var(--ag-ui-tooltip-bg, #1f2430);
|
|
5959
6264
|
--_tooltip-fg: var(--ag-ui-tooltip-fg, #f5f6fa);
|
|
@@ -6048,6 +6353,68 @@ var STYLES = `
|
|
|
6048
6353
|
--_msg-pad: var(--ag-ui-msg-pad, 8px 12px);
|
|
6049
6354
|
--_msg-radius: var(--ag-ui-msg-radius, 14px);
|
|
6050
6355
|
|
|
6356
|
+
/* Edges of the viewport the host has already spent, and that a fixed
|
|
6357
|
+
placement must therefore stay out of: a sticky nav bar, a docked toolbar,
|
|
6358
|
+
a device's safe area. Four longhands rather than one shorthand because a
|
|
6359
|
+
custom property is a token stream and CSS cannot index one -- the height
|
|
6360
|
+
arithmetic below needs the vertical pair on its own.
|
|
6361
|
+
|
|
6362
|
+
Every fixed placement derives from these, and the heights subtract them in
|
|
6363
|
+
one place. A host that reserved its chrome by restating --ag-ui-inset per
|
|
6364
|
+
placement had to keep --ag-ui-height in step by hand, and forgetting it
|
|
6365
|
+
overflowed the panel past the bottom of the screen with nothing to say so.
|
|
6366
|
+
|
|
6367
|
+
These take env(safe-area-inset-*) verbatim, which is what a full-bleed
|
|
6368
|
+
placement wants on a device with a notch. */
|
|
6369
|
+
--_viewport-inset-top: var(--ag-ui-viewport-inset-top, 0px);
|
|
6370
|
+
--_viewport-inset-right: var(--ag-ui-viewport-inset-right, 0px);
|
|
6371
|
+
--_viewport-inset-bottom: var(--ag-ui-viewport-inset-bottom, 0px);
|
|
6372
|
+
--_viewport-inset-left: var(--ag-ui-viewport-inset-left, 0px);
|
|
6373
|
+
/* What is left of the viewport once the host's chrome is taken out, on both
|
|
6374
|
+
axes, so a placement never composes this itself and no host can subtract
|
|
6375
|
+
an edge from the position and forget it in the height.
|
|
6376
|
+
|
|
6377
|
+
Settable in their own right, and that is not only convention: an on-screen
|
|
6378
|
+
keyboard changes no viewport-percentage length -- not vh, not dvh, not svh
|
|
6379
|
+
-- so a full-bleed panel on a phone has to be told the height rather than
|
|
6380
|
+
deriving it. The value to publish there is the visual viewport's. */
|
|
6381
|
+
--_viewport-height: var(--ag-ui-viewport-height, var(--_visual-viewport-height));
|
|
6382
|
+
/* The measured height of the part of the screen the user can actually see,
|
|
6383
|
+
written by the element from the visual viewport and falling back to the
|
|
6384
|
+
layout viewport where nothing has measured yet.
|
|
6385
|
+
|
|
6386
|
+
This is the on-screen keyboard, and it needs measuring because no CSS
|
|
6387
|
+
length describes it: an on-screen keyboard has no effect on any
|
|
6388
|
+
viewport-percentage unit, so 100vh, 100dvh and 100svh are all the same
|
|
6389
|
+
number with the keyboard up as without it. A full-bleed panel sized from
|
|
6390
|
+
any of them puts its composer behind the keyboard the user is typing into.
|
|
6391
|
+
|
|
6392
|
+
Separate from the token above so a host that states the usable height
|
|
6393
|
+
outright still wins: the element writes this one inline, and an inline
|
|
6394
|
+
value would otherwise outrank the host's own rule. */
|
|
6395
|
+
--_visual-viewport-height: var(
|
|
6396
|
+
--ag-ui-visual-viewport-height,
|
|
6397
|
+
calc(100vh - var(--_viewport-inset-top) - var(--_viewport-inset-bottom))
|
|
6398
|
+
);
|
|
6399
|
+
/* How much of the layout viewport is hidden below the visible one, measured
|
|
6400
|
+
and written by the element alongside the height above.
|
|
6401
|
+
|
|
6402
|
+
A shorter panel is not enough on its own for anything anchored to the
|
|
6403
|
+
bottom. A floating widget is positioned against the layout viewport, so
|
|
6404
|
+
with a keyboard up its bottom edge -- and the launcher that lives at that
|
|
6405
|
+
corner -- sits behind the keyboard however tall the panel is. This is what
|
|
6406
|
+
lifts it clear. */
|
|
6407
|
+
/* Two tokens, for the same reason the height above has two: the element
|
|
6408
|
+
writes the measurement inline, and a host that states its own value needs
|
|
6409
|
+
a knob that outranks that write rather than one the next write replaces.
|
|
6410
|
+
A host wanting no keyboard lift at all sets --ag-ui-keyboard-inset: 0px. */
|
|
6411
|
+
--_keyboard-inset: var(--ag-ui-keyboard-inset, var(--_visual-viewport-inset-bottom));
|
|
6412
|
+
--_visual-viewport-inset-bottom: var(--ag-ui-visual-viewport-inset-bottom, 0px);
|
|
6413
|
+
--_viewport-width: var(
|
|
6414
|
+
--ag-ui-viewport-width,
|
|
6415
|
+
calc(100vw - var(--_viewport-inset-left) - var(--_viewport-inset-right))
|
|
6416
|
+
);
|
|
6417
|
+
|
|
6051
6418
|
/* Layout \u2014 override from outside to dock the widget anywhere.
|
|
6052
6419
|
Set --ag-ui-position: static (and place this element in your own
|
|
6053
6420
|
grid/flex layout) to embed it in the page flow instead of floating. */
|
|
@@ -6055,14 +6422,49 @@ var STYLES = `
|
|
|
6055
6422
|
--_z-index: var(--ag-ui-z-index, 2147483000);
|
|
6056
6423
|
--_width: var(--ag-ui-width, 380px);
|
|
6057
6424
|
--_height: var(--ag-ui-height, 560px);
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6425
|
+
/* The gutter a resting floating panel keeps between itself and the edge of
|
|
6426
|
+
the box the host left free. One number, because the inset below spends it
|
|
6427
|
+
and the cap beneath has to know it was spent. */
|
|
6428
|
+
--_edge-gutter: var(--ag-ui-edge-gutter, 24px);
|
|
6429
|
+
--_inset: var(
|
|
6430
|
+
--ag-ui-inset,
|
|
6431
|
+
auto calc(var(--_edge-gutter) + var(--_viewport-inset-right))
|
|
6432
|
+
calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset)) auto
|
|
6433
|
+
);
|
|
6434
|
+
/* The cap is what the host left free, less the *one* gutter the inset above
|
|
6435
|
+
spends on the anchored edge -- not two, and not none.
|
|
6436
|
+
|
|
6437
|
+
Two was the first answer and it was felt on one axis only. The arithmetic
|
|
6438
|
+
says why: the default panel is 560 tall against a cap of the viewport
|
|
6439
|
+
minus 48, which on an 800px screen with a header reserved is 72px of
|
|
6440
|
+
headroom, so the height reaches its cap almost immediately; the default
|
|
6441
|
+
width is 380 against a cap near 1230, which is 850px nothing ever reaches.
|
|
6442
|
+
Once the size is capped a grip cannot grow the panel, so a pull on the
|
|
6443
|
+
anchored edge is written as position instead and the panel travels, and a
|
|
6444
|
+
pull on the free edge stops a whole gutter short of an edge a drag can
|
|
6445
|
+
reach.
|
|
6446
|
+
|
|
6447
|
+
None was the correction, and it overshot: the panel is anchored bottom-
|
|
6448
|
+
right with a gutter already spent there, so a cap of the full usable
|
|
6449
|
+
height puts the far edge exactly one gutter outside it. On an 800px screen
|
|
6450
|
+
with nothing reserved that is a top of -24 -- the header, and every
|
|
6451
|
+
control in it, off the top of the window. Reserve a 120px header and it
|
|
6452
|
+
sits inside that instead, which is the one thing the reservation exists to
|
|
6453
|
+
prevent.
|
|
6454
|
+
|
|
6455
|
+
One is the fixed point of both complaints. A resting panel grown to the
|
|
6456
|
+
cap runs from the usable near edge to its gutter on the far one, so the
|
|
6457
|
+
near edge is reachable by a resize exactly as it is by a drag, and neither
|
|
6458
|
+
can put any part of the panel outside the box. */
|
|
6459
|
+
--_max-width: var(--ag-ui-max-width, calc(var(--_viewport-width) - var(--_edge-gutter)));
|
|
6460
|
+
--_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_edge-gutter)));
|
|
6061
6461
|
/* Reading-column width for placement="page" (full-bleed, centred content). */
|
|
6062
6462
|
--_content-max-width: var(--ag-ui-content-max-width, 820px);
|
|
6063
6463
|
/* Slim rail the sidebar placement collapses to. Only that placement reads
|
|
6064
6464
|
it, but it is declared here so every alias has a default in one place. */
|
|
6065
6465
|
--_rail-width: var(--ag-ui-rail-width, 52px);
|
|
6466
|
+
/* Width of the docked conversation list on a full-page chat. */
|
|
6467
|
+
--_threads-rail-width: var(--ag-ui-threads-rail-width, 280px);
|
|
6066
6468
|
|
|
6067
6469
|
position: var(--_position);
|
|
6068
6470
|
inset: var(--_inset);
|
|
@@ -6133,23 +6535,30 @@ var STYLES = `
|
|
|
6133
6535
|
|
|
6134
6536
|
/* \u2500\u2500 Placement presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
6135
6537
|
:host([placement="bottom-left"]) {
|
|
6136
|
-
--_inset: var(
|
|
6538
|
+
--_inset: var(
|
|
6539
|
+
--ag-ui-inset,
|
|
6540
|
+
auto auto calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset))
|
|
6541
|
+
calc(var(--_edge-gutter) + var(--_viewport-inset-left))
|
|
6542
|
+
);
|
|
6137
6543
|
}
|
|
6138
6544
|
|
|
6139
6545
|
:host([placement="side"]) {
|
|
6140
|
-
--_inset: var(--ag-ui-inset,
|
|
6546
|
+
--_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
6141
6547
|
--_width: var(--ag-ui-width, 420px);
|
|
6142
|
-
--_height: var(--ag-ui-height,
|
|
6143
|
-
--_max-height: var(--ag-ui-max-height,
|
|
6548
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6549
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6144
6550
|
--_radius: var(--ag-ui-radius, 0);
|
|
6145
6551
|
}
|
|
6146
6552
|
|
|
6147
6553
|
:host([placement="full"]) {
|
|
6148
|
-
--_inset: var(
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
--
|
|
6554
|
+
--_inset: var(
|
|
6555
|
+
--ag-ui-inset,
|
|
6556
|
+
var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)
|
|
6557
|
+
);
|
|
6558
|
+
--_width: var(--ag-ui-width, var(--_viewport-width));
|
|
6559
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6560
|
+
--_max-width: var(--ag-ui-max-width, var(--_viewport-width));
|
|
6561
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6153
6562
|
--_radius: var(--ag-ui-radius, 0);
|
|
6154
6563
|
}
|
|
6155
6564
|
|
|
@@ -6159,11 +6568,14 @@ var STYLES = `
|
|
|
6159
6568
|
composer rather than a per-row wrapper, so user pills still right-align and
|
|
6160
6569
|
the assistant well spans the column. */
|
|
6161
6570
|
:host([placement="page"]) {
|
|
6162
|
-
--_inset: var(
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
--
|
|
6571
|
+
--_inset: var(
|
|
6572
|
+
--ag-ui-inset,
|
|
6573
|
+
var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)
|
|
6574
|
+
);
|
|
6575
|
+
--_width: var(--ag-ui-width, var(--_viewport-width));
|
|
6576
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6577
|
+
--_max-width: var(--ag-ui-max-width, var(--_viewport-width));
|
|
6578
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6167
6579
|
--_radius: var(--ag-ui-radius, 0);
|
|
6168
6580
|
}
|
|
6169
6581
|
|
|
@@ -6194,28 +6606,100 @@ var STYLES = `
|
|
|
6194
6606
|
max-width: 100%;
|
|
6195
6607
|
}
|
|
6196
6608
|
|
|
6609
|
+
/* Small viewports: one shape, reached from whichever placement the host chose.
|
|
6610
|
+
|
|
6611
|
+
A phone is not an eighth placement, it is an override that collapses the
|
|
6612
|
+
others onto one of them. The host picked a placement for the desktop it was
|
|
6613
|
+
designing; a 380x560 panel with a 24px margin is not a smaller version of
|
|
6614
|
+
that decision, it is most of the screen with a frame drawn round it.
|
|
6615
|
+
|
|
6616
|
+
The breakpoint is a literal because a custom property cannot be read in a
|
|
6617
|
+
media query -- which is exactly why there is an opt-out. Everything the block
|
|
6618
|
+
sets is a token a host can re-state, but the *trigger* is unreachable, so a
|
|
6619
|
+
host whose layout wants a different threshold, or none, sets
|
|
6620
|
+
data-small-viewport="off" and keeps its desktop shape at every width. 600px is above every common phone in portrait and below every
|
|
6621
|
+
tablet in landscape, and it is a width rather than a pointer test on purpose:
|
|
6622
|
+
a touch laptop is coarse-pointered and wide, a narrow desktop window is
|
|
6623
|
+
fine-pointered and small, and conflating the two gets both wrong. What the
|
|
6624
|
+
pointer decides is which controls make sense, further down.
|
|
6625
|
+
|
|
6626
|
+
Two placements are left alone. "page" is already this shape. "embedded" sits
|
|
6627
|
+
in a box the host sized and placed, and taking that over would break the app
|
|
6628
|
+
shell it was embedded into -- the host is the only party that knows whether
|
|
6629
|
+
its column should become the whole screen. */
|
|
6630
|
+
@media (max-width: 600px) {
|
|
6631
|
+
:host([placement="floating"]:not([data-small-viewport="off"])),
|
|
6632
|
+
:host([placement="bottom-left"]:not([data-small-viewport="off"])),
|
|
6633
|
+
:host([placement="sidebar"]:not([data-small-viewport="off"])),
|
|
6634
|
+
:host([placement="side"]:not([data-small-viewport="off"])),
|
|
6635
|
+
:host(:not([placement]):not([data-small-viewport="off"])),
|
|
6636
|
+
:host([placement=""]:not([data-small-viewport="off"])) {
|
|
6637
|
+
--_inset: var(
|
|
6638
|
+
--ag-ui-inset,
|
|
6639
|
+
var(--_viewport-inset-top) var(--_viewport-inset-right)
|
|
6640
|
+
calc(var(--_viewport-inset-bottom) + var(--_keyboard-inset))
|
|
6641
|
+
var(--_viewport-inset-left)
|
|
6642
|
+
);
|
|
6643
|
+
--_width: var(--ag-ui-width, var(--_viewport-width));
|
|
6644
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6645
|
+
--_max-width: var(--ag-ui-max-width, var(--_viewport-width));
|
|
6646
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6647
|
+
--_radius: var(--ag-ui-radius, 0);
|
|
6648
|
+
--_shadow: var(--ag-ui-shadow, none);
|
|
6649
|
+
}
|
|
6650
|
+
|
|
6651
|
+
/* Nothing to resize once the panel is the screen, and the grips would sit
|
|
6652
|
+
under the thumbs holding the phone. */
|
|
6653
|
+
:host([placement="floating"]:not([data-small-viewport="off"])) .resize-handle,
|
|
6654
|
+
:host([placement="bottom-left"]:not([data-small-viewport="off"])) .resize-handle,
|
|
6655
|
+
:host([placement="sidebar"]:not([data-small-viewport="off"])) .resize-handle,
|
|
6656
|
+
:host([placement="side"]:not([data-small-viewport="off"])) .resize-handle,
|
|
6657
|
+
:host(:not([placement]):not([data-small-viewport="off"])) .resize-handle,
|
|
6658
|
+
:host([placement=""]:not([data-small-viewport="off"])) .resize-handle {
|
|
6659
|
+
display: none;
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
|
|
6197
6663
|
/* Sidebar: a full-height docked panel that slides open/closed and
|
|
6198
6664
|
collapses to a slim icon rail (not the floating launcher). Docked right by
|
|
6199
6665
|
default; data-side="left" docks it left. Overlay by default \u2014 set
|
|
6200
6666
|
--ag-ui-position: static (and place this element in your own layout) for a
|
|
6201
6667
|
host-managed push instead. */
|
|
6202
6668
|
:host([placement="sidebar"]) {
|
|
6203
|
-
--_inset: var(--ag-ui-inset,
|
|
6669
|
+
--_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
6204
6670
|
--_width: var(--ag-ui-width, 420px);
|
|
6205
|
-
--_height: var(--ag-ui-height,
|
|
6206
|
-
--_max-height: var(--ag-ui-max-height,
|
|
6671
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
6672
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
6207
6673
|
--_radius: var(--ag-ui-radius, 0);
|
|
6208
6674
|
transition: width var(--_motion) var(--_ease);
|
|
6209
6675
|
}
|
|
6210
6676
|
|
|
6211
6677
|
:host([placement="sidebar"][data-side="left"]) {
|
|
6212
|
-
--_inset: var(--ag-ui-inset,
|
|
6678
|
+
--_inset: var(--ag-ui-inset, var(--_viewport-inset-top) auto var(--_viewport-inset-bottom) var(--_viewport-inset-left));
|
|
6213
6679
|
}
|
|
6214
6680
|
|
|
6215
6681
|
/* The docked panel is pinned to the edge it docks against rather than filling
|
|
6216
6682
|
the host as a flex child. Collapsing shrinks the host to the rail width, and
|
|
6217
6683
|
a flex child would be squashed to 52px on the way out instead of sliding out
|
|
6218
6684
|
at full width. */
|
|
6685
|
+
/* The panel is taken out of flow so the collapse can slide it out at full
|
|
6686
|
+
width; see the note above. That makes the host its containing block, and the
|
|
6687
|
+
host is only one by accident: it is position: fixed by default. A host that
|
|
6688
|
+
takes the documented route to a pushed layout instead of an overlay sets
|
|
6689
|
+
--ag-ui-position: static, and a static element establishes nothing -- so the
|
|
6690
|
+
panel resolved against the initial containing block, landed at the document
|
|
6691
|
+
origin, and scrolled away with the page. Docked left it pinned to the
|
|
6692
|
+
document's left edge rather than the host's.
|
|
6693
|
+
|
|
6694
|
+
It looked correct wherever it was first tried, because a full-height column
|
|
6695
|
+
at the top of an unscrolled document is exactly where those two answers
|
|
6696
|
+
coincide. Containment makes the host a containing block whatever its
|
|
6697
|
+
position, so the panel stays in the box the host was given. It is a no-op in
|
|
6698
|
+
the default case, which is already positioned. */
|
|
6699
|
+
:host([placement="sidebar"]) {
|
|
6700
|
+
contain: layout;
|
|
6701
|
+
}
|
|
6702
|
+
|
|
6219
6703
|
:host([placement="sidebar"]) .chat {
|
|
6220
6704
|
position: absolute;
|
|
6221
6705
|
inset: 0 0 0 auto;
|
|
@@ -6231,11 +6715,15 @@ var STYLES = `
|
|
|
6231
6715
|
/* Collapsed sidebar: shrink the host to the rail width and slide the panel out
|
|
6232
6716
|
through the edge it docks against. Higher specificity than the generic
|
|
6233
6717
|
collapse rules, so it wins regardless of source order. */
|
|
6718
|
+
/* The rail is the full height of what the host left, not of the screen. It
|
|
6719
|
+
said 100vh and pinned its own bottom, which put it under any chrome the host
|
|
6720
|
+
had reserved -- and the icon lives at the top of the rail, so the one control
|
|
6721
|
+
that reopens the panel was the first thing to disappear behind a sticky
|
|
6722
|
+
header. */
|
|
6234
6723
|
:host([placement="sidebar"][collapsed]) {
|
|
6235
6724
|
width: var(--_rail-width);
|
|
6236
|
-
height:
|
|
6237
|
-
max-height:
|
|
6238
|
-
bottom: 0;
|
|
6725
|
+
height: var(--_viewport-height);
|
|
6726
|
+
max-height: var(--_viewport-height);
|
|
6239
6727
|
pointer-events: auto;
|
|
6240
6728
|
}
|
|
6241
6729
|
|
|
@@ -6351,17 +6839,83 @@ var STYLES = `
|
|
|
6351
6839
|
|
|
6352
6840
|
/* The sidebar collapses to an edge rail instead: full height, square, flush
|
|
6353
6841
|
against the dock. It slides in with the panel rather than popping. */
|
|
6842
|
+
/* The edge rail. It reads as the docked edge of a panel rather than a coloured
|
|
6843
|
+
stripe: the surface the panel is made of, a border on the side it docks
|
|
6844
|
+
against, and the accent kept for the icon -- a full-height slab of accent is
|
|
6845
|
+
the loudest thing on the page and says the least about itself.
|
|
6846
|
+
|
|
6847
|
+
Content sits at the top rather than centred, because a control floating in
|
|
6848
|
+
the middle of a screen-high column has nothing to belong to. */
|
|
6354
6849
|
:host([placement="sidebar"][collapsed]) .launcher {
|
|
6355
6850
|
inset: 0;
|
|
6356
6851
|
width: auto;
|
|
6357
6852
|
height: auto;
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6853
|
+
flex-direction: column;
|
|
6854
|
+
align-items: center;
|
|
6855
|
+
justify-content: flex-start;
|
|
6856
|
+
gap: 12px;
|
|
6857
|
+
padding-top: 14px;
|
|
6858
|
+
border: 0;
|
|
6859
|
+
border-inline-start: 1px solid var(--_border);
|
|
6361
6860
|
border-radius: 0;
|
|
6861
|
+
background: var(--_bg);
|
|
6862
|
+
color: var(--_fg);
|
|
6863
|
+
box-shadow: none;
|
|
6864
|
+
transform: none;
|
|
6865
|
+
}
|
|
6866
|
+
|
|
6867
|
+
:host([placement="sidebar"][data-side="left"][collapsed]) .launcher {
|
|
6868
|
+
border-inline-start: 0;
|
|
6869
|
+
border-inline-end: 1px solid var(--_border);
|
|
6870
|
+
}
|
|
6871
|
+
|
|
6872
|
+
/* The icon keeps the accent, so there is one obvious thing to press.
|
|
6873
|
+
|
|
6874
|
+
The glyph is sized here rather than left to fill the holder. A glyph in an
|
|
6875
|
+
icon holder takes the holder's whole box by default, which is right where the
|
|
6876
|
+
holder is only a box and wrong the moment it becomes a filled circle: the
|
|
6877
|
+
mark then runs edge to edge and reads as a square crammed into a disc. The
|
|
6878
|
+
proportion is the floating launcher's own -- a 26px glyph in a 56px bubble --
|
|
6879
|
+
so the two collapsed states look like the same widget. */
|
|
6880
|
+
:host([placement="sidebar"][collapsed]) .launcher .icon-holder {
|
|
6881
|
+
display: inline-flex;
|
|
6882
|
+
align-items: center;
|
|
6883
|
+
justify-content: center;
|
|
6884
|
+
width: 34px;
|
|
6885
|
+
height: 34px;
|
|
6886
|
+
border-radius: 50%;
|
|
6362
6887
|
background: var(--_header-bg);
|
|
6363
6888
|
color: var(--_header-fg);
|
|
6364
|
-
|
|
6889
|
+
}
|
|
6890
|
+
|
|
6891
|
+
:host([placement="sidebar"][collapsed]) .launcher .icon-holder .glyph,
|
|
6892
|
+
:host([placement="sidebar"][collapsed]) .launcher .icon-holder .icon-img {
|
|
6893
|
+
width: 16px;
|
|
6894
|
+
height: 16px;
|
|
6895
|
+
}
|
|
6896
|
+
|
|
6897
|
+
/* Written down the rail, which is the only direction it fits. Reading upward
|
|
6898
|
+
is the convention for a right-hand edge and matches how a docked panel's
|
|
6899
|
+
label is set everywhere it appears. */
|
|
6900
|
+
.rail-label {
|
|
6901
|
+
display: none;
|
|
6902
|
+
}
|
|
6903
|
+
|
|
6904
|
+
:host([placement="sidebar"][collapsed]) .rail-label {
|
|
6905
|
+
display: block;
|
|
6906
|
+
writing-mode: vertical-rl;
|
|
6907
|
+
transform: rotate(180deg);
|
|
6908
|
+
max-height: calc(100% - 96px);
|
|
6909
|
+
overflow: hidden;
|
|
6910
|
+
font-size: 0.9em;
|
|
6911
|
+
font-weight: 600;
|
|
6912
|
+
letter-spacing: 0.02em;
|
|
6913
|
+
white-space: nowrap;
|
|
6914
|
+
text-overflow: ellipsis;
|
|
6915
|
+
}
|
|
6916
|
+
|
|
6917
|
+
:host([placement="sidebar"][data-side="left"][collapsed]) .rail-label {
|
|
6918
|
+
writing-mode: vertical-rl;
|
|
6365
6919
|
transform: none;
|
|
6366
6920
|
}
|
|
6367
6921
|
|
|
@@ -6475,17 +7029,24 @@ var STYLES = `
|
|
|
6475
7029
|
|
|
6476
7030
|
.header-controls {
|
|
6477
7031
|
display: flex;
|
|
6478
|
-
gap:
|
|
7032
|
+
gap: var(--_header-gap);
|
|
6479
7033
|
flex: none;
|
|
6480
7034
|
}
|
|
6481
7035
|
|
|
6482
7036
|
.header-btn {
|
|
7037
|
+
display: inline-flex;
|
|
7038
|
+
flex: 0 0 auto;
|
|
7039
|
+
align-items: center;
|
|
7040
|
+
justify-content: center;
|
|
7041
|
+
box-sizing: border-box;
|
|
7042
|
+
width: var(--_header-btn-size);
|
|
7043
|
+
height: var(--_header-btn-size);
|
|
6483
7044
|
border: none;
|
|
6484
7045
|
background: transparent;
|
|
6485
7046
|
color: inherit;
|
|
6486
7047
|
font: inherit;
|
|
6487
7048
|
line-height: 1;
|
|
6488
|
-
padding:
|
|
7049
|
+
padding: 0;
|
|
6489
7050
|
border-radius: 6px;
|
|
6490
7051
|
cursor: pointer;
|
|
6491
7052
|
opacity: 0.85;
|
|
@@ -6507,9 +7068,9 @@ var STYLES = `
|
|
|
6507
7068
|
Nothing paints there once the panel is gone, so the box only has to stop
|
|
6508
7069
|
swallowing clicks: pointer events go to none and the launcher takes them.
|
|
6509
7070
|
|
|
6510
|
-
Two placements
|
|
6511
|
-
|
|
6512
|
-
|
|
7071
|
+
Two placements do not use it: "sidebar" slides to its rail (below), and
|
|
7072
|
+
"embedded" keeps its header bar, having no corner for a floating circle that
|
|
7073
|
+
would escape the host's layout. "page" has no collapsed state at all. */
|
|
6513
7074
|
:host([collapsed]) {
|
|
6514
7075
|
pointer-events: none;
|
|
6515
7076
|
/* A collapsed host has to be allowed to shrink, and in the layout hosts
|
|
@@ -6571,37 +7132,107 @@ var STYLES = `
|
|
|
6571
7132
|
transform-origin: bottom right;
|
|
6572
7133
|
}
|
|
6573
7134
|
|
|
6574
|
-
/* The
|
|
6575
|
-
|
|
6576
|
-
:host([collapsed]
|
|
7135
|
+
/* The in-flow placement keeps the original collapse: hide the body, let the
|
|
7136
|
+
host shrink to the header bar. */
|
|
7137
|
+
:host([collapsed][placement="embedded"]) {
|
|
6577
7138
|
height: auto;
|
|
6578
7139
|
max-height: none;
|
|
6579
7140
|
pointer-events: auto;
|
|
6580
7141
|
}
|
|
6581
7142
|
|
|
6582
|
-
:host([collapsed]
|
|
7143
|
+
:host([collapsed][placement="embedded"]) .chat {
|
|
6583
7144
|
opacity: 1;
|
|
6584
7145
|
transform: none;
|
|
6585
7146
|
visibility: visible;
|
|
6586
7147
|
}
|
|
6587
7148
|
|
|
6588
|
-
/*
|
|
7149
|
+
/* It keeps the header bar, so the launcher must stay out of the way: an
|
|
6589
7150
|
embedded host is position: static, which would let an absolutely-positioned
|
|
6590
7151
|
circle escape the layout and land against whatever the page positions. */
|
|
6591
|
-
:host([collapsed]
|
|
7152
|
+
:host([collapsed][placement="embedded"]) .launcher {
|
|
6592
7153
|
visibility: hidden;
|
|
6593
7154
|
opacity: 0;
|
|
6594
7155
|
}
|
|
6595
7156
|
|
|
6596
|
-
:host([collapsed]
|
|
6597
|
-
:host([collapsed]
|
|
6598
|
-
:host([collapsed]
|
|
6599
|
-
:host([collapsed]
|
|
6600
|
-
:host([collapsed]
|
|
6601
|
-
:host([collapsed]
|
|
7157
|
+
:host([collapsed][placement="embedded"]) .messages-wrap,
|
|
7158
|
+
:host([collapsed][placement="embedded"]) .messages,
|
|
7159
|
+
:host([collapsed][placement="embedded"]) .input-row,
|
|
7160
|
+
:host([collapsed][placement="embedded"]) .skill-chips,
|
|
7161
|
+
:host([collapsed][placement="embedded"]) .skill-palette,
|
|
7162
|
+
:host([collapsed][placement="embedded"]) .skill-hint {
|
|
7163
|
+
display: none;
|
|
7164
|
+
}
|
|
7165
|
+
|
|
7166
|
+
/* A docked conversation list: beside the transcript rather than over it.
|
|
7167
|
+
|
|
7168
|
+
A full-page chat is the one surface with width to spare, and covering the
|
|
7169
|
+
conversation to show the list of conversations is the wrong trade there --
|
|
7170
|
+
it hides the thing you are trying to get back to. Everywhere else the panel
|
|
7171
|
+
is a few hundred pixels wide and a docked list would leave a column of
|
|
7172
|
+
transcript narrower than the messages in it, so this is the only placement
|
|
7173
|
+
that gets it.
|
|
7174
|
+
|
|
7175
|
+
The transcript is moved by padding on the shell rather than by making the
|
|
7176
|
+
list a flex sibling: the drawer is the last child of the panel, and no
|
|
7177
|
+
selector reaches backwards from it to the rows that have to shift. That is
|
|
7178
|
+
also why the state is stamped on the host. */
|
|
7179
|
+
:host([data-threads-docked]) .drawer {
|
|
7180
|
+
/* Not a scrim: the page behind it is still the user's to work in. */
|
|
7181
|
+
pointer-events: none;
|
|
7182
|
+
}
|
|
7183
|
+
|
|
7184
|
+
:host([data-threads-docked]) .drawer-backdrop {
|
|
7185
|
+
display: none;
|
|
7186
|
+
}
|
|
7187
|
+
|
|
7188
|
+
:host([data-threads-docked]) .drawer-panel {
|
|
7189
|
+
width: var(--_threads-rail-width);
|
|
7190
|
+
pointer-events: auto;
|
|
7191
|
+
border-inline-end: 1px solid var(--_border);
|
|
7192
|
+
box-shadow: none;
|
|
7193
|
+
}
|
|
7194
|
+
|
|
7195
|
+
:host([data-threads-docked]) .messages,
|
|
7196
|
+
:host([data-threads-docked]) .input-row,
|
|
7197
|
+
:host([data-threads-docked]) .skill-chips,
|
|
7198
|
+
:host([data-threads-docked]) .attachment-tray {
|
|
7199
|
+
padding-inline-start: calc(
|
|
7200
|
+
var(--_threads-rail-width) + max(var(--_pad), (100% - var(--_content-max-width)) / 2)
|
|
7201
|
+
);
|
|
7202
|
+
}
|
|
7203
|
+
|
|
7204
|
+
:host([data-threads-docked]) .header {
|
|
7205
|
+
padding-inline-start: calc(var(--_threads-rail-width) + var(--_pad));
|
|
7206
|
+
}
|
|
7207
|
+
|
|
7208
|
+
/* The page placement has no collapsed state, so it offers no control for one.
|
|
7209
|
+
A dedicated route has no "away" to go to: shrinking it left a strip of
|
|
7210
|
+
application chrome fixed over a route that no longer had an owner, and this
|
|
7211
|
+
is the one placement where the launcher that would bring it back is hidden.
|
|
7212
|
+
|
|
7213
|
+
Hiding the control is only half of it. The state has another way in -- the
|
|
7214
|
+
attribute can be written straight onto the element, and a value stored under
|
|
7215
|
+
a different placement is restored on connect -- and the JS guards that catch
|
|
7216
|
+
those cannot see an attribute set directly. So the placement neutralises the
|
|
7217
|
+
state here as well: with the collapse rules above no longer naming "page", an
|
|
7218
|
+
unguarded collapsed attribute would otherwise fall through to the generic
|
|
7219
|
+
rule that scales the panel away and drops pointer events, leaving nothing on
|
|
7220
|
+
screen and no launcher to press. */
|
|
7221
|
+
:host([placement="page"]) .header-btn--collapse {
|
|
6602
7222
|
display: none;
|
|
6603
7223
|
}
|
|
6604
7224
|
|
|
7225
|
+
:host([placement="page"][collapsed]) {
|
|
7226
|
+
pointer-events: auto;
|
|
7227
|
+
align-self: auto;
|
|
7228
|
+
}
|
|
7229
|
+
|
|
7230
|
+
:host([placement="page"][collapsed]) .chat {
|
|
7231
|
+
opacity: 1;
|
|
7232
|
+
transform: none;
|
|
7233
|
+
visibility: visible;
|
|
7234
|
+
}
|
|
7235
|
+
|
|
6605
7236
|
/* Jump-to-latest: shown only once the reader has scrolled away *and* missed
|
|
6606
7237
|
something. Anchored to the panel rather than the list so it does not scroll
|
|
6607
7238
|
with the content it is offering to scroll to. */
|
|
@@ -6689,6 +7320,28 @@ var STYLES = `
|
|
|
6689
7320
|
|
|
6690
7321
|
The 1px box with clip-path, rather than width/height 0, is the shape that
|
|
6691
7322
|
survives: a zero-sized element is dropped from the tree by some engines. */
|
|
7323
|
+
/* A used-value reader for the four viewport-inset tokens, and the only
|
|
7324
|
+
reliable one. getComputedStyle().getPropertyValue() on an unregistered
|
|
7325
|
+
custom property hands back the substituted token stream, not a length: it
|
|
7326
|
+
returns "4rem" verbatim, and "calc(56px + env(safe-area-inset-top))" as
|
|
7327
|
+
"calc(56px + 0px)" -- which parses as NaN and takes the whole inset with it.
|
|
7328
|
+
Padding is a real property, so the same tokens come back resolved to px.
|
|
7329
|
+
|
|
7330
|
+
visibility rather than display: none, because a box that generates no
|
|
7331
|
+
layout has no used values to read. Zero-sized, absolutely positioned and
|
|
7332
|
+
inert, so it costs nothing but the read. */
|
|
7333
|
+
.viewport-probe {
|
|
7334
|
+
position: absolute;
|
|
7335
|
+
top: 0;
|
|
7336
|
+
left: 0;
|
|
7337
|
+
width: 0;
|
|
7338
|
+
height: 0;
|
|
7339
|
+
visibility: hidden;
|
|
7340
|
+
pointer-events: none;
|
|
7341
|
+
padding: var(--_viewport-inset-top) var(--_viewport-inset-right)
|
|
7342
|
+
var(--_viewport-inset-bottom) var(--_viewport-inset-left);
|
|
7343
|
+
}
|
|
7344
|
+
|
|
6692
7345
|
.sr-only {
|
|
6693
7346
|
position: absolute;
|
|
6694
7347
|
width: 1px;
|
|
@@ -6704,6 +7357,8 @@ var STYLES = `
|
|
|
6704
7357
|
.messages {
|
|
6705
7358
|
flex: 1;
|
|
6706
7359
|
overflow-y: auto;
|
|
7360
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
7361
|
+
overscroll-behavior: contain;
|
|
6707
7362
|
/* The browser's own scroll anchoring competes with the scroller for the same
|
|
6708
7363
|
job and wins unpredictably -- it can hold the view still exactly when we
|
|
6709
7364
|
want to follow. Turned off so following is decided in one place. Safari
|
|
@@ -6856,6 +7511,8 @@ var STYLES = `
|
|
|
6856
7511
|
.message--assistant pre {
|
|
6857
7512
|
padding: 8px 10px;
|
|
6858
7513
|
overflow: auto;
|
|
7514
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
7515
|
+
overscroll-behavior: contain;
|
|
6859
7516
|
background: var(--_bg);
|
|
6860
7517
|
border: 1px solid var(--_border);
|
|
6861
7518
|
border-radius: 6px;
|
|
@@ -7034,6 +7691,8 @@ var STYLES = `
|
|
|
7034
7691
|
border-left: 2px solid var(--_border);
|
|
7035
7692
|
max-height: 220px;
|
|
7036
7693
|
overflow: auto;
|
|
7694
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
7695
|
+
overscroll-behavior: contain;
|
|
7037
7696
|
white-space: pre-wrap;
|
|
7038
7697
|
word-break: break-word;
|
|
7039
7698
|
font-family: inherit;
|
|
@@ -7224,6 +7883,8 @@ var STYLES = `
|
|
|
7224
7883
|
padding: 6px 8px;
|
|
7225
7884
|
max-height: 160px;
|
|
7226
7885
|
overflow: auto;
|
|
7886
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
7887
|
+
overscroll-behavior: contain;
|
|
7227
7888
|
background: var(--_bg);
|
|
7228
7889
|
border: 1px solid var(--_border);
|
|
7229
7890
|
border-radius: 6px;
|
|
@@ -7718,6 +8379,8 @@ var STYLES = `
|
|
|
7718
8379
|
resize: none;
|
|
7719
8380
|
max-height: var(--_composer-max-height);
|
|
7720
8381
|
overflow-y: auto;
|
|
8382
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
8383
|
+
overscroll-behavior: contain;
|
|
7721
8384
|
padding: 6px 4px 2px;
|
|
7722
8385
|
background: transparent;
|
|
7723
8386
|
border: none;
|
|
@@ -8002,6 +8665,30 @@ var STYLES = `
|
|
|
8002
8665
|
opacity: 0.75;
|
|
8003
8666
|
}
|
|
8004
8667
|
|
|
8668
|
+
/* The one control a notice may carry. Quiet, because it reports something
|
|
8669
|
+
already done rather than asking for a decision. */
|
|
8670
|
+
.run-notice-undo {
|
|
8671
|
+
flex: 0 0 auto;
|
|
8672
|
+
margin-inline-start: auto;
|
|
8673
|
+
padding: 2px 8px;
|
|
8674
|
+
border: 1px solid var(--_border);
|
|
8675
|
+
border-radius: var(--_radius);
|
|
8676
|
+
background: var(--_bg);
|
|
8677
|
+
color: var(--_accent);
|
|
8678
|
+
font: inherit;
|
|
8679
|
+
font-size: 0.9em;
|
|
8680
|
+
cursor: pointer;
|
|
8681
|
+
}
|
|
8682
|
+
|
|
8683
|
+
.run-notice-undo:hover:not(:disabled) {
|
|
8684
|
+
background: var(--_hover);
|
|
8685
|
+
}
|
|
8686
|
+
|
|
8687
|
+
.run-notice-undo:disabled {
|
|
8688
|
+
opacity: 0.5;
|
|
8689
|
+
cursor: default;
|
|
8690
|
+
}
|
|
8691
|
+
|
|
8005
8692
|
.run-notice-text {
|
|
8006
8693
|
min-width: 0;
|
|
8007
8694
|
overflow-wrap: anywhere;
|
|
@@ -8146,6 +8833,8 @@ var STYLES = `
|
|
|
8146
8833
|
padding: 8px 10px;
|
|
8147
8834
|
max-height: 140px;
|
|
8148
8835
|
overflow: auto;
|
|
8836
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
8837
|
+
overscroll-behavior: contain;
|
|
8149
8838
|
font-size: 12px;
|
|
8150
8839
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
8151
8840
|
background: var(--_assistant-bg);
|
|
@@ -8334,6 +9023,43 @@ var STYLES = `
|
|
|
8334
9023
|
}
|
|
8335
9024
|
}
|
|
8336
9025
|
|
|
9026
|
+
/* Touch. Separate from the width breakpoint above on purpose: width decides
|
|
9027
|
+
the layout, the pointer decides which controls make sense, and a touch
|
|
9028
|
+
laptop is coarse-pointered and wide while a narrow desktop window is
|
|
9029
|
+
fine-pointered and small. */
|
|
9030
|
+
@media (pointer: coarse) {
|
|
9031
|
+
/* A 6px edge strip is not a control, it is a trap that eats a scroll. The
|
|
9032
|
+
corners are 14px, still under the 24px floor a target is meant to clear,
|
|
9033
|
+
and the panel has other ways to be resized on a device that has a pointer
|
|
9034
|
+
precise enough to grab one. */
|
|
9035
|
+
.resize-handle {
|
|
9036
|
+
display: none;
|
|
9037
|
+
}
|
|
9038
|
+
|
|
9039
|
+
/* Platform guidance is 44pt on iOS and 48dp on Android. These sit at 28-30px,
|
|
9040
|
+
which clears the WCAG minimum and misses both. */
|
|
9041
|
+
:host {
|
|
9042
|
+
--_action-size: var(--ag-ui-action-size, 44px);
|
|
9043
|
+
--_tool-btn-size: var(--ag-ui-tool-btn-size, 44px);
|
|
9044
|
+
--_send-size: var(--ag-ui-send-size, 44px);
|
|
9045
|
+
/* Missed the first time these were raised, which left the header -- the
|
|
9046
|
+
widget's primary controls, and the only way to reach history, a new chat
|
|
9047
|
+
or the collapse -- at half the size of everything beside it. */
|
|
9048
|
+
--_header-btn-size: var(--ag-ui-header-btn-size, 44px);
|
|
9049
|
+
--_header-gap: var(--ag-ui-header-gap, 6px);
|
|
9050
|
+
}
|
|
9051
|
+
|
|
9052
|
+
/* iOS Safari zooms the page when a control under 16px takes focus, which
|
|
9053
|
+
drags the whole fixed panel with it and leaves the user pinching back out
|
|
9054
|
+
of a chat they only wanted to type into. The composer inherits the widget
|
|
9055
|
+
font, which is 14px by default and 13px at compact density, so it has to
|
|
9056
|
+
say 16 outright -- and only ever upward, so a host that has deliberately
|
|
9057
|
+
set something larger keeps it. */
|
|
9058
|
+
.input {
|
|
9059
|
+
font-size: max(16px, var(--_font-size));
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
|
|
8337
9063
|
.message-action[aria-pressed="true"] {
|
|
8338
9064
|
opacity: 1;
|
|
8339
9065
|
color: var(--_accent);
|
|
@@ -8522,6 +9248,8 @@ var STYLES = `
|
|
|
8522
9248
|
flex-direction: column;
|
|
8523
9249
|
max-height: 220px;
|
|
8524
9250
|
overflow: auto;
|
|
9251
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
9252
|
+
overscroll-behavior: contain;
|
|
8525
9253
|
background: var(--_bg);
|
|
8526
9254
|
border: 1px solid var(--_border);
|
|
8527
9255
|
border-radius: 8px;
|
|
@@ -8610,6 +9338,8 @@ var STYLES = `
|
|
|
8610
9338
|
box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
|
|
8611
9339
|
max-height: 60%;
|
|
8612
9340
|
overflow-y: auto;
|
|
9341
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
9342
|
+
overscroll-behavior: contain;
|
|
8613
9343
|
transform-origin: top center;
|
|
8614
9344
|
transition:
|
|
8615
9345
|
opacity var(--_motion) var(--_ease),
|
|
@@ -8785,10 +9515,20 @@ var STYLES = `
|
|
|
8785
9515
|
}
|
|
8786
9516
|
|
|
8787
9517
|
.drawer-title {
|
|
9518
|
+
/* Takes the slack so the two controls group at the trailing edge. With
|
|
9519
|
+
space-between and three items the middle one floats, which reads as the
|
|
9520
|
+
title and the close button being a pair with New chat wedged between
|
|
9521
|
+
them. Truncates rather than pushing them off the row. */
|
|
9522
|
+
flex: 1 1 auto;
|
|
9523
|
+
min-width: 0;
|
|
9524
|
+
overflow: hidden;
|
|
9525
|
+
text-overflow: ellipsis;
|
|
9526
|
+
white-space: nowrap;
|
|
8788
9527
|
font-weight: 600;
|
|
8789
9528
|
}
|
|
8790
9529
|
|
|
8791
9530
|
.drawer-new {
|
|
9531
|
+
flex: 0 0 auto;
|
|
8792
9532
|
border: 1px solid var(--_border);
|
|
8793
9533
|
border-radius: var(--_radius);
|
|
8794
9534
|
background: var(--_bg);
|
|
@@ -8799,10 +9539,63 @@ var STYLES = `
|
|
|
8799
9539
|
cursor: pointer;
|
|
8800
9540
|
}
|
|
8801
9541
|
|
|
9542
|
+
/* Narrows the list. Sits under the header rather than in it: the header's two
|
|
9543
|
+
controls act on the conversation, and a field that filters what is below it
|
|
9544
|
+
belongs with what it filters. */
|
|
9545
|
+
/* What is waiting for the run to finish. Above the composer, beside the
|
|
9546
|
+
attachment tray, because both are things already handed over and not yet
|
|
9547
|
+
sent. Each chip is a button: pressing it takes that message back. */
|
|
9548
|
+
.queued {
|
|
9549
|
+
display: flex;
|
|
9550
|
+
flex-wrap: wrap;
|
|
9551
|
+
gap: var(--_space);
|
|
9552
|
+
padding: 0 var(--_pad) var(--_space);
|
|
9553
|
+
}
|
|
9554
|
+
|
|
9555
|
+
.queued-chip {
|
|
9556
|
+
max-width: 100%;
|
|
9557
|
+
overflow: hidden;
|
|
9558
|
+
padding: 4px 10px;
|
|
9559
|
+
border: 1px dashed var(--_border);
|
|
9560
|
+
border-radius: var(--_msg-radius);
|
|
9561
|
+
background: var(--_bg);
|
|
9562
|
+
color: var(--_muted);
|
|
9563
|
+
font: inherit;
|
|
9564
|
+
font-size: 0.9em;
|
|
9565
|
+
text-overflow: ellipsis;
|
|
9566
|
+
white-space: nowrap;
|
|
9567
|
+
cursor: pointer;
|
|
9568
|
+
}
|
|
9569
|
+
|
|
9570
|
+
.queued-chip:hover {
|
|
9571
|
+
border-style: solid;
|
|
9572
|
+
color: var(--_fg);
|
|
9573
|
+
}
|
|
9574
|
+
|
|
9575
|
+
.drawer-filter {
|
|
9576
|
+
flex: 0 0 auto;
|
|
9577
|
+
box-sizing: border-box;
|
|
9578
|
+
width: calc(100% - var(--_pad) * 2);
|
|
9579
|
+
margin: var(--_space) var(--_pad) 0;
|
|
9580
|
+
padding: 6px 10px;
|
|
9581
|
+
border: 1px solid var(--_border);
|
|
9582
|
+
border-radius: var(--_radius);
|
|
9583
|
+
background: var(--_input-bg);
|
|
9584
|
+
color: var(--_fg);
|
|
9585
|
+
font: inherit;
|
|
9586
|
+
font-size: 0.9em;
|
|
9587
|
+
}
|
|
9588
|
+
|
|
9589
|
+
.drawer-filter::placeholder {
|
|
9590
|
+
color: var(--_muted);
|
|
9591
|
+
}
|
|
9592
|
+
|
|
8802
9593
|
.drawer-list {
|
|
8803
9594
|
flex: 1;
|
|
8804
9595
|
min-height: 0;
|
|
8805
9596
|
overflow-y: auto;
|
|
9597
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
9598
|
+
overscroll-behavior: contain;
|
|
8806
9599
|
}
|
|
8807
9600
|
|
|
8808
9601
|
.drawer-empty {
|
|
@@ -8923,19 +9716,44 @@ var STYLES = `
|
|
|
8923
9716
|
background: none;
|
|
8924
9717
|
}
|
|
8925
9718
|
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
9719
|
+
/* The way back out of the list. It sits beside New chat, which is the control
|
|
9720
|
+
it must not be mistaken for: one returns you to the conversation you were
|
|
9721
|
+
reading, the other replaces it. */
|
|
9722
|
+
.drawer-close {
|
|
9723
|
+
flex: 0 0 auto;
|
|
9724
|
+
display: inline-flex;
|
|
9725
|
+
align-items: center;
|
|
9726
|
+
justify-content: center;
|
|
9727
|
+
width: 26px;
|
|
9728
|
+
height: 26px;
|
|
9729
|
+
padding: 0;
|
|
9730
|
+
border: 0;
|
|
9731
|
+
border-radius: 6px;
|
|
9732
|
+
background: none;
|
|
9733
|
+
color: inherit;
|
|
9734
|
+
font: inherit;
|
|
9735
|
+
font-size: 18px;
|
|
9736
|
+
line-height: 1;
|
|
9737
|
+
cursor: pointer;
|
|
9738
|
+
}
|
|
9739
|
+
|
|
9740
|
+
.drawer-close:hover {
|
|
9741
|
+
background: var(--_hover);
|
|
9742
|
+
}
|
|
9743
|
+
|
|
9744
|
+
:host([placement="embedded"]) .drawer-panel {
|
|
9745
|
+
width: 100%;
|
|
9746
|
+
border-right: none;
|
|
9747
|
+
box-shadow: none;
|
|
9748
|
+
}
|
|
9749
|
+
`;
|
|
9750
|
+
|
|
9751
|
+
// src/ui/subagent_panel.ts
|
|
9752
|
+
var SubAgentPanel = class {
|
|
9753
|
+
/** The panel's root; append this into the delegating card's slot. */
|
|
9754
|
+
element;
|
|
9755
|
+
/**
|
|
9756
|
+
* The child agent's name, as the last update that carried one gave it.
|
|
8939
9757
|
*
|
|
8940
9758
|
* Read back rather than only written to the DOM, because the two carriers
|
|
8941
9759
|
* split the name away from the close: `SUBAGENT_STARTED` names the agent and
|
|
@@ -9190,6 +10008,7 @@ var ThoughtsBlock = class {
|
|
|
9190
10008
|
};
|
|
9191
10009
|
|
|
9192
10010
|
// src/ui/thread_drawer.ts
|
|
10011
|
+
var FILTER_FROM = 8;
|
|
9193
10012
|
var ThreadDrawer = class {
|
|
9194
10013
|
/** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
|
|
9195
10014
|
element;
|
|
@@ -9202,6 +10021,23 @@ var ThreadDrawer = class {
|
|
|
9202
10021
|
#strings;
|
|
9203
10022
|
#threads = [];
|
|
9204
10023
|
#activeId = "";
|
|
10024
|
+
/** Dismisses the list without starting a new conversation. */
|
|
10025
|
+
#closeButton;
|
|
10026
|
+
/** Narrows the list. Only rendered once there is enough of a list to narrow. */
|
|
10027
|
+
#filter;
|
|
10028
|
+
/** The current filter text, lowercased once rather than per row. */
|
|
10029
|
+
#query = "";
|
|
10030
|
+
/**
|
|
10031
|
+
* Whether the list covers the conversation, or sits beside it.
|
|
10032
|
+
*
|
|
10033
|
+
* Everything a dialog implies follows from this and nothing else does: the
|
|
10034
|
+
* modal role, the focus trap, and taking focus on open. A list docked beside
|
|
10035
|
+
* a full-page conversation is a region of that page -- telling assistive
|
|
10036
|
+
* technology to ignore the rest of the document while it is showing would be
|
|
10037
|
+
* a lie, and stealing focus into it would interrupt someone mid-sentence to
|
|
10038
|
+
* announce a list they can already see.
|
|
10039
|
+
*/
|
|
10040
|
+
#modal = true;
|
|
9205
10041
|
/** The element focused before the drawer opened, restored on close. */
|
|
9206
10042
|
#lastFocused = null;
|
|
9207
10043
|
constructor(callbacks, strings = DEFAULT_UI_STRINGS) {
|
|
@@ -9238,11 +10074,29 @@ var ThreadDrawer = class {
|
|
|
9238
10074
|
this.close();
|
|
9239
10075
|
this.#callbacks.onNew();
|
|
9240
10076
|
});
|
|
9241
|
-
|
|
10077
|
+
this.#closeButton = document.createElement("button");
|
|
10078
|
+
this.#closeButton.type = "button";
|
|
10079
|
+
this.#closeButton.className = "drawer-close";
|
|
10080
|
+
this.#closeButton.setAttribute("part", "drawer-close");
|
|
10081
|
+
this.#closeButton.title = strings.closeHistory;
|
|
10082
|
+
this.#closeButton.setAttribute("aria-label", strings.closeHistory);
|
|
10083
|
+
this.#closeButton.append(document.createTextNode("\xD7"));
|
|
10084
|
+
this.#closeButton.addEventListener("click", () => this.close());
|
|
10085
|
+
header.append(this.#heading, this.#newButton, this.#closeButton);
|
|
10086
|
+
this.#filter = document.createElement("input");
|
|
10087
|
+
this.#filter.type = "search";
|
|
10088
|
+
this.#filter.className = "drawer-filter";
|
|
10089
|
+
this.#filter.setAttribute("part", "drawer-filter");
|
|
10090
|
+
this.#filter.placeholder = strings.searchConversations;
|
|
10091
|
+
this.#filter.setAttribute("aria-label", strings.searchConversations);
|
|
10092
|
+
this.#filter.addEventListener("input", () => {
|
|
10093
|
+
this.#query = this.#filter.value.trim().toLowerCase();
|
|
10094
|
+
this.#renderList();
|
|
10095
|
+
});
|
|
9242
10096
|
this.#list = document.createElement("div");
|
|
9243
10097
|
this.#list.className = "drawer-list";
|
|
9244
10098
|
this.#list.setAttribute("part", "drawer-list");
|
|
9245
|
-
this.#panel.append(header, this.#list);
|
|
10099
|
+
this.#panel.append(header, this.#filter, this.#list);
|
|
9246
10100
|
this.element.append(backdrop, this.#panel);
|
|
9247
10101
|
}
|
|
9248
10102
|
/** Re-localize the drawer's chrome and rows (the host calls this on connect). */
|
|
@@ -9266,8 +10120,26 @@ var ThreadDrawer = class {
|
|
|
9266
10120
|
this.#panel.setAttribute("aria-label", strings.chatHistory);
|
|
9267
10121
|
this.#heading.textContent = strings.chats;
|
|
9268
10122
|
this.#newButton.textContent = strings.newChat;
|
|
10123
|
+
this.#closeButton.title = strings.closeHistory;
|
|
10124
|
+
this.#closeButton.setAttribute("aria-label", strings.closeHistory);
|
|
10125
|
+
this.#filter.placeholder = strings.searchConversations;
|
|
10126
|
+
this.#filter.setAttribute("aria-label", strings.searchConversations);
|
|
9269
10127
|
this.#renderList();
|
|
9270
10128
|
}
|
|
10129
|
+
/**
|
|
10130
|
+
* Whether the list is a dialog over the conversation or a rail beside it.
|
|
10131
|
+
* The host decides, because only the placement knows whether there is room.
|
|
10132
|
+
*/
|
|
10133
|
+
setModal(modal) {
|
|
10134
|
+
this.#modal = modal;
|
|
10135
|
+
if (modal) {
|
|
10136
|
+
this.#panel.setAttribute("role", "dialog");
|
|
10137
|
+
this.#panel.setAttribute("aria-modal", "true");
|
|
10138
|
+
} else {
|
|
10139
|
+
this.#panel.setAttribute("role", "region");
|
|
10140
|
+
this.#panel.removeAttribute("aria-modal");
|
|
10141
|
+
}
|
|
10142
|
+
}
|
|
9271
10143
|
isOpen() {
|
|
9272
10144
|
return !this.element.hidden;
|
|
9273
10145
|
}
|
|
@@ -9277,15 +10149,21 @@ var ThreadDrawer = class {
|
|
|
9277
10149
|
}
|
|
9278
10150
|
this.#lastFocused = this.#activeElement();
|
|
9279
10151
|
this.element.hidden = false;
|
|
9280
|
-
this.#
|
|
10152
|
+
if (this.#modal) {
|
|
10153
|
+
this.#newButton.focus();
|
|
10154
|
+
}
|
|
10155
|
+
this.#callbacks.onVisibility?.(true);
|
|
9281
10156
|
}
|
|
9282
10157
|
close() {
|
|
9283
10158
|
if (!this.isOpen()) {
|
|
9284
10159
|
return;
|
|
9285
10160
|
}
|
|
9286
10161
|
this.element.hidden = true;
|
|
9287
|
-
this.#
|
|
10162
|
+
if (this.#modal) {
|
|
10163
|
+
this.#lastFocused?.focus();
|
|
10164
|
+
}
|
|
9288
10165
|
this.#lastFocused = null;
|
|
10166
|
+
this.#callbacks.onVisibility?.(false);
|
|
9289
10167
|
}
|
|
9290
10168
|
toggle() {
|
|
9291
10169
|
if (this.isOpen()) {
|
|
@@ -9305,7 +10183,7 @@ var ThreadDrawer = class {
|
|
|
9305
10183
|
this.close();
|
|
9306
10184
|
return;
|
|
9307
10185
|
}
|
|
9308
|
-
if (event.key !== "Tab") {
|
|
10186
|
+
if (event.key !== "Tab" || !this.#modal) {
|
|
9309
10187
|
return;
|
|
9310
10188
|
}
|
|
9311
10189
|
const focusables = Array.from(
|
|
@@ -9330,18 +10208,42 @@ var ThreadDrawer = class {
|
|
|
9330
10208
|
}
|
|
9331
10209
|
#renderList() {
|
|
9332
10210
|
this.#list.replaceChildren();
|
|
9333
|
-
|
|
10211
|
+
const hideFilter = this.#threads.length < FILTER_FROM;
|
|
10212
|
+
this.#filter.hidden = hideFilter;
|
|
10213
|
+
if (hideFilter && this.#query !== "") {
|
|
10214
|
+
this.#filter.value = "";
|
|
10215
|
+
this.#query = "";
|
|
10216
|
+
}
|
|
10217
|
+
const shown = this.#matching();
|
|
10218
|
+
if (shown.length === 0) {
|
|
9334
10219
|
const empty = document.createElement("div");
|
|
9335
10220
|
empty.className = "drawer-empty";
|
|
9336
10221
|
empty.setAttribute("part", "drawer-empty");
|
|
9337
|
-
empty.textContent = this.#strings.noConversations;
|
|
10222
|
+
empty.textContent = this.#threads.length === 0 ? this.#strings.noConversations : this.#strings.noMatches;
|
|
9338
10223
|
this.#list.appendChild(empty);
|
|
9339
10224
|
return;
|
|
9340
10225
|
}
|
|
9341
|
-
for (const meta of
|
|
10226
|
+
for (const meta of shown) {
|
|
9342
10227
|
this.#list.appendChild(this.#renderRow(meta));
|
|
9343
10228
|
}
|
|
9344
10229
|
}
|
|
10230
|
+
/**
|
|
10231
|
+
* The rows the filter leaves.
|
|
10232
|
+
*
|
|
10233
|
+
* Title and preview both, because the title is often the model's summary of
|
|
10234
|
+
* a conversation and the thing being looked for is as likely to be a phrase
|
|
10235
|
+
* from inside it. Client-side over what the drawer already holds: the server
|
|
10236
|
+
* index is fetched whole, so a query would be a round trip to filter a list
|
|
10237
|
+
* already in memory.
|
|
10238
|
+
*/
|
|
10239
|
+
#matching() {
|
|
10240
|
+
if (this.#query === "") {
|
|
10241
|
+
return this.#threads;
|
|
10242
|
+
}
|
|
10243
|
+
return this.#threads.filter(
|
|
10244
|
+
(meta) => meta.title.toLowerCase().includes(this.#query) || meta.preview.toLowerCase().includes(this.#query)
|
|
10245
|
+
);
|
|
10246
|
+
}
|
|
9345
10247
|
#renderRow(meta) {
|
|
9346
10248
|
const row = document.createElement("div");
|
|
9347
10249
|
row.className = "drawer-row";
|
|
@@ -11113,7 +12015,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11113
12015
|
* Resolve a `scroll_to` / `drag_and_drop` target string to a host-page
|
|
11114
12016
|
* element (or `null`). Defaults to a CSS-selector lookup; override to map
|
|
11115
12017
|
* page-map element ids. The page-action tools are opt-in via the
|
|
11116
|
-
* `data-page-actions` attribute (`"scroll"` / `"drag"`).
|
|
12018
|
+
* `data-page-actions` attribute (`"scroll"` / `"drag"` / `"chat"`).
|
|
11117
12019
|
*/
|
|
11118
12020
|
resolvePageTarget = (target) => document.querySelector(target);
|
|
11119
12021
|
/**
|
|
@@ -11229,6 +12131,12 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11229
12131
|
#root;
|
|
11230
12132
|
/** Screen-reader-only status region -- see {@link AgUiChat.#announce}. */
|
|
11231
12133
|
#announcer = document.createElement("div");
|
|
12134
|
+
/**
|
|
12135
|
+
* A zero-sized box carrying the host's viewport insets as padding, so they
|
|
12136
|
+
* can be read back as used pixel lengths. See the `.viewport-probe` rule for
|
|
12137
|
+
* why a custom property cannot be read directly.
|
|
12138
|
+
*/
|
|
12139
|
+
#viewportProbe = document.createElement("div");
|
|
11232
12140
|
/** Return-to-foot affordance, shown only once something has been missed. */
|
|
11233
12141
|
#jumpButton = document.createElement("button");
|
|
11234
12142
|
/**
|
|
@@ -11290,6 +12198,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11290
12198
|
#launcher;
|
|
11291
12199
|
/** The launcher's unread badge; hidden at zero, and when the host opts out. */
|
|
11292
12200
|
#badge;
|
|
12201
|
+
/** The rail's vertical caption. Rendered only by the sidebar's edge rail. */
|
|
12202
|
+
#railLabel = document.createElement("span");
|
|
11293
12203
|
// Answers that finished while the widget was collapsed. Expanding clears it.
|
|
11294
12204
|
#unread = 0;
|
|
11295
12205
|
/** Empty-state region at the top of the message list; hidden once anything renders. */
|
|
@@ -11302,6 +12212,14 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11302
12212
|
* position -- see #applyLauncherPlacement for what that costs the host.
|
|
11303
12213
|
*/
|
|
11304
12214
|
#launcherPos = null;
|
|
12215
|
+
/** What the user has sent this session, newest first, for arrow-key recall. */
|
|
12216
|
+
#sentDrafts = [];
|
|
12217
|
+
/** Typed while a run was in flight, oldest first; sent when it settles. */
|
|
12218
|
+
#queued = [];
|
|
12219
|
+
/** The row those show up in, above the composer. */
|
|
12220
|
+
#queuedRow = document.createElement("div");
|
|
12221
|
+
/** How far back the composer has been walked; null while the user is typing. */
|
|
12222
|
+
#recallIndex = null;
|
|
11305
12223
|
/**
|
|
11306
12224
|
* Where the user dragged the *panel*, in viewport coordinates, or null while
|
|
11307
12225
|
* its position is still derived from the launcher's.
|
|
@@ -11332,8 +12250,53 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11332
12250
|
* a field so `removeEventListener` on disconnect gets the same reference.
|
|
11333
12251
|
*/
|
|
11334
12252
|
#onViewportResize = () => {
|
|
12253
|
+
this.#publishVisualViewport();
|
|
12254
|
+
if (this.#dragging()) {
|
|
12255
|
+
return;
|
|
12256
|
+
}
|
|
11335
12257
|
this.#restoreLauncherPosition();
|
|
12258
|
+
if (this.#drawer.isOpen()) {
|
|
12259
|
+
this.#drawer.setModal(!this.#threadsDock());
|
|
12260
|
+
this.#syncThreadsState();
|
|
12261
|
+
}
|
|
11336
12262
|
};
|
|
12263
|
+
/**
|
|
12264
|
+
* Hold a resized box inside the part of the screen the host left free.
|
|
12265
|
+
*
|
|
12266
|
+
* Each edge on its own, unlike the drag's clamp: a drag moves a box of fixed
|
|
12267
|
+
* size, so pushing it back in is right, while a resize is anchored on the
|
|
12268
|
+
* opposite edge and pushing it back would move the edge the user is not
|
|
12269
|
+
* touching. Bounding each edge instead leaves the grip stopped at the limit
|
|
12270
|
+
* -- the gesture keeps going and the panel simply stops growing, which is
|
|
12271
|
+
* what dragging already does.
|
|
12272
|
+
*
|
|
12273
|
+
* The minimum size is the grip's own concern and is applied before this, so
|
|
12274
|
+
* a panel that cannot fit the space is left at its minimum and overflowing
|
|
12275
|
+
* rather than collapsed to nothing.
|
|
12276
|
+
*/
|
|
12277
|
+
#withinViewport(box) {
|
|
12278
|
+
const viewport = this.#viewport();
|
|
12279
|
+
const left = viewport.left + SCREEN_EDGE_MARGIN;
|
|
12280
|
+
const top = viewport.top + SCREEN_EDGE_MARGIN;
|
|
12281
|
+
const right = viewport.left + viewport.width - SCREEN_EDGE_MARGIN;
|
|
12282
|
+
const bottom = viewport.top + viewport.height - SCREEN_EDGE_MARGIN;
|
|
12283
|
+
return {
|
|
12284
|
+
left: Math.min(Math.max(box.left, left), box.right),
|
|
12285
|
+
top: Math.min(Math.max(box.top, top), box.bottom),
|
|
12286
|
+
right: Math.max(Math.min(box.right, right), box.left),
|
|
12287
|
+
bottom: Math.max(Math.min(box.bottom, bottom), box.top)
|
|
12288
|
+
};
|
|
12289
|
+
}
|
|
12290
|
+
/**
|
|
12291
|
+
* Whether a pointer or key gesture is currently placing the widget.
|
|
12292
|
+
*
|
|
12293
|
+
* Read from the stamp the drag helpers already set, rather than tracked
|
|
12294
|
+
* separately: one source of truth, and it clears on `pointercancel` as well
|
|
12295
|
+
* as `pointerup`, which is the end a touch gesture usually gets.
|
|
12296
|
+
*/
|
|
12297
|
+
#dragging() {
|
|
12298
|
+
return this.#launcher.hasAttribute("data-dragging") || this.#root.querySelector(".header[data-dragging]") !== null;
|
|
12299
|
+
}
|
|
11337
12300
|
/** Mic button mount point (input row); the control mounts on connect when enabled. */
|
|
11338
12301
|
#voiceSlot;
|
|
11339
12302
|
/** Voice-input control; created on connect when transcription is available. */
|
|
@@ -11455,6 +12418,9 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11455
12418
|
},
|
|
11456
12419
|
onDelete: (threadId) => {
|
|
11457
12420
|
this.#deleteThread(threadId);
|
|
12421
|
+
},
|
|
12422
|
+
onVisibility: () => {
|
|
12423
|
+
this.#syncThreadsState();
|
|
11458
12424
|
}
|
|
11459
12425
|
});
|
|
11460
12426
|
this.#checkpoints = new CheckpointMenu((runId, verb) => {
|
|
@@ -11543,11 +12509,15 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11543
12509
|
if (name === "placement") {
|
|
11544
12510
|
this.#releaseOwnedAxes();
|
|
11545
12511
|
this.#releaseLauncherPosition();
|
|
12512
|
+
if (!this.#collapsible() && this.collapsed) {
|
|
12513
|
+
this.setCollapsed(false);
|
|
12514
|
+
}
|
|
11546
12515
|
requestAnimationFrame(() => this.#syncResizeAnchor());
|
|
11547
12516
|
return;
|
|
11548
12517
|
}
|
|
11549
12518
|
if (name === "title-text") {
|
|
11550
12519
|
this.#title.textContent = value ?? this.#strings.title;
|
|
12520
|
+
this.#railLabel.textContent = this.#title.textContent;
|
|
11551
12521
|
return;
|
|
11552
12522
|
}
|
|
11553
12523
|
if (name === "user-key") {
|
|
@@ -11656,7 +12626,10 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11656
12626
|
const enabled = new Set(
|
|
11657
12627
|
attr.split(",").map((token) => token.trim()).filter((token) => token !== "")
|
|
11658
12628
|
);
|
|
11659
|
-
return
|
|
12629
|
+
return [
|
|
12630
|
+
...createPageActionTools(enabled, (target) => this.resolvePageTarget(target)),
|
|
12631
|
+
...enabled.has(PAGE_ACTIONS.CHAT) ? createChatSurfaceTools(this) : []
|
|
12632
|
+
];
|
|
11660
12633
|
}
|
|
11661
12634
|
/** All built-in (route + page + page-action + ask_user) frontend tools. */
|
|
11662
12635
|
#builtinTools() {
|
|
@@ -11896,7 +12869,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11896
12869
|
});
|
|
11897
12870
|
this.#strings = mergeUiStrings({ ...this.#readStringOverrides(), ...this.strings });
|
|
11898
12871
|
if (this.getAttribute("data-theme-toggle") !== null) {
|
|
11899
|
-
const saved = this.#
|
|
12872
|
+
const saved = this.#readPreference(THEME_KEY);
|
|
11900
12873
|
if (saved !== null) {
|
|
11901
12874
|
this.setAttribute("theme", saved);
|
|
11902
12875
|
}
|
|
@@ -11904,7 +12877,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11904
12877
|
this.#render();
|
|
11905
12878
|
this.#drawer.setStrings(this.#strings);
|
|
11906
12879
|
this.#checkpoints.setStrings(this.#strings);
|
|
11907
|
-
if (this.#
|
|
12880
|
+
if (this.#collapsible() && this.#startsCollapsed()) {
|
|
11908
12881
|
this.setAttribute("collapsed", "");
|
|
11909
12882
|
}
|
|
11910
12883
|
this.#syncLauncher();
|
|
@@ -11915,6 +12888,9 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11915
12888
|
this.conversationStore = this.#builtinStore;
|
|
11916
12889
|
}
|
|
11917
12890
|
window.addEventListener("resize", this.#onViewportResize);
|
|
12891
|
+
window.visualViewport?.addEventListener("resize", this.#onViewportResize);
|
|
12892
|
+
window.visualViewport?.addEventListener("scroll", this.#onViewportResize);
|
|
12893
|
+
this.#publishVisualViewport();
|
|
11918
12894
|
this.#wireThreadStore();
|
|
11919
12895
|
this.#wireAttachments();
|
|
11920
12896
|
this.#wireVoice();
|
|
@@ -11978,6 +12954,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
11978
12954
|
disconnectedCallback() {
|
|
11979
12955
|
this.#connected = false;
|
|
11980
12956
|
window.removeEventListener("resize", this.#onViewportResize);
|
|
12957
|
+
window.visualViewport?.removeEventListener("resize", this.#onViewportResize);
|
|
12958
|
+
window.visualViewport?.removeEventListener("scroll", this.#onViewportResize);
|
|
11981
12959
|
if (this.#claimedNs !== null) {
|
|
11982
12960
|
CLAIMED_NAMESPACES.delete(this.#claimedNs);
|
|
11983
12961
|
this.#claimedNs = null;
|
|
@@ -12050,7 +13028,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
12050
13028
|
this.#fileInput.accept = accept;
|
|
12051
13029
|
this.#attachButton.hidden = false;
|
|
12052
13030
|
this.#enableDragAndDrop();
|
|
12053
|
-
this.#enablePaste();
|
|
13031
|
+
this.#enablePaste(tray);
|
|
12054
13032
|
}
|
|
12055
13033
|
/** The built-in multipart upload handler for `data-attachments-url`, or `null`. */
|
|
12056
13034
|
#defaultUploadHandler(url) {
|
|
@@ -12285,6 +13263,62 @@ ${quoted}`;
|
|
|
12285
13263
|
}
|
|
12286
13264
|
});
|
|
12287
13265
|
}
|
|
13266
|
+
/**
|
|
13267
|
+
* Turn a very long text paste into an attachment instead of a wall of text.
|
|
13268
|
+
*
|
|
13269
|
+
* A composer capped at `40vh` is not where forty thousand characters go: the
|
|
13270
|
+
* user cannot read what they pasted, cannot edit around it, and sends one
|
|
13271
|
+
* enormous turn. As a file it stays whole, the model still receives it, and
|
|
13272
|
+
* the box is left for the question about it.
|
|
13273
|
+
*
|
|
13274
|
+
* Only where the host has configured uploads -- and structurally so, rather
|
|
13275
|
+
* than by a check here: the paste listener is wired inside the attachment
|
|
13276
|
+
* setup, so with no tray there is no listener at all and an ordinary paste is
|
|
13277
|
+
* untouched. Quietly dropping a paste for being long would be far worse than
|
|
13278
|
+
* an awkward composer. The tray is passed rather than read off the field for
|
|
13279
|
+
* the same reason its `onChange` hook is: it can only be called from one that
|
|
13280
|
+
* exists, so taking it as an argument removes a null check no caller can
|
|
13281
|
+
* reach.
|
|
13282
|
+
*
|
|
13283
|
+
* Nothing is lost by removing the chip: the text is still on the clipboard,
|
|
13284
|
+
* so pasting again brings it back. That is why this needs no undo of its own.
|
|
13285
|
+
*/
|
|
13286
|
+
#pasteLongTextAsFile(event, clipboard, tray) {
|
|
13287
|
+
const threshold = this.#pasteAttachThreshold();
|
|
13288
|
+
const text4 = clipboard.getData("text/plain");
|
|
13289
|
+
if (threshold === null || text4.length < threshold) {
|
|
13290
|
+
return;
|
|
13291
|
+
}
|
|
13292
|
+
event.preventDefault();
|
|
13293
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13294
|
+
tray.add(new File([text4], `pasted-${stamp}.txt`, { type: "text/plain" }));
|
|
13295
|
+
}
|
|
13296
|
+
/**
|
|
13297
|
+
* How long a pasted string has to be before it becomes a file, or `null` to
|
|
13298
|
+
* leave every paste in the composer.
|
|
13299
|
+
*
|
|
13300
|
+
* One attribute with three answers rather than three attributes: absent is
|
|
13301
|
+
* the default, `off` refuses, and a number states the threshold. A value that
|
|
13302
|
+
* is neither says so, because a typo silently meaning "off" is the failure
|
|
13303
|
+
* this whole release keeps finding.
|
|
13304
|
+
*/
|
|
13305
|
+
#pasteAttachThreshold() {
|
|
13306
|
+
const raw = this.getAttribute("data-paste-attach");
|
|
13307
|
+
if (raw === null) {
|
|
13308
|
+
return PASTE_ATTACH_CHARS;
|
|
13309
|
+
}
|
|
13310
|
+
if (raw === "off") {
|
|
13311
|
+
return null;
|
|
13312
|
+
}
|
|
13313
|
+
const stated = Number.parseInt(raw, 10);
|
|
13314
|
+
if (Number.isNaN(stated) || stated <= 0) {
|
|
13315
|
+
console.warn(
|
|
13316
|
+
`<ag-ui-chat>: data-paste-attach="${raw}" is neither "off" nor a positive number of characters, so the default of ${PASTE_ATTACH_CHARS} is used.`
|
|
13317
|
+
);
|
|
13318
|
+
return PASTE_ATTACH_CHARS;
|
|
13319
|
+
}
|
|
13320
|
+
return stated;
|
|
13321
|
+
}
|
|
12288
13322
|
/**
|
|
12289
13323
|
* Accept files pasted into the composer.
|
|
12290
13324
|
*
|
|
@@ -12298,7 +13332,7 @@ ${quoted}`;
|
|
|
12298
13332
|
* clipboard, and swallowing the words someone meant to paste in order to
|
|
12299
13333
|
* attach a picture they did not is the worse of the two failures.
|
|
12300
13334
|
*/
|
|
12301
|
-
#enablePaste() {
|
|
13335
|
+
#enablePaste(tray) {
|
|
12302
13336
|
this.#chat.addEventListener("paste", (event) => {
|
|
12303
13337
|
const clipboard = event.clipboardData ?? null;
|
|
12304
13338
|
if (clipboard === null) {
|
|
@@ -12306,6 +13340,7 @@ ${quoted}`;
|
|
|
12306
13340
|
}
|
|
12307
13341
|
const files = Array.from(clipboard.files);
|
|
12308
13342
|
if (files.length === 0) {
|
|
13343
|
+
this.#pasteLongTextAsFile(event, clipboard, tray);
|
|
12309
13344
|
return;
|
|
12310
13345
|
}
|
|
12311
13346
|
if (clipboard.getData("text/plain") === "") {
|
|
@@ -12466,7 +13501,13 @@ ${quoted}`;
|
|
|
12466
13501
|
* per-tab, and emit a {@link TOGGLE_EVENT} so a host can mirror the state in
|
|
12467
13502
|
* its own chrome.
|
|
12468
13503
|
*/
|
|
12469
|
-
setCollapsed(collapsed) {
|
|
13504
|
+
setCollapsed(collapsed, options = {}) {
|
|
13505
|
+
if (collapsed && !this.#collapsible()) {
|
|
13506
|
+
return;
|
|
13507
|
+
}
|
|
13508
|
+
if (options.announce === true && collapsed && !this.collapsed) {
|
|
13509
|
+
this.#announceSurfaceChange(this.#strings.chatMinimised, null);
|
|
13510
|
+
}
|
|
12470
13511
|
if (!collapsed) {
|
|
12471
13512
|
this.#restoreLauncherPosition();
|
|
12472
13513
|
}
|
|
@@ -12494,6 +13535,167 @@ ${quoted}`;
|
|
|
12494
13535
|
get unread() {
|
|
12495
13536
|
return this.#unread;
|
|
12496
13537
|
}
|
|
13538
|
+
/**
|
|
13539
|
+
* Whether to mount collapsed.
|
|
13540
|
+
*
|
|
13541
|
+
* A stored choice always wins -- in either direction, so a user who opened
|
|
13542
|
+
* the panel finds it open. With nothing stored, the corner placements start
|
|
13543
|
+
* collapsed: they are the two that have a launcher, and a launcher is the
|
|
13544
|
+
* resting state of every corner chat in the field. Mounting open put a
|
|
13545
|
+
* 380x560 panel over the host page's own bottom-right corner on a visitor's
|
|
13546
|
+
* first load, uninvited.
|
|
13547
|
+
*
|
|
13548
|
+
* The placements that place themselves are unchanged. A host that docks a
|
|
13549
|
+
* sidebar has already decided the widget belongs on screen, and one that
|
|
13550
|
+
* embeds it in its own layout has given it a box to fill.
|
|
13551
|
+
*
|
|
13552
|
+
* `data-start-open` restores the previous behaviour for a host that wants
|
|
13553
|
+
* the panel up immediately.
|
|
13554
|
+
*/
|
|
13555
|
+
#startsCollapsed() {
|
|
13556
|
+
const stored = this.#readScopedItem(COLLAPSED_KEY);
|
|
13557
|
+
if (stored !== null) {
|
|
13558
|
+
return stored === "1";
|
|
13559
|
+
}
|
|
13560
|
+
return DRAGGABLE_PLACEMENTS.has(this.getAttribute("placement")) && !this.hasAttribute("data-start-open");
|
|
13561
|
+
}
|
|
13562
|
+
/**
|
|
13563
|
+
* Whether this placement has a collapsed state at all.
|
|
13564
|
+
*
|
|
13565
|
+
* `page` does not. It is a dedicated route rather than a panel sitting on
|
|
13566
|
+
* someone else's page, so there is no "away" for it to go to: collapsing it
|
|
13567
|
+
* left a strip of application chrome fixed over a route that no longer had an
|
|
13568
|
+
* owner. It is also the placement that hides the launcher, so the usual way
|
|
13569
|
+
* back does not exist here.
|
|
13570
|
+
*
|
|
13571
|
+
* The header hides its collapse control under this placement, but a control
|
|
13572
|
+
* removed from the UI is not a state removed from the model -- the property,
|
|
13573
|
+
* the attribute and a value restored from storage all still reach it. This is
|
|
13574
|
+
* what the reachable paths are gated on; the stylesheet covers the one path
|
|
13575
|
+
* that never passes through here, an attribute written straight onto the
|
|
13576
|
+
* element.
|
|
13577
|
+
*/
|
|
13578
|
+
#collapsible() {
|
|
13579
|
+
return this.getAttribute("placement") !== "page";
|
|
13580
|
+
}
|
|
13581
|
+
/**
|
|
13582
|
+
* Describe the panel to whoever is asking -- in practice the agent, through
|
|
13583
|
+
* the opt-in chat-surface tools.
|
|
13584
|
+
*
|
|
13585
|
+
* `movable` folds two separate reasons into the one answer a caller needs:
|
|
13586
|
+
* a placement that places itself owns its position, and a panel that fills
|
|
13587
|
+
* the screen has nowhere to go. Reporting them apart would make every caller
|
|
13588
|
+
* re-derive the same conjunction.
|
|
13589
|
+
*/
|
|
13590
|
+
describeSurface() {
|
|
13591
|
+
const box = this.getBoundingClientRect();
|
|
13592
|
+
const viewport = this.#viewport();
|
|
13593
|
+
const fullBleed = box.width >= viewport.width - 1 && box.height >= viewport.height - 1;
|
|
13594
|
+
return {
|
|
13595
|
+
placement: this.getAttribute("placement"),
|
|
13596
|
+
collapsed: this.collapsed,
|
|
13597
|
+
collapsible: this.#collapsible(),
|
|
13598
|
+
movable: this.#launcherDraggable() && !fullBleed,
|
|
13599
|
+
draggable: this.#launcherDraggable(),
|
|
13600
|
+
fullBleed,
|
|
13601
|
+
box: {
|
|
13602
|
+
left: Math.round(box.left),
|
|
13603
|
+
top: Math.round(box.top),
|
|
13604
|
+
width: Math.round(box.width),
|
|
13605
|
+
height: Math.round(box.height)
|
|
13606
|
+
},
|
|
13607
|
+
viewport: {
|
|
13608
|
+
left: Math.round(viewport.left),
|
|
13609
|
+
top: Math.round(viewport.top),
|
|
13610
|
+
width: Math.round(viewport.width),
|
|
13611
|
+
height: Math.round(viewport.height)
|
|
13612
|
+
}
|
|
13613
|
+
};
|
|
13614
|
+
}
|
|
13615
|
+
/**
|
|
13616
|
+
* Send the panel to a corner, and report whether it went.
|
|
13617
|
+
*
|
|
13618
|
+
* A third claimant on the axes a placement and a user drag already share, and
|
|
13619
|
+
* it takes them the same way the drag does rather than inventing a second
|
|
13620
|
+
* mechanism: the same commit path, so the launcher travels with the panel,
|
|
13621
|
+
* the corner it opens from is re-picked, and switching placement hands
|
|
13622
|
+
* everything back. What it must not do is claim a move it did not make --
|
|
13623
|
+
* hence the boolean, and hence {@link describeSurface} existing so a caller
|
|
13624
|
+
* can ask first.
|
|
13625
|
+
*/
|
|
13626
|
+
moveTo(corner, options = {}) {
|
|
13627
|
+
if (!this.#launcherDraggable()) {
|
|
13628
|
+
return false;
|
|
13629
|
+
}
|
|
13630
|
+
const restore = options.announce === true ? this.#captureGeometry() : null;
|
|
13631
|
+
const viewport = this.#viewport();
|
|
13632
|
+
const box = this.getBoundingClientRect();
|
|
13633
|
+
if (box.width >= viewport.width - 1 && box.height >= viewport.height - 1) {
|
|
13634
|
+
return false;
|
|
13635
|
+
}
|
|
13636
|
+
const [edgeY, edgeX] = corner.split("-");
|
|
13637
|
+
const nearX = viewport.left + EDGE_MARGIN;
|
|
13638
|
+
const nearY = viewport.top + EDGE_MARGIN;
|
|
13639
|
+
const left = edgeX === "left" ? nearX : Math.max(nearX, viewport.left + viewport.width - EDGE_MARGIN - box.width);
|
|
13640
|
+
const top = edgeY === "top" ? nearY : Math.max(nearY, viewport.top + viewport.height - EDGE_MARGIN - box.height);
|
|
13641
|
+
const host = { left, top, right: left + box.width, bottom: top + box.height };
|
|
13642
|
+
const launcherWidth = this.#launcher.offsetWidth;
|
|
13643
|
+
const launcherHeight = this.#launcher.offsetHeight;
|
|
13644
|
+
this.#placePanelAndLauncher(host, {
|
|
13645
|
+
left: edgeX === "left" ? host.left : host.right - launcherWidth,
|
|
13646
|
+
top: edgeY === "top" ? host.top : host.bottom - launcherHeight,
|
|
13647
|
+
width: launcherWidth,
|
|
13648
|
+
height: launcherHeight
|
|
13649
|
+
});
|
|
13650
|
+
this.#storeLauncherPosition();
|
|
13651
|
+
if (restore !== null) {
|
|
13652
|
+
this.#announceSurfaceChange(this.#strings.chatMoved, restore);
|
|
13653
|
+
}
|
|
13654
|
+
return true;
|
|
13655
|
+
}
|
|
13656
|
+
/**
|
|
13657
|
+
* Snapshot the panel's stated position, and return a function that puts it
|
|
13658
|
+
* back.
|
|
13659
|
+
*
|
|
13660
|
+
* Both insets and the expand corner, because they are one decision: the
|
|
13661
|
+
* corner is what the panel grows from, so restoring a position without it
|
|
13662
|
+
* puts the box back and animates it out of the wrong side. Absent values are
|
|
13663
|
+
* captured as absent and removed on the way back, rather than written as
|
|
13664
|
+
* empty strings that would outrank the placement.
|
|
13665
|
+
*/
|
|
13666
|
+
#captureGeometry() {
|
|
13667
|
+
const inset2 = this.style.getPropertyValue("--ag-ui-inset");
|
|
13668
|
+
const launcherInset = this.style.getPropertyValue("--ag-ui-launcher-inset");
|
|
13669
|
+
const corner = this.getAttribute("data-expand-corner");
|
|
13670
|
+
const launcherPos = this.#launcherPos;
|
|
13671
|
+
const panelPos = this.#panelPos;
|
|
13672
|
+
const expandCorner = this.#expandCorner;
|
|
13673
|
+
return () => {
|
|
13674
|
+
const put = (name, value) => {
|
|
13675
|
+
if (value === "") {
|
|
13676
|
+
this.style.removeProperty(name);
|
|
13677
|
+
} else {
|
|
13678
|
+
this.style.setProperty(name, value);
|
|
13679
|
+
}
|
|
13680
|
+
};
|
|
13681
|
+
put("--ag-ui-inset", inset2);
|
|
13682
|
+
put("--ag-ui-launcher-inset", launcherInset);
|
|
13683
|
+
if (corner === null) {
|
|
13684
|
+
this.removeAttribute("data-expand-corner");
|
|
13685
|
+
} else {
|
|
13686
|
+
this.setAttribute("data-expand-corner", corner);
|
|
13687
|
+
}
|
|
13688
|
+
this.#launcherPos = launcherPos;
|
|
13689
|
+
this.#panelPos = panelPos;
|
|
13690
|
+
this.#expandCorner = expandCorner;
|
|
13691
|
+
if (launcherPos === null) {
|
|
13692
|
+
this.#clearPreference(LAUNCHER_KEY);
|
|
13693
|
+
} else {
|
|
13694
|
+
this.#storeLauncherPosition();
|
|
13695
|
+
}
|
|
13696
|
+
this.#syncResizeAnchor();
|
|
13697
|
+
};
|
|
13698
|
+
}
|
|
12497
13699
|
/** Flip the collapsed state. Bound to the built-in header toggle. */
|
|
12498
13700
|
toggleCollapsed() {
|
|
12499
13701
|
this.setCollapsed(!this.collapsed);
|
|
@@ -12507,7 +13709,7 @@ ${quoted}`;
|
|
|
12507
13709
|
toggleTheme() {
|
|
12508
13710
|
const next = this.getAttribute("theme") === "dark" ? "light" : "dark";
|
|
12509
13711
|
this.setAttribute("theme", next);
|
|
12510
|
-
|
|
13712
|
+
this.#writePreference(THEME_KEY, next);
|
|
12511
13713
|
this.#syncThemeGlyph();
|
|
12512
13714
|
}
|
|
12513
13715
|
/**
|
|
@@ -12635,9 +13837,81 @@ ${quoted}`;
|
|
|
12635
13837
|
#launcherDraggable() {
|
|
12636
13838
|
return this.getAttribute("data-launcher-drag") !== "false" && DRAGGABLE_PLACEMENTS.has(this.getAttribute("placement"));
|
|
12637
13839
|
}
|
|
12638
|
-
/**
|
|
13840
|
+
/**
|
|
13841
|
+
* The viewport the launcher and the panel both have to fit inside.
|
|
13842
|
+
*
|
|
13843
|
+
* The *visual* viewport, not the layout one, because they come apart exactly
|
|
13844
|
+
* when this matters. An on-screen keyboard shrinks the visual viewport and
|
|
13845
|
+
* leaves the layout viewport alone, so clamping against `innerHeight` parks
|
|
13846
|
+
* the launcher behind the keyboard and decides which corner to open into
|
|
13847
|
+
* using space that is not on the screen. Pinch-zoom does the same on both
|
|
13848
|
+
* axes.
|
|
13849
|
+
*
|
|
13850
|
+
* Falls back where the API is absent, which keeps this working in the
|
|
13851
|
+
* happy-dom project as well as in an old browser.
|
|
13852
|
+
*/
|
|
12639
13853
|
#viewport() {
|
|
12640
|
-
|
|
13854
|
+
const visual = window.visualViewport;
|
|
13855
|
+
const width = visual?.width ?? window.innerWidth;
|
|
13856
|
+
const height = visual?.height ?? window.innerHeight;
|
|
13857
|
+
const style = getComputedStyle(this.#viewportProbe);
|
|
13858
|
+
const edge = (name) => {
|
|
13859
|
+
const value = Number.parseFloat(style.getPropertyValue(name));
|
|
13860
|
+
return Number.isFinite(value) ? value : 0;
|
|
13861
|
+
};
|
|
13862
|
+
const left = edge("padding-left");
|
|
13863
|
+
const top = edge("padding-top");
|
|
13864
|
+
return {
|
|
13865
|
+
left,
|
|
13866
|
+
top,
|
|
13867
|
+
width: Math.max(0, width - left - edge("padding-right")),
|
|
13868
|
+
height: Math.max(0, height - top - edge("padding-bottom"))
|
|
13869
|
+
};
|
|
13870
|
+
}
|
|
13871
|
+
/**
|
|
13872
|
+
* The whole viewport, before anything the host reserved is taken out of it.
|
|
13873
|
+
*
|
|
13874
|
+
* Distinct from {@link #viewport} on purpose, and the two must not be
|
|
13875
|
+
* swapped. The usable box decides where the widget may rest; this is what a
|
|
13876
|
+
* CSS `inset` on a fixed element is measured from, because that is what the
|
|
13877
|
+
* browser measures it from.
|
|
13878
|
+
*/
|
|
13879
|
+
#screen() {
|
|
13880
|
+
const root = document.documentElement;
|
|
13881
|
+
return {
|
|
13882
|
+
width: root.clientWidth || window.innerWidth,
|
|
13883
|
+
height: root.clientHeight || window.innerHeight
|
|
13884
|
+
};
|
|
13885
|
+
}
|
|
13886
|
+
/**
|
|
13887
|
+
* Publish the measured viewport height so the stylesheet can size a
|
|
13888
|
+
* full-bleed panel to what the user can see.
|
|
13889
|
+
*
|
|
13890
|
+
* No CSS length carries this. An on-screen keyboard has no effect on any
|
|
13891
|
+
* viewport-percentage unit, so a panel sized from `100dvh` puts its composer
|
|
13892
|
+
* behind the keyboard being typed into. Written inline, and read through a
|
|
13893
|
+
* token the host's own `--ag-ui-viewport-height` still outranks.
|
|
13894
|
+
*
|
|
13895
|
+
* Removed rather than frozen when the two viewports agree, so a desktop that
|
|
13896
|
+
* never diverges carries no inline override at all and the declared fallback
|
|
13897
|
+
* stays in charge.
|
|
13898
|
+
*/
|
|
13899
|
+
#publishVisualViewport() {
|
|
13900
|
+
const visual = window.visualViewport;
|
|
13901
|
+
if (visual === null || visual === void 0) {
|
|
13902
|
+
return;
|
|
13903
|
+
}
|
|
13904
|
+
if (Math.abs(visual.height - window.innerHeight) < 1) {
|
|
13905
|
+
this.style.removeProperty("--ag-ui-visual-viewport-height");
|
|
13906
|
+
this.style.removeProperty("--ag-ui-visual-viewport-inset-bottom");
|
|
13907
|
+
return;
|
|
13908
|
+
}
|
|
13909
|
+
this.style.setProperty("--ag-ui-visual-viewport-height", `${Math.round(visual.height)}px`);
|
|
13910
|
+
const hidden = window.innerHeight - visual.height - visual.offsetTop;
|
|
13911
|
+
this.style.setProperty(
|
|
13912
|
+
"--ag-ui-visual-viewport-inset-bottom",
|
|
13913
|
+
`${Math.max(0, Math.round(hidden))}px`
|
|
13914
|
+
);
|
|
12641
13915
|
}
|
|
12642
13916
|
/**
|
|
12643
13917
|
* The launcher's box in viewport coordinates, with its transform divided out.
|
|
@@ -12686,7 +13960,8 @@ ${quoted}`;
|
|
|
12686
13960
|
const placement = launcherPlacement(
|
|
12687
13961
|
this.#launcherBox(),
|
|
12688
13962
|
{ width: panel.width, height: panel.height },
|
|
12689
|
-
this.#viewport()
|
|
13963
|
+
this.#viewport(),
|
|
13964
|
+
this.#screen()
|
|
12690
13965
|
);
|
|
12691
13966
|
this.style.setProperty("--ag-ui-inset", placement.hostInset);
|
|
12692
13967
|
this.style.setProperty("--ag-ui-launcher-inset", placement.launcherInset);
|
|
@@ -12717,28 +13992,39 @@ ${quoted}`;
|
|
|
12717
13992
|
* measured from it, and rewriting one of them from a new corner while the
|
|
12718
13993
|
* other still names the old one would move the launcher for no reason.
|
|
12719
13994
|
*/
|
|
12720
|
-
#movePanel(box) {
|
|
13995
|
+
#movePanel(box, from) {
|
|
12721
13996
|
if (!this.#launcherDraggable()) {
|
|
12722
|
-
return box;
|
|
13997
|
+
return { held: box, launcher: null };
|
|
12723
13998
|
}
|
|
12724
13999
|
if (this.#launcherPos === null) {
|
|
12725
14000
|
const resting = this.#launcherBox();
|
|
12726
14001
|
this.#launcherPos = { left: resting.left, top: resting.top };
|
|
12727
14002
|
}
|
|
12728
|
-
const
|
|
14003
|
+
const start = this.#launcherPos;
|
|
14004
|
+
const held = clampPanel(box, this.#viewport(), SCREEN_EDGE_MARGIN);
|
|
12729
14005
|
const corner = this.#expandCorner ?? this.#anchor;
|
|
12730
|
-
const
|
|
14006
|
+
const screen = this.#screen();
|
|
12731
14007
|
this.style.setProperty(
|
|
12732
14008
|
"--ag-ui-inset",
|
|
12733
14009
|
[
|
|
12734
14010
|
corner.y === "top" ? `${Math.round(held.top)}px` : "auto",
|
|
12735
|
-
corner.x === "right" ? `${Math.round(
|
|
12736
|
-
corner.y === "bottom" ? `${Math.round(
|
|
14011
|
+
corner.x === "right" ? `${Math.round(screen.width - held.right)}px` : "auto",
|
|
14012
|
+
corner.y === "bottom" ? `${Math.round(screen.height - held.bottom)}px` : "auto",
|
|
12737
14013
|
corner.x === "left" ? `${Math.round(held.left)}px` : "auto"
|
|
12738
14014
|
].join(" ")
|
|
12739
14015
|
);
|
|
12740
14016
|
this.#panelPos = { left: held.left, top: held.top };
|
|
12741
|
-
|
|
14017
|
+
const carried = {
|
|
14018
|
+
...this.#launcherBox(),
|
|
14019
|
+
left: start.left + (held.left - from.left),
|
|
14020
|
+
top: start.top + (held.top - from.top)
|
|
14021
|
+
};
|
|
14022
|
+
const launcher = { ...carried, ...clampLauncher(carried, this.#viewport()) };
|
|
14023
|
+
this.style.setProperty(
|
|
14024
|
+
"--ag-ui-launcher-inset",
|
|
14025
|
+
placeWidget(held, launcher, corner, this.#screen()).launcherInset
|
|
14026
|
+
);
|
|
14027
|
+
return { held, launcher };
|
|
12742
14028
|
}
|
|
12743
14029
|
/**
|
|
12744
14030
|
* Finish a header drag: settle where both halves ended up, and remember it.
|
|
@@ -12754,20 +14040,11 @@ ${quoted}`;
|
|
|
12754
14040
|
* both insets are rewritten from positions that are already decided.
|
|
12755
14041
|
*/
|
|
12756
14042
|
#commitPanel(box, from) {
|
|
12757
|
-
const held = this.#movePanel(box);
|
|
12758
|
-
|
|
12759
|
-
if (!this.#launcherDraggable() || start === null) {
|
|
14043
|
+
const { held, launcher } = this.#movePanel(box, from);
|
|
14044
|
+
if (launcher === null) {
|
|
12760
14045
|
return;
|
|
12761
14046
|
}
|
|
12762
|
-
|
|
12763
|
-
...this.#launcherBox(),
|
|
12764
|
-
left: start.left + (held.left - from.left),
|
|
12765
|
-
top: start.top + (held.top - from.top)
|
|
12766
|
-
};
|
|
12767
|
-
this.#placePanelAndLauncher(held, {
|
|
12768
|
-
...carried,
|
|
12769
|
-
...clampLauncher(carried, this.#viewport())
|
|
12770
|
-
});
|
|
14047
|
+
this.#placePanelAndLauncher(held, launcher);
|
|
12771
14048
|
this.#storeLauncherPosition();
|
|
12772
14049
|
}
|
|
12773
14050
|
/**
|
|
@@ -12776,9 +14053,10 @@ ${quoted}`;
|
|
|
12776
14053
|
*/
|
|
12777
14054
|
#placePanelAndLauncher(host, launcher) {
|
|
12778
14055
|
const viewport = this.#viewport();
|
|
14056
|
+
const screen = this.#screen();
|
|
12779
14057
|
const size = { width: host.right - host.left, height: host.bottom - host.top };
|
|
12780
|
-
const { corner } = launcherPlacement(launcher, size, viewport);
|
|
12781
|
-
const insets = placeWidget(host, launcher, corner,
|
|
14058
|
+
const { corner } = launcherPlacement(launcher, size, viewport, screen);
|
|
14059
|
+
const insets = placeWidget(host, launcher, corner, screen);
|
|
12782
14060
|
this.style.setProperty("--ag-ui-inset", insets.hostInset);
|
|
12783
14061
|
this.style.setProperty("--ag-ui-launcher-inset", insets.launcherInset);
|
|
12784
14062
|
this.#launcherPos = { left: launcher.left, top: launcher.top };
|
|
@@ -12802,7 +14080,8 @@ ${quoted}`;
|
|
|
12802
14080
|
const rect = this.getBoundingClientRect();
|
|
12803
14081
|
const held = clampPanel(
|
|
12804
14082
|
{ left: at2.left, top: at2.top, right: at2.left + rect.width, bottom: at2.top + rect.height },
|
|
12805
|
-
this.#viewport()
|
|
14083
|
+
this.#viewport(),
|
|
14084
|
+
SCREEN_EDGE_MARGIN
|
|
12806
14085
|
);
|
|
12807
14086
|
const launcher = this.#launcherBox();
|
|
12808
14087
|
const carried = {
|
|
@@ -12830,8 +14109,8 @@ ${quoted}`;
|
|
|
12830
14109
|
return;
|
|
12831
14110
|
}
|
|
12832
14111
|
const panel = this.#panelPos;
|
|
12833
|
-
|
|
12834
|
-
|
|
14112
|
+
this.#writePreference(
|
|
14113
|
+
LAUNCHER_KEY,
|
|
12835
14114
|
JSON.stringify(panel === null ? position : { ...position, panel })
|
|
12836
14115
|
);
|
|
12837
14116
|
}
|
|
@@ -12860,7 +14139,7 @@ ${quoted}`;
|
|
|
12860
14139
|
}
|
|
12861
14140
|
/** The persisted position for this instance, or null. */
|
|
12862
14141
|
#readLauncherPosition() {
|
|
12863
|
-
const raw = this.#
|
|
14142
|
+
const raw = this.#readPreference(LAUNCHER_KEY);
|
|
12864
14143
|
if (raw === null) {
|
|
12865
14144
|
return null;
|
|
12866
14145
|
}
|
|
@@ -12910,19 +14189,20 @@ ${quoted}`;
|
|
|
12910
14189
|
* drags the edge it was holding.
|
|
12911
14190
|
*/
|
|
12912
14191
|
#applyResize(grip, box) {
|
|
14192
|
+
box = this.#withinViewport(box);
|
|
12913
14193
|
this.#applySize({ width: box.right - box.left, height: box.bottom - box.top });
|
|
12914
14194
|
if (grip.x !== this.#anchor.x && grip.y !== this.#anchor.y) {
|
|
12915
|
-
return;
|
|
14195
|
+
return box;
|
|
12916
14196
|
}
|
|
12917
|
-
const
|
|
14197
|
+
const screen = this.#screen();
|
|
12918
14198
|
const anchor = this.#anchor;
|
|
12919
14199
|
const side = (value) => `${Math.round(value)}px`;
|
|
12920
14200
|
this.style.setProperty(
|
|
12921
14201
|
"--ag-ui-inset",
|
|
12922
14202
|
[
|
|
12923
14203
|
anchor.y === "top" ? side(box.top) : "auto",
|
|
12924
|
-
anchor.x === "right" ? side(
|
|
12925
|
-
anchor.y === "bottom" ? side(
|
|
14204
|
+
anchor.x === "right" ? side(screen.width - box.right) : "auto",
|
|
14205
|
+
anchor.y === "bottom" ? side(screen.height - box.bottom) : "auto",
|
|
12926
14206
|
anchor.x === "left" ? side(box.left) : "auto"
|
|
12927
14207
|
].join(" ")
|
|
12928
14208
|
);
|
|
@@ -12936,11 +14216,12 @@ ${quoted}`;
|
|
|
12936
14216
|
if (this.#panelPos !== null) {
|
|
12937
14217
|
this.#panelPos = { left: box.left, top: box.top };
|
|
12938
14218
|
}
|
|
14219
|
+
return box;
|
|
12939
14220
|
}
|
|
12940
14221
|
/** Finish a resize: keep the box, remember it, and re-read the pinned edges. */
|
|
12941
14222
|
#commitResize(grip, box) {
|
|
12942
|
-
this.#applyResize(grip, box);
|
|
12943
|
-
this.#persistSize({ width:
|
|
14223
|
+
const held = this.#applyResize(grip, box);
|
|
14224
|
+
this.#persistSize({ width: held.right - held.left, height: held.bottom - held.top });
|
|
12944
14225
|
this.#storeLauncherPosition();
|
|
12945
14226
|
this.#syncResizeAnchor();
|
|
12946
14227
|
}
|
|
@@ -12967,11 +14248,11 @@ ${quoted}`;
|
|
|
12967
14248
|
/** Persist a dragged size per tab, alongside the collapsed/theme preferences. */
|
|
12968
14249
|
#persistSize(size) {
|
|
12969
14250
|
const stored = { ...this.#readSize(), ...size };
|
|
12970
|
-
|
|
14251
|
+
this.#writePreference(SIZE_KEY, JSON.stringify(stored));
|
|
12971
14252
|
}
|
|
12972
14253
|
/** The persisted size for this instance, or an empty record. */
|
|
12973
14254
|
#readSize() {
|
|
12974
|
-
const raw = this.#
|
|
14255
|
+
const raw = this.#readPreference(SIZE_KEY);
|
|
12975
14256
|
if (raw === null) {
|
|
12976
14257
|
return {};
|
|
12977
14258
|
}
|
|
@@ -13098,10 +14379,81 @@ ${quoted}`;
|
|
|
13098
14379
|
}
|
|
13099
14380
|
return sessionStorage.getItem(base);
|
|
13100
14381
|
}
|
|
14382
|
+
/**
|
|
14383
|
+
* Read a layout preference: where the widget sits, how big it is, which
|
|
14384
|
+
* theme it wears.
|
|
14385
|
+
*
|
|
14386
|
+
* These live in `localStorage` rather than beside the transcript, because a
|
|
14387
|
+
* layout preference is not a conversation. The transcript is deliberately
|
|
14388
|
+
* per-tab -- two tabs are two conversations, and closing the tab ends it --
|
|
14389
|
+
* and everything else inherited that scoping without earning it. A user who
|
|
14390
|
+
* dragged the panel clear of their own UI did it again in the next tab, and
|
|
14391
|
+
* again after every restart.
|
|
14392
|
+
*
|
|
14393
|
+
* Whether the widget is *currently open* stays per-tab with the transcript.
|
|
14394
|
+
* It is a statement about this tab rather than a preference: carrying it
|
|
14395
|
+
* across would pop the panel open on every new tab because it was opened
|
|
14396
|
+
* once, somewhere else.
|
|
14397
|
+
*
|
|
14398
|
+
* Falls back to the session value it used to be written to, so an existing
|
|
14399
|
+
* position survives the upgrade rather than resetting once.
|
|
14400
|
+
*/
|
|
14401
|
+
#readPreference(base) {
|
|
14402
|
+
try {
|
|
14403
|
+
const stored = localStorage.getItem(this.#storageKey(base));
|
|
14404
|
+
if (stored !== null) {
|
|
14405
|
+
return stored;
|
|
14406
|
+
}
|
|
14407
|
+
} catch {
|
|
14408
|
+
}
|
|
14409
|
+
return this.#readScopedItem(base);
|
|
14410
|
+
}
|
|
14411
|
+
/**
|
|
14412
|
+
* Persist a layout preference as durably as this browser allows: to
|
|
14413
|
+
* `localStorage` so it outlives the tab, and to the per-tab store as well.
|
|
14414
|
+
*
|
|
14415
|
+
* The second write is not redundancy for its own sake. A privacy mode can
|
|
14416
|
+
* deny `localStorage` while allowing `sessionStorage`, and losing the
|
|
14417
|
+
* durable copy should degrade to the per-tab behaviour this replaced rather
|
|
14418
|
+
* than to no persistence at all. The read above prefers the durable copy, so
|
|
14419
|
+
* a tab that has both cannot be shadowed by its own stale one.
|
|
14420
|
+
*
|
|
14421
|
+
* Neither write is worth an exception. Losing where the panel sat is not
|
|
14422
|
+
* worth a warning either -- unlike the transcript, which says so once,
|
|
14423
|
+
* because losing that loses the conversation on the next reload.
|
|
14424
|
+
*/
|
|
14425
|
+
#writePreference(base, value) {
|
|
14426
|
+
const key = this.#storageKey(base);
|
|
14427
|
+
try {
|
|
14428
|
+
localStorage.setItem(key, value);
|
|
14429
|
+
} catch {
|
|
14430
|
+
}
|
|
14431
|
+
writeStoredItem(key, value);
|
|
14432
|
+
}
|
|
14433
|
+
/**
|
|
14434
|
+
* Drop a layout preference from both stores.
|
|
14435
|
+
*
|
|
14436
|
+
* The mirror of {@link AgUiChat.#writePreference}, and it has to clear both
|
|
14437
|
+
* for the same reason that writes both: leaving either copy behind means the
|
|
14438
|
+
* value comes back on the next read.
|
|
14439
|
+
*/
|
|
14440
|
+
#clearPreference(base) {
|
|
14441
|
+
const key = this.#storageKey(base);
|
|
14442
|
+
try {
|
|
14443
|
+
localStorage.removeItem(key);
|
|
14444
|
+
} catch {
|
|
14445
|
+
}
|
|
14446
|
+
try {
|
|
14447
|
+
sessionStorage.removeItem(key);
|
|
14448
|
+
} catch {
|
|
14449
|
+
}
|
|
14450
|
+
}
|
|
13101
14451
|
/** Reflect the current theme on the toggle: show the destination's glyph. */
|
|
13102
14452
|
#syncThemeGlyph() {
|
|
13103
14453
|
const dark = this.getAttribute("theme") === "dark";
|
|
13104
|
-
this.#themeToggle.
|
|
14454
|
+
this.#themeToggle.replaceChildren(
|
|
14455
|
+
this.#iconElement("theme", "theme-icon", dark ? ICON_SUN : ICON_MOON, null)
|
|
14456
|
+
);
|
|
13105
14457
|
}
|
|
13106
14458
|
/**
|
|
13107
14459
|
* Open the thread-history drawer: the imperative route to the control that
|
|
@@ -13115,7 +14467,37 @@ ${quoted}`;
|
|
|
13115
14467
|
openThreads() {
|
|
13116
14468
|
this.#checkpoints.close();
|
|
13117
14469
|
void this.#refreshDrawer();
|
|
14470
|
+
this.#drawer.setModal(!this.#threadsDock());
|
|
13118
14471
|
this.#drawer.open();
|
|
14472
|
+
this.#syncThreadsState();
|
|
14473
|
+
}
|
|
14474
|
+
/**
|
|
14475
|
+
* Whether the conversation list docks beside the transcript rather than
|
|
14476
|
+
* covering it.
|
|
14477
|
+
*
|
|
14478
|
+
* Only the full-page placement, and only where there is room. A dedicated
|
|
14479
|
+
* route is the one surface with width to spare -- everywhere else the panel
|
|
14480
|
+
* is a few hundred pixels wide, and a list docked into that leaves a column
|
|
14481
|
+
* of transcript too narrow to read. The width is the panel's own rather than
|
|
14482
|
+
* the window's, because an embedded host can give a full-page-sized box to
|
|
14483
|
+
* something that is not a page.
|
|
14484
|
+
*/
|
|
14485
|
+
#threadsDock() {
|
|
14486
|
+
return this.getAttribute("placement") === "page" && this.getBoundingClientRect().width >= THREADS_DOCK_MIN_WIDTH;
|
|
14487
|
+
}
|
|
14488
|
+
/**
|
|
14489
|
+
* Stamp whether the list is showing, and how.
|
|
14490
|
+
*
|
|
14491
|
+
* On the host rather than inside the shell because the transcript has to move
|
|
14492
|
+
* over for a docked list, and the drawer is the last child of the panel -- CSS
|
|
14493
|
+
* cannot select backwards from it to the rows it needs to shift.
|
|
14494
|
+
*/
|
|
14495
|
+
#syncThreadsState() {
|
|
14496
|
+
if (this.#drawer.isOpen() && this.#threadsDock()) {
|
|
14497
|
+
this.setAttribute("data-threads-docked", "");
|
|
14498
|
+
} else {
|
|
14499
|
+
this.removeAttribute("data-threads-docked");
|
|
14500
|
+
}
|
|
13119
14501
|
}
|
|
13120
14502
|
/**
|
|
13121
14503
|
* Open the checkpoints panel (the `::part(checkpoints-button)` route).
|
|
@@ -13129,6 +14511,16 @@ ${quoted}`;
|
|
|
13129
14511
|
void this.#refreshCheckpoints();
|
|
13130
14512
|
this.#checkpoints.open();
|
|
13131
14513
|
}
|
|
14514
|
+
/**
|
|
14515
|
+
* Close the conversation list, if it is open.
|
|
14516
|
+
*
|
|
14517
|
+
* No state sync here: the drawer reports every close through `onVisibility`,
|
|
14518
|
+
* including the four that never reach this method, and doing it twice would
|
|
14519
|
+
* be a second place to keep right.
|
|
14520
|
+
*/
|
|
14521
|
+
closeThreads() {
|
|
14522
|
+
this.#drawer.close();
|
|
14523
|
+
}
|
|
13132
14524
|
/** Close the checkpoints panel, if it is open. */
|
|
13133
14525
|
closeCheckpoints() {
|
|
13134
14526
|
this.#checkpoints.close();
|
|
@@ -13169,6 +14561,8 @@ ${quoted}`;
|
|
|
13169
14561
|
this.#client = null;
|
|
13170
14562
|
this.#clearTranscript();
|
|
13171
14563
|
this.#initialMessages = [];
|
|
14564
|
+
this.#sentDrafts.length = 0;
|
|
14565
|
+
this.#recallIndex = null;
|
|
13172
14566
|
}
|
|
13173
14567
|
/**
|
|
13174
14568
|
* Wipe the rendered transcript and everything that indexes into it.
|
|
@@ -13505,7 +14899,7 @@ ${quoted}`;
|
|
|
13505
14899
|
enablePanelDrag(header, {
|
|
13506
14900
|
enabled: () => !this.collapsed && this.#launcherDraggable(),
|
|
13507
14901
|
rect: () => this.getBoundingClientRect(),
|
|
13508
|
-
apply: (box) => this.#movePanel(box),
|
|
14902
|
+
apply: (box, from) => this.#movePanel(box, from),
|
|
13509
14903
|
commit: (box, from) => this.#commitPanel(box, from)
|
|
13510
14904
|
});
|
|
13511
14905
|
this.#messages.className = "messages";
|
|
@@ -13550,7 +14944,16 @@ ${quoted}`;
|
|
|
13550
14944
|
this.#emptyWrap.setAttribute("part", "empty");
|
|
13551
14945
|
const emptySlot = document.createElement("slot");
|
|
13552
14946
|
emptySlot.name = "empty";
|
|
14947
|
+
const starters = this.#starterChips();
|
|
14948
|
+
if (starters !== null) {
|
|
14949
|
+
emptySlot.append(starters);
|
|
14950
|
+
}
|
|
13553
14951
|
this.#emptyWrap.append(emptySlot);
|
|
14952
|
+
this.#queuedRow.className = "queued";
|
|
14953
|
+
this.#queuedRow.setAttribute("part", "queued");
|
|
14954
|
+
this.#queuedRow.setAttribute("role", "group");
|
|
14955
|
+
this.#queuedRow.setAttribute("aria-label", this.#strings.queued);
|
|
14956
|
+
this.#queuedRow.hidden = true;
|
|
13554
14957
|
this.#messages.append(this.#emptyWrap);
|
|
13555
14958
|
const inputRow = document.createElement("div");
|
|
13556
14959
|
inputRow.className = "input-row";
|
|
@@ -13616,6 +15019,7 @@ ${quoted}`;
|
|
|
13616
15019
|
this.#skillsMenu.palette,
|
|
13617
15020
|
this.#skillsMenu.chips,
|
|
13618
15021
|
this.#skillHint,
|
|
15022
|
+
this.#queuedRow,
|
|
13619
15023
|
this.#attachSlot,
|
|
13620
15024
|
inputRow,
|
|
13621
15025
|
footer,
|
|
@@ -13640,8 +15044,13 @@ ${quoted}`;
|
|
|
13640
15044
|
this.#badge.setAttribute("part", "launcher-badge");
|
|
13641
15045
|
this.#badge.setAttribute("aria-hidden", "true");
|
|
13642
15046
|
this.#badge.hidden = true;
|
|
15047
|
+
this.#railLabel.className = "rail-label";
|
|
15048
|
+
this.#railLabel.setAttribute("part", "rail-label");
|
|
15049
|
+
this.#railLabel.setAttribute("aria-hidden", "true");
|
|
15050
|
+
this.#railLabel.textContent = this.getAttribute("title-text") ?? this.#strings.title;
|
|
13643
15051
|
this.#launcher.append(
|
|
13644
15052
|
this.#iconElement("launcher", "launcher-icon", ICON_LAUNCHER, this.#launcherIconUrl()),
|
|
15053
|
+
this.#railLabel,
|
|
13645
15054
|
this.#badge
|
|
13646
15055
|
);
|
|
13647
15056
|
this.#launcher.addEventListener("click", () => this.setCollapsed(false));
|
|
@@ -13667,7 +15076,9 @@ ${quoted}`;
|
|
|
13667
15076
|
}
|
|
13668
15077
|
this.#focusableGrip();
|
|
13669
15078
|
this.#adoptStyles();
|
|
13670
|
-
this.#
|
|
15079
|
+
this.#viewportProbe.className = "viewport-probe";
|
|
15080
|
+
this.#viewportProbe.setAttribute("aria-hidden", "true");
|
|
15081
|
+
this.#root.append(this.#viewportProbe, this.#announcer, this.#chat, this.#launcher);
|
|
13671
15082
|
}
|
|
13672
15083
|
/**
|
|
13673
15084
|
* Attach the stylesheet without an inline `<style>` element.
|
|
@@ -13846,6 +15257,37 @@ ${quoted}`;
|
|
|
13846
15257
|
this.#setUnread(this.#unread + 1);
|
|
13847
15258
|
}
|
|
13848
15259
|
/** Hide the empty-state region once the message list holds anything else. */
|
|
15260
|
+
/**
|
|
15261
|
+
* The prompts offered on an empty transcript, from `data-starters`.
|
|
15262
|
+
*
|
|
15263
|
+
* Different from the suggestion chips a run pushes, which are follow-ups to
|
|
15264
|
+
* something already said. These answer the blank-page question instead, and
|
|
15265
|
+
* they are the host's rather than the model's -- only the host knows what its
|
|
15266
|
+
* page is for. Shares the renderer, the count and the length limit, because
|
|
15267
|
+
* two rows of prompt chips that behaved differently would be the harder
|
|
15268
|
+
* thing to explain.
|
|
15269
|
+
*
|
|
15270
|
+
* Read once at connect: it is content for a state the widget is in before
|
|
15271
|
+
* anything happens, and a host that wants it to change has `slot="empty"`.
|
|
15272
|
+
*/
|
|
15273
|
+
#starterChips() {
|
|
15274
|
+
const raw = this.getAttribute("data-starters");
|
|
15275
|
+
if (raw === null) {
|
|
15276
|
+
return null;
|
|
15277
|
+
}
|
|
15278
|
+
let parsed;
|
|
15279
|
+
try {
|
|
15280
|
+
parsed = JSON.parse(raw);
|
|
15281
|
+
} catch {
|
|
15282
|
+
console.warn(
|
|
15283
|
+
`<ag-ui-chat>: data-starters is not valid JSON, so no starters are shown. It takes an array of strings, e.g. data-starters='["Summarise this page"]'.`
|
|
15284
|
+
);
|
|
15285
|
+
return null;
|
|
15286
|
+
}
|
|
15287
|
+
return renderSuggestionChips({ prompts: parsed }, this.#strings, (prompt) => {
|
|
15288
|
+
void this.sendMessage(prompt);
|
|
15289
|
+
});
|
|
15290
|
+
}
|
|
13849
15291
|
#updateEmptyState() {
|
|
13850
15292
|
this.#emptyWrap.hidden = this.#messages.childElementCount > 1;
|
|
13851
15293
|
}
|
|
@@ -13854,6 +15296,7 @@ ${quoted}`;
|
|
|
13854
15296
|
this.#skillsMenu.onInput(this.#input.value);
|
|
13855
15297
|
this.#skillHint.hidden = true;
|
|
13856
15298
|
this.#autoGrow();
|
|
15299
|
+
this.#recallIndex = null;
|
|
13857
15300
|
}
|
|
13858
15301
|
#onKeydown(event) {
|
|
13859
15302
|
if (this.#skillsMenu.onKeydown(event)) {
|
|
@@ -13868,7 +15311,44 @@ ${quoted}`;
|
|
|
13868
15311
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
13869
15312
|
event.preventDefault();
|
|
13870
15313
|
void this.#submit();
|
|
15314
|
+
return;
|
|
15315
|
+
}
|
|
15316
|
+
this.#recallHistory(event);
|
|
15317
|
+
}
|
|
15318
|
+
/**
|
|
15319
|
+
* Walk back through what the user has already sent, on the arrow keys.
|
|
15320
|
+
*
|
|
15321
|
+
* Only from an empty composer, and only with the palette closed -- which the
|
|
15322
|
+
* caller has already established, since the palette consumes arrows while it
|
|
15323
|
+
* is open. Both conditions matter: arrows inside text are how you move the
|
|
15324
|
+
* caret, and taking them would break editing to add a shortcut.
|
|
15325
|
+
*
|
|
15326
|
+
* The drafts are the user's own turns in this conversation, newest first,
|
|
15327
|
+
* which is what every shell and every coding agent means by this. Arrowing
|
|
15328
|
+
* forward past the newest empties the composer again rather than sticking on
|
|
15329
|
+
* it, so the way out is the same key that got you in.
|
|
15330
|
+
*/
|
|
15331
|
+
#recallHistory(event) {
|
|
15332
|
+
const back = event.key === "ArrowUp";
|
|
15333
|
+
if (!back && event.key !== "ArrowDown" || this.#skillsMenu.isOpen()) {
|
|
15334
|
+
return;
|
|
15335
|
+
}
|
|
15336
|
+
const drafts = this.#sentDrafts;
|
|
15337
|
+
if (drafts.length === 0) {
|
|
15338
|
+
return;
|
|
15339
|
+
}
|
|
15340
|
+
if (this.#recallIndex === null && (!back || this.#input.value !== "")) {
|
|
15341
|
+
return;
|
|
15342
|
+
}
|
|
15343
|
+
const next = this.#recallIndex === null ? 0 : this.#recallIndex + (back ? 1 : -1);
|
|
15344
|
+
if (next >= drafts.length) {
|
|
15345
|
+
return;
|
|
13871
15346
|
}
|
|
15347
|
+
event.preventDefault();
|
|
15348
|
+
this.#recallIndex = next < 0 ? null : next;
|
|
15349
|
+
this.#input.value = next < 0 ? "" : drafts[next];
|
|
15350
|
+
this.#input.setSelectionRange(this.#input.value.length, this.#input.value.length);
|
|
15351
|
+
this.#autoGrow();
|
|
13872
15352
|
}
|
|
13873
15353
|
/**
|
|
13874
15354
|
* Stop the in-flight run: decline any confirmation card awaiting a decision
|
|
@@ -13877,6 +15357,13 @@ ${quoted}`;
|
|
|
13877
15357
|
* observes the disconnect).
|
|
13878
15358
|
*/
|
|
13879
15359
|
#cancelRun() {
|
|
15360
|
+
for (const text4 of this.#queued) {
|
|
15361
|
+
if (this.#sentDrafts[0] !== text4) {
|
|
15362
|
+
this.#sentDrafts.unshift(text4);
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
15365
|
+
this.#queued.length = 0;
|
|
15366
|
+
this.#renderQueued();
|
|
13880
15367
|
this.#confirmAbort?.abort();
|
|
13881
15368
|
this.#client?.cancel();
|
|
13882
15369
|
}
|
|
@@ -13888,11 +15375,54 @@ ${quoted}`;
|
|
|
13888
15375
|
* that it carries no text.
|
|
13889
15376
|
*/
|
|
13890
15377
|
#setRunning(running) {
|
|
15378
|
+
const settled = this.#running && !running;
|
|
13891
15379
|
this.#running = running;
|
|
13892
15380
|
const label = running ? this.#strings.stop : this.#strings.send;
|
|
13893
15381
|
this.#send.title = label;
|
|
13894
15382
|
this.#send.setAttribute("aria-label", label);
|
|
13895
15383
|
this.#send.dataset["state"] = running ? "running" : "idle";
|
|
15384
|
+
if (settled) {
|
|
15385
|
+
this.#flushQueued();
|
|
15386
|
+
}
|
|
15387
|
+
}
|
|
15388
|
+
/**
|
|
15389
|
+
* Send the next message that was typed while the run was going.
|
|
15390
|
+
*
|
|
15391
|
+
* One at a time, through the same path as anything else: each queued turn
|
|
15392
|
+
* starts a run of its own, and the next is sent when *that* one settles. Any
|
|
15393
|
+
* other shape would be a second sender racing the guard above.
|
|
15394
|
+
*/
|
|
15395
|
+
#flushQueued() {
|
|
15396
|
+
const next = this.#queued.shift();
|
|
15397
|
+
this.#renderQueued();
|
|
15398
|
+
if (next !== void 0) {
|
|
15399
|
+
void this.sendMessage(next);
|
|
15400
|
+
}
|
|
15401
|
+
}
|
|
15402
|
+
/**
|
|
15403
|
+
* Draw what is waiting, as chips that can be taken back.
|
|
15404
|
+
*
|
|
15405
|
+
* Visible and removable, because a message the user typed and cannot see is
|
|
15406
|
+
* a message they will type again -- and one they changed their mind about
|
|
15407
|
+
* has to be retractable before it is sent on their behalf.
|
|
15408
|
+
*/
|
|
15409
|
+
#renderQueued() {
|
|
15410
|
+
this.#queuedRow.replaceChildren();
|
|
15411
|
+
this.#queuedRow.hidden = this.#queued.length === 0;
|
|
15412
|
+
for (const [index, text4] of this.#queued.entries()) {
|
|
15413
|
+
const chip = document.createElement("button");
|
|
15414
|
+
chip.type = "button";
|
|
15415
|
+
chip.className = "queued-chip";
|
|
15416
|
+
chip.setAttribute("part", "queued-chip");
|
|
15417
|
+
chip.textContent = text4;
|
|
15418
|
+
chip.title = this.#strings.removeQueued.replace("{text}", text4);
|
|
15419
|
+
chip.setAttribute("aria-label", chip.title);
|
|
15420
|
+
chip.addEventListener("click", () => {
|
|
15421
|
+
this.#queued.splice(index, 1);
|
|
15422
|
+
this.#renderQueued();
|
|
15423
|
+
});
|
|
15424
|
+
this.#queuedRow.appendChild(chip);
|
|
15425
|
+
}
|
|
13896
15426
|
}
|
|
13897
15427
|
/**
|
|
13898
15428
|
* Size the field to its content: one row when empty, growing with what is
|
|
@@ -13907,14 +15437,24 @@ ${quoted}`;
|
|
|
13907
15437
|
this.#input.style.height = `${this.#input.scrollHeight}px`;
|
|
13908
15438
|
}
|
|
13909
15439
|
async #submit() {
|
|
13910
|
-
if (this.#running) {
|
|
13911
|
-
return;
|
|
13912
|
-
}
|
|
13913
15440
|
const content = this.#input.value.trim();
|
|
13914
15441
|
const attachments = this.#attachTray?.readyRefs() ?? [];
|
|
13915
15442
|
if (content === "" && attachments.length === 0) {
|
|
13916
15443
|
return;
|
|
13917
15444
|
}
|
|
15445
|
+
if (this.#running) {
|
|
15446
|
+
if (content !== "") {
|
|
15447
|
+
this.#queued.push(content);
|
|
15448
|
+
this.#renderQueued();
|
|
15449
|
+
this.#input.value = "";
|
|
15450
|
+
this.#autoGrow();
|
|
15451
|
+
}
|
|
15452
|
+
return;
|
|
15453
|
+
}
|
|
15454
|
+
if (content !== "" && this.#sentDrafts[0] !== content) {
|
|
15455
|
+
this.#sentDrafts.unshift(content);
|
|
15456
|
+
}
|
|
15457
|
+
this.#recallIndex = null;
|
|
13918
15458
|
this.#input.value = "";
|
|
13919
15459
|
this.#autoGrow();
|
|
13920
15460
|
if (this.#attachTray?.hasPending() === true) {
|
|
@@ -14700,11 +16240,33 @@ ${quoted}`;
|
|
|
14700
16240
|
this.#appendNotice("\u2728", this.#strings.usingSkill.replace("{name}", skill), "skill");
|
|
14701
16241
|
return true;
|
|
14702
16242
|
}
|
|
14703
|
-
|
|
14704
|
-
|
|
16243
|
+
/**
|
|
16244
|
+
* An inline notice about something the run did.
|
|
16245
|
+
*
|
|
16246
|
+
* `undo` is offered only where the agent rearranged the user's own window --
|
|
16247
|
+
* see {@link renderRunNotice} for why a notice may carry that one control and
|
|
16248
|
+
* nothing else.
|
|
16249
|
+
*/
|
|
16250
|
+
#appendNotice(icon, text4, kind, undo) {
|
|
16251
|
+
this.#ensureGroup().appendChild(renderRunNotice(icon, text4, kind, undo));
|
|
14705
16252
|
this.#updateEmptyState();
|
|
14706
16253
|
this.#scroller.follow();
|
|
14707
16254
|
}
|
|
16255
|
+
/**
|
|
16256
|
+
* Say that the agent rearranged the user's window, and offer the way back.
|
|
16257
|
+
*
|
|
16258
|
+
* Only on the agent's path. A host calling {@link moveTo} is arranging its
|
|
16259
|
+
* own page and does not need telling what it just did; an agent doing it
|
|
16260
|
+
* mid-conversation is the case where a panel appears to move on its own.
|
|
16261
|
+
*/
|
|
16262
|
+
#announceSurfaceChange(text4, undo) {
|
|
16263
|
+
this.#appendNotice(
|
|
16264
|
+
"\u2922",
|
|
16265
|
+
text4,
|
|
16266
|
+
"surface",
|
|
16267
|
+
undo === null ? void 0 : { label: this.#strings.undo, onActivate: undo }
|
|
16268
|
+
);
|
|
16269
|
+
}
|
|
14708
16270
|
/**
|
|
14709
16271
|
* Turn on chart rendering, by whichever route this consumer wants.
|
|
14710
16272
|
*
|
|
@@ -14977,13 +16539,14 @@ function setControlValue(el2, value) {
|
|
|
14977
16539
|
}
|
|
14978
16540
|
|
|
14979
16541
|
// src/version.ts
|
|
14980
|
-
var VERSION = "0.
|
|
16542
|
+
var VERSION = "0.35.1";
|
|
14981
16543
|
export {
|
|
14982
16544
|
ATTACHMENT_EVENT,
|
|
14983
16545
|
AgUiChat,
|
|
14984
16546
|
AgUiClient,
|
|
14985
16547
|
CHART_ACTIVITY_TYPE,
|
|
14986
16548
|
CHART_TOOL_NAME,
|
|
16549
|
+
CHAT_CORNERS,
|
|
14987
16550
|
COMPACTION_ACTIVITY_TYPE,
|
|
14988
16551
|
CUSTOM_AGENT_EVENT,
|
|
14989
16552
|
CheckpointMenu,
|
|
@@ -15024,6 +16587,7 @@ export {
|
|
|
15024
16587
|
attachQuoteOffer,
|
|
15025
16588
|
chartSpecFrom,
|
|
15026
16589
|
clickElement,
|
|
16590
|
+
createChatSurfaceTools,
|
|
15027
16591
|
createHttpAgent,
|
|
15028
16592
|
createPageActionTools,
|
|
15029
16593
|
createPageMapContext,
|
|
@@ -15035,6 +16599,7 @@ export {
|
|
|
15035
16599
|
flash,
|
|
15036
16600
|
focusWithFlash,
|
|
15037
16601
|
highlightThenClick,
|
|
16602
|
+
isChatCorner,
|
|
15038
16603
|
isDestructive,
|
|
15039
16604
|
isNavigates,
|
|
15040
16605
|
mergeUiStrings,
|
|
@@ -15059,6 +16624,7 @@ export {
|
|
|
15059
16624
|
setControlValue,
|
|
15060
16625
|
setNativeChecked,
|
|
15061
16626
|
setNativeValue,
|
|
16627
|
+
showHighlightOverlay,
|
|
15062
16628
|
suggestionPrompts,
|
|
15063
16629
|
toggleCheckbox,
|
|
15064
16630
|
toggleControl,
|