@oharness/server 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/i18n.js +356 -62
- package/dist/i18n.js.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/page/client/account.js +430 -23
- package/dist/page/client/account.js.map +1 -1
- package/dist/page/client/base.js +20 -1
- package/dist/page/client/base.js.map +1 -1
- package/dist/page/client/composer.js +709 -58
- package/dist/page/client/composer.js.map +1 -1
- package/dist/page/client/computer.d.ts +27 -0
- package/dist/page/client/computer.js +358 -0
- package/dist/page/client/computer.js.map +1 -0
- package/dist/page/client/dialogs.js +93 -6
- package/dist/page/client/dialogs.js.map +1 -1
- package/dist/page/client/events.js +94 -7
- package/dist/page/client/events.js.map +1 -1
- package/dist/page/client/files.js +86 -1
- package/dist/page/client/files.js.map +1 -1
- package/dist/page/client/index.js +4 -0
- package/dist/page/client/index.js.map +1 -1
- package/dist/page/client/panels.js +12 -6
- package/dist/page/client/panels.js.map +1 -1
- package/dist/page/client/routines.js +296 -41
- package/dist/page/client/routines.js.map +1 -1
- package/dist/page/client/shell.js +229 -51
- package/dist/page/client/shell.js.map +1 -1
- package/dist/page/client/state.js +273 -19
- package/dist/page/client/state.js.map +1 -1
- package/dist/page/client/tasks.d.ts +8 -0
- package/dist/page/client/tasks.js +293 -0
- package/dist/page/client/tasks.js.map +1 -0
- package/dist/page/client/transcript.js +174 -6
- package/dist/page/client/transcript.js.map +1 -1
- package/dist/page/client/usage.js +1 -11
- package/dist/page/client/usage.js.map +1 -1
- package/dist/page/style.js +706 -104
- package/dist/page/style.js.map +1 -1
- package/dist/page.js +161 -54
- package/dist/page.js.map +1 -1
- package/dist/prefs.d.ts +14 -0
- package/dist/prefs.js +8 -1
- package/dist/prefs.js.map +1 -1
- package/dist/remote-crypto.d.ts +78 -0
- package/dist/remote-crypto.js +216 -0
- package/dist/remote-crypto.js.map +1 -0
- package/dist/remote.d.ts +103 -0
- package/dist/remote.js +498 -0
- package/dist/remote.js.map +1 -0
- package/dist/server.d.ts +70 -0
- package/dist/server.js +1291 -49
- package/dist/server.js.map +1 -1
- package/dist/starters.d.ts +92 -0
- package/dist/starters.js +213 -0
- package/dist/starters.js.map +1 -0
- package/dist/theme.js +5 -0
- package/dist/theme.js.map +1 -1
- package/dist/turn-shots.d.ts +27 -0
- package/dist/turn-shots.js +71 -0
- package/dist/turn-shots.js.map +1 -0
- package/package.json +2 -2
|
@@ -8,6 +8,9 @@ export const SHELL = String.raw `
|
|
|
8
8
|
|
|
9
9
|
const projectBtn = document.getElementById("project");
|
|
10
10
|
let projects = [];
|
|
11
|
+
/** Session directories that are worktrees, each naming the project it came from. */
|
|
12
|
+
let worktrees = [];
|
|
13
|
+
const worktreeAt = (cwd) => worktrees.find((one) => one.path === cwd);
|
|
11
14
|
|
|
12
15
|
/** The last path segment, which is what the directory is called. */
|
|
13
16
|
function projectName(dir) {
|
|
@@ -25,6 +28,7 @@ function paintProject() {
|
|
|
25
28
|
// it too, or it goes on advertising the tree the user just left.
|
|
26
29
|
const cwdEl = document.getElementById("empty-cwd");
|
|
27
30
|
if (cwdEl && active) cwdEl.textContent = active.name;
|
|
31
|
+
if (active) void paintStarters(active.path);
|
|
28
32
|
// A directory added while the chip was open belongs on its list.
|
|
29
33
|
refreshWorkspace();
|
|
30
34
|
}
|
|
@@ -50,6 +54,7 @@ function repaintPanel() {
|
|
|
50
54
|
async function loadProjects() {
|
|
51
55
|
const body = await getJson("/projects");
|
|
52
56
|
projects = body.projects || [];
|
|
57
|
+
worktrees = body.worktrees || [];
|
|
53
58
|
projectOrder = body.order || [];
|
|
54
59
|
paintProject();
|
|
55
60
|
// A project's pin and its place decide how the session list is grouped, so
|
|
@@ -294,6 +299,12 @@ const IS_WIN = /win/i.test(navigator.platform || navigator.userAgent || "");
|
|
|
294
299
|
/** The rows the current query left on screen, in the order they are drawn. */
|
|
295
300
|
let paletteRows = [];
|
|
296
301
|
let paletteCursor = 0;
|
|
302
|
+
/**
|
|
303
|
+
* Whether the palette was opened from the sidebar's "Search sessions": then it
|
|
304
|
+
* lists sessions and nothing else, and every one of them rather than the
|
|
305
|
+
* recent eight — the question asked was "where is that conversation".
|
|
306
|
+
*/
|
|
307
|
+
let paletteSessionsOnly = false;
|
|
297
308
|
|
|
298
309
|
/** A nav item's own icon, so the palette names things the way the sidebar does. */
|
|
299
310
|
function navIcon(id) {
|
|
@@ -315,6 +326,7 @@ const enLabel = (key) => (STRINGS.en && STRINGS.en[key]) || "";
|
|
|
315
326
|
function paletteItems() {
|
|
316
327
|
const items = [];
|
|
317
328
|
const query = paletteField.value.trim();
|
|
329
|
+
if (paletteSessionsOnly) return sessionItems(sessionRows);
|
|
318
330
|
const actions = t("palette.actions");
|
|
319
331
|
const here = (projects.find((project) => project.current) || {}).path || currentCwd;
|
|
320
332
|
|
|
@@ -342,6 +354,7 @@ function paletteItems() {
|
|
|
342
354
|
for (const [id, key] of [
|
|
343
355
|
["nav-artifacts", "nav.artifacts"],
|
|
344
356
|
["nav-sessions", "nav.sessions"],
|
|
357
|
+
["nav-usage", "nav.usage"],
|
|
345
358
|
["nav-models", "nav.models"],
|
|
346
359
|
["nav-routines", "nav.routines"],
|
|
347
360
|
["nav-settings", "nav.settings"],
|
|
@@ -359,21 +372,7 @@ function paletteItems() {
|
|
|
359
372
|
// Unfiltered, the list is a preview of the most recent work and the actions
|
|
360
373
|
// above it still have to be visible. Once there is a query, every session in
|
|
361
374
|
// every tree is fair game — that is what the field is for.
|
|
362
|
-
|
|
363
|
-
for (const session of query ? sessionRows : sessionRows.slice(0, 8)) {
|
|
364
|
-
items.push({
|
|
365
|
-
kind: "session",
|
|
366
|
-
group: sessions,
|
|
367
|
-
icon: () => svgIcon("chat"),
|
|
368
|
-
label: session.title || t("session.untitled"),
|
|
369
|
-
sub: [projectName(session.cwd || ""), ago(session.updatedAt)].filter(Boolean).join(" · "),
|
|
370
|
-
keywords: session.cwd || "",
|
|
371
|
-
run: () => {
|
|
372
|
-
if (session.current) return void setNav(null);
|
|
373
|
-
void openSession(session.id, session.cwd);
|
|
374
|
-
},
|
|
375
|
-
});
|
|
376
|
-
}
|
|
375
|
+
items.push(...sessionItems(query ? sessionRows : sessionRows.slice(0, 8)));
|
|
377
376
|
|
|
378
377
|
const named = t("palette.projects");
|
|
379
378
|
for (const project of query ? projects : projects.slice(0, 5)) {
|
|
@@ -394,6 +393,23 @@ function paletteItems() {
|
|
|
394
393
|
return items;
|
|
395
394
|
}
|
|
396
395
|
|
|
396
|
+
/** Palette rows for sessions, each reopening its own transcript. */
|
|
397
|
+
function sessionItems(rows) {
|
|
398
|
+
const group = t("palette.sessions");
|
|
399
|
+
return rows.map((session) => ({
|
|
400
|
+
kind: "session",
|
|
401
|
+
group,
|
|
402
|
+
icon: () => svgIcon("chat"),
|
|
403
|
+
label: session.title || t("session.untitled"),
|
|
404
|
+
sub: [projectName(session.cwd || ""), ago(session.updatedAt)].filter(Boolean).join(" · "),
|
|
405
|
+
keywords: session.cwd || "",
|
|
406
|
+
run: () => {
|
|
407
|
+
if (session.current) return void setNav(null);
|
|
408
|
+
void openSession(session.id, session.cwd);
|
|
409
|
+
},
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
|
|
397
413
|
/** Same word-by-word match the pickers use, over the row's hidden aliases too. */
|
|
398
414
|
const paletteMatch = (item, query) =>
|
|
399
415
|
matchesQuery(
|
|
@@ -454,12 +470,14 @@ function movePalette(step) {
|
|
|
454
470
|
if (active) active.scrollIntoView({ block: "nearest" });
|
|
455
471
|
}
|
|
456
472
|
|
|
457
|
-
function openPalette() {
|
|
473
|
+
function openPalette(sessionsOnly = false) {
|
|
458
474
|
// One modal at a time. A permission request is a question the agent is
|
|
459
475
|
// blocked on; a palette over it would answer neither.
|
|
460
476
|
if (document.querySelector("dialog[open]")) return;
|
|
461
477
|
closeAttachMenu();
|
|
462
478
|
closeRowMenu();
|
|
479
|
+
paletteSessionsOnly = sessionsOnly === true;
|
|
480
|
+
paletteField.placeholder = t(paletteSessionsOnly ? "palette.sessionsPlaceholder" : "palette.placeholder");
|
|
463
481
|
paletteField.value = "";
|
|
464
482
|
paletteCursor = 0;
|
|
465
483
|
paintPalette();
|
|
@@ -471,7 +489,7 @@ function openPalette() {
|
|
|
471
489
|
// The desktop shell reaches the palette through this: a menu accelerator is
|
|
472
490
|
// handled before the page ever sees the keystroke, so the menu item calls the
|
|
473
491
|
// same function the key would have.
|
|
474
|
-
window.openCommandPalette = openPalette;
|
|
492
|
+
window.openCommandPalette = () => openPalette();
|
|
475
493
|
|
|
476
494
|
paletteField.addEventListener("input", () => {
|
|
477
495
|
paletteCursor = 0;
|
|
@@ -520,7 +538,8 @@ document.addEventListener("keydown", (e) => {
|
|
|
520
538
|
openPalette();
|
|
521
539
|
});
|
|
522
540
|
|
|
523
|
-
document.getElementById("nav-search").addEventListener("click", openPalette);
|
|
541
|
+
document.getElementById("nav-search").addEventListener("click", () => openPalette());
|
|
542
|
+
document.getElementById("side-search").addEventListener("click", () => openPalette(true));
|
|
524
543
|
// The chord as this machine actually spells it, in the one place a mouse user
|
|
525
544
|
// will see it.
|
|
526
545
|
document.getElementById("palette-hint").textContent = IS_MAC ? "⌘K" : "Ctrl K";
|
|
@@ -540,43 +559,151 @@ function applyStrings() {
|
|
|
540
559
|
for (const node of document.querySelectorAll("[data-t-aria-label]")) {
|
|
541
560
|
node.setAttribute("aria-label", ts(node.dataset.tAriaLabel));
|
|
542
561
|
}
|
|
562
|
+
// The rail shows icons only, so each names itself to a screen reader in the
|
|
563
|
+
// words its hidden label carries; the hover label below reads the same.
|
|
564
|
+
for (const item of document.querySelectorAll("#rail .nav-item")) {
|
|
565
|
+
const label = item.querySelector("[data-t]");
|
|
566
|
+
if (label) item.setAttribute("aria-label", label.textContent);
|
|
567
|
+
}
|
|
543
568
|
document.documentElement.lang = LOCALE;
|
|
544
569
|
}
|
|
545
570
|
|
|
571
|
+
/*
|
|
572
|
+
* The rail's names, beside the icon the pointer is on.
|
|
573
|
+
*
|
|
574
|
+
* Not the title attribute: the browser shows that after a second's pause, in
|
|
575
|
+
* its own style, and an icon that only says what it is if you wait is one
|
|
576
|
+
* people click to find out. One label for the whole rail, placed against the
|
|
577
|
+
* button, because the rail clips anything that tries to hang out of it.
|
|
578
|
+
*/
|
|
579
|
+
const railTip = document.createElement("div");
|
|
580
|
+
railTip.className = "rail-tip";
|
|
581
|
+
railTip.setAttribute("aria-hidden", "true");
|
|
582
|
+
document.body.appendChild(railTip);
|
|
583
|
+
function showRailTip(item) {
|
|
584
|
+
const name = item.getAttribute("aria-label");
|
|
585
|
+
if (!name) return;
|
|
586
|
+
const box = item.getBoundingClientRect();
|
|
587
|
+
railTip.textContent = name;
|
|
588
|
+
railTip.style.left = box.right + 8 + "px";
|
|
589
|
+
railTip.style.top = box.top + box.height / 2 + "px";
|
|
590
|
+
railTip.classList.add("on");
|
|
591
|
+
}
|
|
592
|
+
const hideRailTip = () => railTip.classList.remove("on");
|
|
593
|
+
const rail = document.getElementById("rail");
|
|
594
|
+
rail.addEventListener("mouseover", (event) => {
|
|
595
|
+
const item = event.target.closest(".nav-item");
|
|
596
|
+
if (item) showRailTip(item);
|
|
597
|
+
else hideRailTip();
|
|
598
|
+
});
|
|
599
|
+
rail.addEventListener("mouseleave", hideRailTip);
|
|
600
|
+
rail.addEventListener("focusin", (event) => {
|
|
601
|
+
const item = event.target.closest(".nav-item");
|
|
602
|
+
if (item && item.matches(":focus-visible")) showRailTip(item);
|
|
603
|
+
});
|
|
604
|
+
rail.addEventListener("focusout", hideRailTip);
|
|
605
|
+
rail.addEventListener("click", hideRailTip);
|
|
606
|
+
|
|
546
607
|
/**
|
|
547
|
-
* What the empty state offers to do
|
|
608
|
+
* What the empty state offers to do.
|
|
548
609
|
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
* "…prompt" string; the label is what fits on
|
|
554
|
-
* a chip, the prompt is the sentence the chip actually sends.
|
|
610
|
+
* The server decides which kind: a project with no memory gets a rotating pool
|
|
611
|
+
* of everyday offers, and one the agent has notes on gets three written from
|
|
612
|
+
* those notes (see ../../starters.ts). Each chip's label is what fits on it,
|
|
613
|
+
* and the sentence it actually sends rides along in data-p.
|
|
555
614
|
*/
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
615
|
+
const FIXED_STARTERS = ["trip", "email", "research", "savings", "japanTrip"];
|
|
616
|
+
const VISIBLE_STARTERS = 3;
|
|
617
|
+
const STARTER_ROTATION_MS = 6000;
|
|
618
|
+
/** Which project the chips on screen were drawn for, and the newest ask. */
|
|
619
|
+
let startersFor = "";
|
|
620
|
+
let startersAsk = 0;
|
|
621
|
+
let starterOffset = 0;
|
|
622
|
+
let starterTimer;
|
|
623
|
+
|
|
624
|
+
function starterChip(label, prompt) {
|
|
625
|
+
const button = el("button", "", label);
|
|
626
|
+
button.type = "button";
|
|
627
|
+
button.dataset.p = prompt;
|
|
628
|
+
// The label is a chip; the title is the sentence it will actually send.
|
|
629
|
+
button.title = prompt;
|
|
630
|
+
return button;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function animateStarters(box) {
|
|
634
|
+
box.classList.remove("fresh");
|
|
635
|
+
void box.offsetWidth;
|
|
636
|
+
box.classList.add("fresh");
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function stopStarterRotation() {
|
|
640
|
+
clearInterval(starterTimer);
|
|
641
|
+
starterTimer = undefined;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function drawFixedStarters(box, animate = false) {
|
|
645
|
+
const ids = Array.from({ length: VISIBLE_STARTERS }, (_, index) =>
|
|
646
|
+
FIXED_STARTERS[(starterOffset + index) % FIXED_STARTERS.length]);
|
|
647
|
+
box.replaceChildren(...ids.map((id) => starterChip(t("starter." + id), t("starter." + id + ".prompt"))));
|
|
648
|
+
if (animate) animateStarters(box);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function startStarterRotation(box) {
|
|
652
|
+
stopStarterRotation();
|
|
653
|
+
if (FIXED_STARTERS.length <= VISIBLE_STARTERS) return;
|
|
654
|
+
starterTimer = setInterval(() => {
|
|
655
|
+
// Do not move a choice out from under a mouse pointer or keyboard focus.
|
|
656
|
+
if (!box.offsetParent || box.matches(":hover") || box.matches(":focus-within")) return;
|
|
657
|
+
starterOffset = (starterOffset + 1) % FIXED_STARTERS.length;
|
|
658
|
+
drawFixedStarters(box, true);
|
|
659
|
+
}, STARTER_ROTATION_MS);
|
|
660
|
+
}
|
|
561
661
|
|
|
562
662
|
/**
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
663
|
+
* Ask for the project's chips and draw them. Skipped when the project on
|
|
664
|
+
* screen has not changed, so a repaint of the project button does not pay for
|
|
665
|
+
* another round trip. The answer never waits on a model: a project whose set
|
|
666
|
+
* is not written yet gets the fixed three now, and the written set arrives
|
|
667
|
+
* later as a "starters" event (see startersArrived).
|
|
566
668
|
*/
|
|
567
|
-
function paintStarters() {
|
|
669
|
+
async function paintStarters(cwd) {
|
|
568
670
|
const box = document.getElementById("starters");
|
|
569
|
-
if (!box) return;
|
|
671
|
+
if (!box || !cwd || cwd === startersFor) return;
|
|
672
|
+
stopStarterRotation();
|
|
673
|
+
startersFor = cwd;
|
|
674
|
+
starterOffset = 0;
|
|
675
|
+
const ask = ++startersAsk;
|
|
570
676
|
box.replaceChildren();
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
// The label is a chip; the title is the sentence it will actually send.
|
|
577
|
-
button.title = button.dataset.p;
|
|
578
|
-
box.appendChild(button);
|
|
677
|
+
let body;
|
|
678
|
+
try {
|
|
679
|
+
body = await getJson("/starters?cwd=" + encodeURIComponent(cwd) + "&locale=" + encodeURIComponent(LOCALE));
|
|
680
|
+
} catch {
|
|
681
|
+
body = { kind: "fixed" };
|
|
579
682
|
}
|
|
683
|
+
// A switch made while this was being written owns the box now.
|
|
684
|
+
if (ask !== startersAsk) return;
|
|
685
|
+
if (body && body.kind === "written" && Array.isArray(body.starters)) {
|
|
686
|
+
box.replaceChildren(...body.starters.map((item) => starterChip(item.label, item.prompt)));
|
|
687
|
+
} else {
|
|
688
|
+
drawFixedStarters(box);
|
|
689
|
+
startStarterRotation(box);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* A set written in the background for a project, pushed when it lands.
|
|
695
|
+
*
|
|
696
|
+
* Swapped in only where it still fits: the same project and language on
|
|
697
|
+
* screen, and the empty state still showing — nobody's chip is replaced while
|
|
698
|
+
* they are reading a conversation, and the next visit reads it from disk.
|
|
699
|
+
*/
|
|
700
|
+
function startersArrived(event) {
|
|
701
|
+
const box = document.getElementById("starters");
|
|
702
|
+
if (!box || !event || event.cwd !== startersFor || event.locale !== LOCALE || !Array.isArray(event.starters)) return;
|
|
703
|
+
if (!box.offsetParent) return;
|
|
704
|
+
stopStarterRotation();
|
|
705
|
+
box.replaceChildren(...event.starters.map((item) => starterChip(item.label, item.prompt)));
|
|
706
|
+
animateStarters(box);
|
|
580
707
|
}
|
|
581
708
|
|
|
582
709
|
/**
|
|
@@ -628,9 +755,52 @@ if (IS_DESKTOP && window.oharness.updates) {
|
|
|
628
755
|
window.oharness.updates.state().then(paintUpdateHint, () => {});
|
|
629
756
|
}
|
|
630
757
|
|
|
758
|
+
/**
|
|
759
|
+
* A prompt from a link on the website — oharness://new?prompt=… — opened as a
|
|
760
|
+
* new session with the text in the composer, and nothing more.
|
|
761
|
+
*
|
|
762
|
+
* The main process checked and cleaned the text (desktop/deep-link.js), but
|
|
763
|
+
* it came from whatever page the person was on, so this page treats it as
|
|
764
|
+
* someone else's suggestion: it is never sent, no permission is changed and
|
|
765
|
+
* no card is answered. It sits in the box until the person reads it and
|
|
766
|
+
* presses send, or deletes it. A new session rather than the open one, so the
|
|
767
|
+
* suggestion never lands in the middle of a conversation as if it belonged
|
|
768
|
+
* there; and a draft already in the box is only replaced when the person says
|
|
769
|
+
* so, because the box is shared by every tab and the draft may be the only
|
|
770
|
+
* copy of what they were writing.
|
|
771
|
+
*
|
|
772
|
+
* Not before boot has finished: switching sessions while the first one is
|
|
773
|
+
* still being opened leaves the two fighting over the page. Boot takes the
|
|
774
|
+
* one that launched the app at the end; the nudge covers every later click.
|
|
775
|
+
*/
|
|
776
|
+
let deepLinkReady = false;
|
|
777
|
+
|
|
778
|
+
async function takeDeepLink() {
|
|
779
|
+
if (!deepLinkReady || !IS_DESKTOP || !window.oharness.deepLink) return;
|
|
780
|
+
let text;
|
|
781
|
+
try {
|
|
782
|
+
text = await window.oharness.deepLink.take();
|
|
783
|
+
} catch {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (typeof text !== "string" || !text) return;
|
|
787
|
+
const draft = input.value.trim();
|
|
788
|
+
if (draft && draft !== text && !confirm(t("deepLink.replaceDraft"))) return;
|
|
789
|
+
await switchTo({ action: "new" });
|
|
790
|
+
input.value = text;
|
|
791
|
+
// Called rather than an input event, which would also open the command
|
|
792
|
+
// menu for a prompt that starts with "/" — one Enter from running it.
|
|
793
|
+
autosize();
|
|
794
|
+
paintMirror();
|
|
795
|
+
input.focus();
|
|
796
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
797
|
+
}
|
|
798
|
+
if (IS_DESKTOP && window.oharness.deepLink) {
|
|
799
|
+
window.oharness.deepLink.onArrived(() => void takeDeepLink());
|
|
800
|
+
}
|
|
801
|
+
|
|
631
802
|
void (async () => {
|
|
632
803
|
applyStrings();
|
|
633
|
-
paintStarters();
|
|
634
804
|
// The language is here because it is the one thing that decides what to
|
|
635
805
|
// translate next, and the shell because a desktop window and a browser tab
|
|
636
806
|
// are different products to the person using them.
|
|
@@ -685,18 +855,26 @@ void (async () => {
|
|
|
685
855
|
await loadSidePanels();
|
|
686
856
|
await loadProjects();
|
|
687
857
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
858
|
+
// Wired here, after the session has loaded: a chip that submits before the
|
|
859
|
+
// session it would submit to exists is worse than one that waits. Delegated,
|
|
860
|
+
// because the chips are redrawn on every project switch. Absent when the tab
|
|
861
|
+
// opened on a conversation: its first entry took the invitation off the
|
|
862
|
+
// page, and a throw here stopped everything after it, the session list too.
|
|
863
|
+
document.getElementById("starters")?.addEventListener("click", (event) => {
|
|
864
|
+
const button = event.target.closest("#starters button");
|
|
865
|
+
if (!button) return;
|
|
866
|
+
stopStarterRotation();
|
|
867
|
+
input.value = button.dataset.p;
|
|
868
|
+
paintMirror();
|
|
869
|
+
document.getElementById("composer").requestSubmit();
|
|
870
|
+
});
|
|
695
871
|
await loadSessions();
|
|
696
872
|
await refreshRoutineCount();
|
|
697
873
|
await refreshPluginPages();
|
|
698
874
|
await refreshModelCount();
|
|
699
875
|
await loadAccount();
|
|
700
876
|
input.focus();
|
|
877
|
+
deepLinkReady = true;
|
|
878
|
+
await takeDeepLink();
|
|
701
879
|
})();`;
|
|
702
880
|
//# sourceMappingURL=shell.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../../src/page/client/shell.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../../src/page/client/shell.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAy2BzB,CAAC"}
|