@volter-ai-dev/supercode-ui 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/components.d.ts +2 -0
- package/components.mjs +200 -108
- package/composer.mjs +18 -17
- package/conversation.d.ts +2 -0
- package/conversation.mjs +136 -44
- package/embed.mjs +198 -108
- package/index.d.ts +2 -0
- package/messenger.mjs +198 -108
- package/package.json +1 -1
- package/sessions.mjs +14 -13
- package/styles.css +2 -1
package/README.md
CHANGED
|
@@ -132,6 +132,11 @@ inventing fallback prompt text. The composer bounds images to four supported bro
|
|
|
132
132
|
messages, retry, edit, and new-chat recovery. The host still chooses how explicit file selection
|
|
133
133
|
is exposed and must revalidate every returned item.
|
|
134
134
|
|
|
135
|
+
Transcript images with a browser-safe URL open in a keyboard-accessible, viewport-bounded viewer;
|
|
136
|
+
remote images expose copy-link and open-original actions, while local data images can be downloaded.
|
|
137
|
+
If a host deliberately omits a large historical data URL from its bounded projection, the UI shows
|
|
138
|
+
an honest unavailable-preview attachment instead of a broken thumbnail or a deceptive action.
|
|
139
|
+
|
|
135
140
|
An embedding product such as Vibewaiting should therefore be small: Lucarne owns its iframe and
|
|
136
141
|
launcher lifecycle, Supercode owns this UI and the controller semantics, and Vibewaiting only
|
|
137
142
|
bridges state/intents plus host-specific theme and persistence policy.
|
package/components.d.ts
CHANGED
package/components.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/composer.jsx
|
|
2
|
-
import { useEffect, useRef, useState } from "preact/hooks";
|
|
2
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "preact/hooks";
|
|
3
3
|
|
|
4
4
|
// core.mjs
|
|
5
5
|
var HARNESS_NAMES = Object.freeze({
|
|
@@ -815,7 +815,8 @@ function UiIcon({ name, size = 16, class: className = "" }) {
|
|
|
815
815
|
}
|
|
816
816
|
|
|
817
817
|
// src/context.jsx
|
|
818
|
-
import {
|
|
818
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
819
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
819
820
|
var MAX_CONTEXT_ITEMS = 32;
|
|
820
821
|
var MAX_IMAGE_ITEMS = 4;
|
|
821
822
|
var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
@@ -914,12 +915,101 @@ function ImageTray({ items, onRemove }) {
|
|
|
914
915
|
onRemove ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove image ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) }) : null
|
|
915
916
|
] }, item.id ?? `${item.label}:${index}`)) });
|
|
916
917
|
}
|
|
917
|
-
function
|
|
918
|
+
function imageFilename(label) {
|
|
919
|
+
const value = label.trim().replace(/[\\/:*?"<>|]+/g, "-");
|
|
920
|
+
return value || "image";
|
|
921
|
+
}
|
|
922
|
+
function ImageViewer({ items, index, adapter, onChange, onClose }) {
|
|
923
|
+
const dialog = useRef(null);
|
|
924
|
+
const reset = useRef(null);
|
|
925
|
+
const [copyState, setCopyState2] = useState("idle");
|
|
926
|
+
const item = items[index];
|
|
927
|
+
const remote = item?.url?.startsWith("http://") || item?.url?.startsWith("https://");
|
|
928
|
+
useEffect(() => {
|
|
929
|
+
if (!dialog.current?.open) dialog.current?.showModal();
|
|
930
|
+
return () => clearTimeout(reset.current);
|
|
931
|
+
}, []);
|
|
932
|
+
useEffect(() => {
|
|
933
|
+
clearTimeout(reset.current);
|
|
934
|
+
setCopyState2("idle");
|
|
935
|
+
}, [index]);
|
|
936
|
+
if (!item?.url) return null;
|
|
937
|
+
const move = (amount) => onChange((index + amount + items.length) % items.length);
|
|
938
|
+
const copy = async () => {
|
|
939
|
+
try {
|
|
940
|
+
await adapter.copyText(item.url);
|
|
941
|
+
setCopyState2("copied");
|
|
942
|
+
} catch {
|
|
943
|
+
setCopyState2("failed");
|
|
944
|
+
}
|
|
945
|
+
clearTimeout(reset.current);
|
|
946
|
+
reset.current = setTimeout(() => setCopyState2("idle"), 1500);
|
|
947
|
+
};
|
|
948
|
+
const close = () => dialog.current?.close();
|
|
949
|
+
return /* @__PURE__ */ jsxs2(
|
|
950
|
+
"dialog",
|
|
951
|
+
{
|
|
952
|
+
ref: dialog,
|
|
953
|
+
class: "scui-image-viewer",
|
|
954
|
+
"aria-label": `Image preview: ${item.label}`,
|
|
955
|
+
onClose,
|
|
956
|
+
onClick: (event) => {
|
|
957
|
+
if (event.target === event.currentTarget) close();
|
|
958
|
+
},
|
|
959
|
+
onKeyDown: (event) => {
|
|
960
|
+
if (items.length < 2 || !["ArrowLeft", "ArrowRight"].includes(event.key)) return;
|
|
961
|
+
event.preventDefault();
|
|
962
|
+
move(event.key === "ArrowLeft" ? -1 : 1);
|
|
963
|
+
},
|
|
964
|
+
children: [
|
|
965
|
+
/* @__PURE__ */ jsxs2("header", { children: [
|
|
966
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
967
|
+
/* @__PURE__ */ jsx2("strong", { children: item.label }),
|
|
968
|
+
items.length > 1 ? /* @__PURE__ */ jsxs2("small", { children: [
|
|
969
|
+
index + 1,
|
|
970
|
+
" of ",
|
|
971
|
+
items.length
|
|
972
|
+
] }) : null
|
|
973
|
+
] }),
|
|
974
|
+
/* @__PURE__ */ jsxs2("nav", { "aria-label": "Image actions", children: [
|
|
975
|
+
remote && adapter?.copyText ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": copyState === "copied" ? "Image link copied" : copyState === "failed" ? "Could not copy image link" : "Copy image link", title: "Copy image link", "data-status": copyState, onClick: copy, children: /* @__PURE__ */ jsx2(UiIcon, { name: copyState === "copied" ? "check" : "copy", size: 16 }) }) : null,
|
|
976
|
+
remote ? /* @__PURE__ */ jsx2("a", { href: item.url, target: "_blank", rel: "noreferrer", "aria-label": "Open original image", title: "Open original image", children: /* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 16 }) }) : /* @__PURE__ */ jsx2("a", { href: item.url, download: imageFilename(item.label), "aria-label": "Download image", title: "Download image", children: /* @__PURE__ */ jsx2(UiIcon, { name: "down", size: 16 }) }),
|
|
977
|
+
/* @__PURE__ */ jsx2("button", { type: "button", "aria-label": "Close image preview", title: "Close", onClick: close, children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 17 }) })
|
|
978
|
+
] })
|
|
979
|
+
] }),
|
|
980
|
+
/* @__PURE__ */ jsxs2("figure", { children: [
|
|
981
|
+
/* @__PURE__ */ jsx2("img", { src: item.url, alt: item.label }),
|
|
982
|
+
items.length > 1 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
983
|
+
/* @__PURE__ */ jsx2("button", { type: "button", class: "scui-image-previous", "aria-label": "Previous image", onClick: () => move(-1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) }),
|
|
984
|
+
/* @__PURE__ */ jsx2("button", { type: "button", class: "scui-image-next", "aria-label": "Next image", onClick: () => move(1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) })
|
|
985
|
+
] }) : null
|
|
986
|
+
] })
|
|
987
|
+
]
|
|
988
|
+
}
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
function MessageImages({ items, adapter }) {
|
|
992
|
+
const [active, setActive] = useState(null);
|
|
993
|
+
const opener = useRef(null);
|
|
918
994
|
if (!items?.length) return null;
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
995
|
+
const viewable = items.filter((item) => item.url);
|
|
996
|
+
const close = () => {
|
|
997
|
+
setActive(null);
|
|
998
|
+
requestAnimationFrame(() => opener.current?.focus({ preventScroll: true }));
|
|
999
|
+
};
|
|
1000
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1001
|
+
/* @__PURE__ */ jsx2("div", { class: "scui-message-images", "aria-label": "Message images", children: items.map((item, index) => item.url ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `View image ${item.label}`, onClick: (event) => {
|
|
1002
|
+
opener.current = event.currentTarget;
|
|
1003
|
+
setActive(viewable.indexOf(item));
|
|
1004
|
+
}, children: /* @__PURE__ */ jsx2("img", { src: item.url, alt: "" }) }, item.id ?? `${item.label}:${index}`) : /* @__PURE__ */ jsxs2("span", { "data-unavailable": "true", children: [
|
|
1005
|
+
/* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 14 }),
|
|
1006
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
1007
|
+
/* @__PURE__ */ jsx2("strong", { children: item.label }),
|
|
1008
|
+
/* @__PURE__ */ jsx2("small", { children: "Preview unavailable" })
|
|
1009
|
+
] })
|
|
1010
|
+
] }, item.id ?? `${item.label}:${index}`)) }),
|
|
1011
|
+
active !== null && viewable[active] ? /* @__PURE__ */ jsx2(ImageViewer, { items: viewable, index: active, adapter, onChange: setActive, onClose: close }) : null
|
|
1012
|
+
] });
|
|
923
1013
|
}
|
|
924
1014
|
|
|
925
1015
|
// src/textarea.js
|
|
@@ -961,18 +1051,18 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
961
1051
|
}
|
|
962
1052
|
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
|
|
963
1053
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
964
|
-
const [draft, setDraft] =
|
|
965
|
-
const [context, setContext] =
|
|
966
|
-
const [images, setImages] =
|
|
967
|
-
const [queue, setQueue] =
|
|
968
|
-
const [dispatching, setDispatching] =
|
|
969
|
-
const [picking, setPicking] =
|
|
970
|
-
const [dragging, setDragging] =
|
|
971
|
-
const [pickerError, setPickerError] =
|
|
972
|
-
const textarea =
|
|
1054
|
+
const [draft, setDraft] = useState2(remembered.draft);
|
|
1055
|
+
const [context, setContext] = useState2(remembered.context ?? []);
|
|
1056
|
+
const [images, setImages] = useState2(remembered.images ?? []);
|
|
1057
|
+
const [queue, setQueue] = useState2((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
|
|
1058
|
+
const [dispatching, setDispatching] = useState2(false);
|
|
1059
|
+
const [picking, setPicking] = useState2(false);
|
|
1060
|
+
const [dragging, setDragging] = useState2(false);
|
|
1061
|
+
const [pickerError, setPickerError] = useState2(null);
|
|
1062
|
+
const textarea = useRef2(null);
|
|
973
1063
|
useAutosizeTextarea(textarea, draft);
|
|
974
1064
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
975
|
-
|
|
1065
|
+
useEffect2(() => {
|
|
976
1066
|
remember(draft, context, images, queue);
|
|
977
1067
|
}, [draft, context, images, memoryKey, queue]);
|
|
978
1068
|
const updateQueue = (update) => setQueue((items) => {
|
|
@@ -982,7 +1072,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
982
1072
|
});
|
|
983
1073
|
const queueBlocked = state.busy || pendingStatus !== null || dispatching;
|
|
984
1074
|
const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
|
|
985
|
-
|
|
1075
|
+
useEffect2(() => {
|
|
986
1076
|
if (!queueBlocked && state.canSend && queue.length) {
|
|
987
1077
|
const [next, ...rest] = queue;
|
|
988
1078
|
setDispatching(true);
|
|
@@ -992,13 +1082,13 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
992
1082
|
adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {}, ...next.images.length ? { images: next.images } : {} });
|
|
993
1083
|
}
|
|
994
1084
|
}, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
|
|
995
|
-
|
|
1085
|
+
useEffect2(() => {
|
|
996
1086
|
if (pendingStatus !== null || state.busy) setDispatching(false);
|
|
997
1087
|
}, [pendingStatus, state.busy]);
|
|
998
|
-
|
|
1088
|
+
useEffect2(() => {
|
|
999
1089
|
textarea.current?.focus({ preventScroll: true });
|
|
1000
1090
|
}, [memoryKey]);
|
|
1001
|
-
|
|
1091
|
+
useEffect2(() => {
|
|
1002
1092
|
if (!restoreDraft) return;
|
|
1003
1093
|
setDraft(restoreDraft.text);
|
|
1004
1094
|
const restoredContext = normalizeContext(restoreDraft.context);
|
|
@@ -1009,7 +1099,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1009
1099
|
textarea.current?.focus({ preventScroll: true });
|
|
1010
1100
|
onDraftRestored?.(restoreDraft.id);
|
|
1011
1101
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
1012
|
-
|
|
1102
|
+
useEffect2(() => {
|
|
1013
1103
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
1014
1104
|
return () => clearTimeout(timer);
|
|
1015
1105
|
}, [adapter, draft]);
|
|
@@ -1134,12 +1224,12 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1134
1224
|
}
|
|
1135
1225
|
|
|
1136
1226
|
// src/conversation.jsx
|
|
1137
|
-
import { Fragment as
|
|
1138
|
-
import { useEffect as
|
|
1227
|
+
import { Fragment as Fragment3 } from "preact";
|
|
1228
|
+
import { useEffect as useEffect4, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState3 } from "preact/hooks";
|
|
1139
1229
|
|
|
1140
1230
|
// src/markdown.jsx
|
|
1141
1231
|
import MarkdownIt from "markdown-it";
|
|
1142
|
-
import { useEffect as
|
|
1232
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "preact/hooks";
|
|
1143
1233
|
import { jsx as jsx4 } from "preact/jsx-runtime";
|
|
1144
1234
|
var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
|
|
1145
1235
|
var LANGUAGE_LABELS = {
|
|
@@ -1201,8 +1291,8 @@ function setCopyState(button, status) {
|
|
|
1201
1291
|
}
|
|
1202
1292
|
function Markdown({ value, copyText }) {
|
|
1203
1293
|
const html = useMemo(() => markdown.render(value), [value]);
|
|
1204
|
-
const resets =
|
|
1205
|
-
|
|
1294
|
+
const resets = useRef3(/* @__PURE__ */ new Map());
|
|
1295
|
+
useEffect3(() => () => {
|
|
1206
1296
|
for (const timer of resets.current.values()) clearTimeout(timer);
|
|
1207
1297
|
resets.current.clear();
|
|
1208
1298
|
}, []);
|
|
@@ -1231,7 +1321,7 @@ function Markdown({ value, copyText }) {
|
|
|
1231
1321
|
}
|
|
1232
1322
|
|
|
1233
1323
|
// src/conversation.jsx
|
|
1234
|
-
import { Fragment as
|
|
1324
|
+
import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
1235
1325
|
function LoadingStatus({ state, compact = false }) {
|
|
1236
1326
|
const copy = {
|
|
1237
1327
|
connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
|
|
@@ -1280,9 +1370,9 @@ function ContextDisclosure({ context }) {
|
|
|
1280
1370
|
] });
|
|
1281
1371
|
}
|
|
1282
1372
|
function MessageMeta({ entry, adapter }) {
|
|
1283
|
-
const [copyState, setCopyState2] =
|
|
1284
|
-
const reset =
|
|
1285
|
-
|
|
1373
|
+
const [copyState, setCopyState2] = useState3("idle");
|
|
1374
|
+
const reset = useRef4(null);
|
|
1375
|
+
useEffect4(() => () => clearTimeout(reset.current), []);
|
|
1286
1376
|
const date = entry.ts === null ? null : new Date(entry.ts);
|
|
1287
1377
|
const validDate = date && Number.isFinite(date.valueOf()) ? date : null;
|
|
1288
1378
|
if (!validDate && (!adapter?.copyText || !entry.text)) return null;
|
|
@@ -1303,33 +1393,33 @@ function MessageMeta({ entry, adapter }) {
|
|
|
1303
1393
|
] });
|
|
1304
1394
|
}
|
|
1305
1395
|
var TOOL_ICONS = {
|
|
1306
|
-
read: () => /* @__PURE__ */ jsxs4(
|
|
1396
|
+
read: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1307
1397
|
/* @__PURE__ */ jsx5("path", { d: "M5 2.75h7.25L15 5.5v7.75A1.75 1.75 0 0 1 13.25 15h-8.5A1.75 1.75 0 0 1 3 13.25v-8.5A2 2 0 0 1 5 2.75Z" }),
|
|
1308
1398
|
/* @__PURE__ */ jsx5("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
|
|
1309
1399
|
] }),
|
|
1310
|
-
search: () => /* @__PURE__ */ jsxs4(
|
|
1400
|
+
search: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1311
1401
|
/* @__PURE__ */ jsx5("circle", { cx: "8", cy: "8", r: "4.5" }),
|
|
1312
1402
|
/* @__PURE__ */ jsx5("path", { d: "m11.5 11.5 3 3" })
|
|
1313
1403
|
] }),
|
|
1314
|
-
edit: () => /* @__PURE__ */ jsxs4(
|
|
1404
|
+
edit: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1315
1405
|
/* @__PURE__ */ jsx5("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
|
|
1316
1406
|
/* @__PURE__ */ jsx5("path", { d: "m10 5 3 3" })
|
|
1317
1407
|
] }),
|
|
1318
|
-
command: () => /* @__PURE__ */ jsx5(
|
|
1319
|
-
test: () => /* @__PURE__ */ jsxs4(
|
|
1408
|
+
command: () => /* @__PURE__ */ jsx5(Fragment4, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
|
|
1409
|
+
test: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1320
1410
|
/* @__PURE__ */ jsx5("path", { d: "M6 2.5v3L3 12a2 2 0 0 0 1.8 3h8.4a2 2 0 0 0 1.8-3l-3-6.5v-3M5 9h8" }),
|
|
1321
1411
|
/* @__PURE__ */ jsx5("path", { d: "M5 2.5h8" })
|
|
1322
1412
|
] }),
|
|
1323
|
-
web: () => /* @__PURE__ */ jsxs4(
|
|
1413
|
+
web: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1324
1414
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "6.5" }),
|
|
1325
1415
|
/* @__PURE__ */ jsx5("path", { d: "M2.75 9h12.5M9 2.5c2 1.8 3 4 3 6.5s-1 4.7-3 6.5c-2-1.8-3-4-3-6.5s1-4.7 3-6.5Z" })
|
|
1326
1416
|
] }),
|
|
1327
|
-
agent: () => /* @__PURE__ */ jsxs4(
|
|
1417
|
+
agent: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1328
1418
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "6", r: "2.5" }),
|
|
1329
1419
|
/* @__PURE__ */ jsx5("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
|
|
1330
1420
|
] }),
|
|
1331
|
-
plan: () => /* @__PURE__ */ jsx5(
|
|
1332
|
-
other: () => /* @__PURE__ */ jsxs4(
|
|
1421
|
+
plan: () => /* @__PURE__ */ jsx5(Fragment4, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
|
|
1422
|
+
other: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1333
1423
|
/* @__PURE__ */ jsx5("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
|
|
1334
1424
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "3.5" })
|
|
1335
1425
|
] })
|
|
@@ -1395,9 +1485,9 @@ function ToolMetrics({ presentation }) {
|
|
|
1395
1485
|
}
|
|
1396
1486
|
function PendingElapsed({ now }) {
|
|
1397
1487
|
const clock = now ?? Date.now;
|
|
1398
|
-
const started =
|
|
1399
|
-
const [elapsed, setElapsed] =
|
|
1400
|
-
|
|
1488
|
+
const started = useRef4(clock());
|
|
1489
|
+
const [elapsed, setElapsed] = useState3(0);
|
|
1490
|
+
useEffect4(() => {
|
|
1401
1491
|
const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
|
|
1402
1492
|
return () => clearInterval(timer);
|
|
1403
1493
|
}, [clock]);
|
|
@@ -1438,7 +1528,7 @@ function SearchPreview({ presentation }) {
|
|
|
1438
1528
|
] }) : null,
|
|
1439
1529
|
lines.length ? /* @__PURE__ */ jsx5("ol", { children: lines.map((line, index) => {
|
|
1440
1530
|
const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
|
|
1441
|
-
return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(
|
|
1531
|
+
return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1442
1532
|
/* @__PURE__ */ jsx5("code", { children: match[1] }),
|
|
1443
1533
|
/* @__PURE__ */ jsxs4("small", { children: [
|
|
1444
1534
|
match[2],
|
|
@@ -1471,9 +1561,9 @@ function ToolPreview({ presentation, entry }) {
|
|
|
1471
1561
|
return presentation.preview ? /* @__PURE__ */ jsx5("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
|
|
1472
1562
|
}
|
|
1473
1563
|
function ToolActions({ presentation, adapter }) {
|
|
1474
|
-
const [copied, setCopied] =
|
|
1475
|
-
const reset =
|
|
1476
|
-
|
|
1564
|
+
const [copied, setCopied] = useState3(false);
|
|
1565
|
+
const reset = useRef4(null);
|
|
1566
|
+
useEffect4(() => () => clearTimeout(reset.current), []);
|
|
1477
1567
|
if (!adapter?.copyText) return null;
|
|
1478
1568
|
const action = presentation.command ? ["Copy command", presentation.command] : presentation.path ? ["Copy path", presentation.path] : presentation.url ? ["Copy URL", presentation.url] : presentation.query ? ["Copy query", presentation.query] : null;
|
|
1479
1569
|
const copy = async () => {
|
|
@@ -1520,7 +1610,7 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
1520
1610
|
}
|
|
1521
1611
|
if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx5("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
|
|
1522
1612
|
return /* @__PURE__ */ jsxs4("article", { class: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
|
|
1523
|
-
/* @__PURE__ */ jsx5(MessageImages, { items: entry.images }),
|
|
1613
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: entry.images, adapter }),
|
|
1524
1614
|
/* @__PURE__ */ jsx5(Markdown, { value: entry.text, copyText: adapter?.copyText }),
|
|
1525
1615
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: entry.context }),
|
|
1526
1616
|
entry.truncated ? /* @__PURE__ */ jsx5("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
|
|
@@ -1529,14 +1619,14 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
1529
1619
|
}
|
|
1530
1620
|
function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
1531
1621
|
const presentation = entry.presentation ?? createToolPresentation(entry);
|
|
1532
|
-
const [expanded, setExpanded] =
|
|
1533
|
-
|
|
1622
|
+
const [expanded, setExpanded] = useState3(open || entry.status === "pending");
|
|
1623
|
+
useEffect4(() => {
|
|
1534
1624
|
if (entry.status === "pending") setExpanded(true);
|
|
1535
1625
|
}, [entry.status]);
|
|
1536
1626
|
const target = compactToolTarget(presentation.target, workspace);
|
|
1537
1627
|
const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
|
|
1538
1628
|
const category = presentation.category ?? toolCategory(entry);
|
|
1539
|
-
const summary = /* @__PURE__ */ jsxs4(
|
|
1629
|
+
const summary = /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1540
1630
|
/* @__PURE__ */ jsx5(ToolIcon, { category }),
|
|
1541
1631
|
/* @__PURE__ */ jsx5("strong", { children: toolAction2(entry, category, presentation) }),
|
|
1542
1632
|
target ? /* @__PURE__ */ jsx5("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
|
|
@@ -1564,9 +1654,9 @@ function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
|
1564
1654
|
function ActivityGroup({ entries, state, adapter }) {
|
|
1565
1655
|
if (entries.length === 1) return /* @__PURE__ */ jsx5("section", { class: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx5(ToolRow, { entry: entries[0], workspace: state.workspace, adapter }) });
|
|
1566
1656
|
const active = entries.some((entry) => entry.status === "pending");
|
|
1567
|
-
const [open, setOpen] =
|
|
1657
|
+
const [open, setOpen] = useState3(active);
|
|
1568
1658
|
const id = useId();
|
|
1569
|
-
|
|
1659
|
+
useEffect4(() => {
|
|
1570
1660
|
if (active) setOpen(true);
|
|
1571
1661
|
}, [active]);
|
|
1572
1662
|
return /* @__PURE__ */ jsxs4("section", { class: "scui-activity", children: [
|
|
@@ -1642,9 +1732,9 @@ function SessionDetails({ semantics }) {
|
|
|
1642
1732
|
}
|
|
1643
1733
|
var conversationMemory = /* @__PURE__ */ new Map();
|
|
1644
1734
|
function ConversationAnnouncements({ state }) {
|
|
1645
|
-
const previousBusy =
|
|
1646
|
-
const [announcement, setAnnouncement] =
|
|
1647
|
-
|
|
1735
|
+
const previousBusy = useRef4(state.busy);
|
|
1736
|
+
const [announcement, setAnnouncement] = useState3("");
|
|
1737
|
+
useEffect4(() => {
|
|
1648
1738
|
if (previousBusy.current && !state.busy && !state.error) {
|
|
1649
1739
|
setAnnouncement(`${harnessDisplayName(state.harness) || "Coding agent"} finished working`);
|
|
1650
1740
|
}
|
|
@@ -1653,13 +1743,13 @@ function ConversationAnnouncements({ state }) {
|
|
|
1653
1743
|
return /* @__PURE__ */ jsx5("span", { class: "scui-sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement });
|
|
1654
1744
|
}
|
|
1655
1745
|
function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null, unreadAfterMessages = null }) {
|
|
1656
|
-
const scroller =
|
|
1746
|
+
const scroller = useRef4(null);
|
|
1657
1747
|
const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
|
|
1658
|
-
const [atBottom, setAtBottom] =
|
|
1659
|
-
const earlierAnchor =
|
|
1660
|
-
const restored =
|
|
1748
|
+
const [atBottom, setAtBottom] = useState3(remembered.atBottom);
|
|
1749
|
+
const earlierAnchor = useRef4(null);
|
|
1750
|
+
const restored = useRef4(false);
|
|
1661
1751
|
const blocks = groupConversation(state.transcript);
|
|
1662
|
-
const unreadBoundary =
|
|
1752
|
+
const unreadBoundary = useRef4(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
|
|
1663
1753
|
const unreadBlock = unreadBoundary.current === null ? -1 : blocks.findIndex((block) => {
|
|
1664
1754
|
const entries = block.kind === "activity" ? block.entries : [block.entry];
|
|
1665
1755
|
return entries.some((entry) => Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > unreadBoundary.current);
|
|
@@ -1718,12 +1808,12 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1718
1808
|
}, children: "Load earlier messages" }) : null,
|
|
1719
1809
|
!blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx5(LoadingStatus, { state }) : null,
|
|
1720
1810
|
!blocks.length && state.startup === "ready" ? Empty ? /* @__PURE__ */ jsx5(Empty, { state, adapter, value: null }) : /* @__PURE__ */ jsx5("div", { class: "scui-empty", children: state.error ?? (state.harness ? `${harnessDisplayName(state.harness)} is listening. Say something.` : "No transcript yet.") }) : null,
|
|
1721
|
-
blocks.map((block, index) => /* @__PURE__ */ jsxs4(
|
|
1811
|
+
blocks.map((block, index) => /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1722
1812
|
index === unreadBlock ? /* @__PURE__ */ jsx5("div", { class: "scui-unread-divider", role: "separator", "aria-label": "New messages", children: /* @__PURE__ */ jsx5("span", { children: "New" }) }) : null,
|
|
1723
1813
|
block.kind === "activity" ? /* @__PURE__ */ jsx5(Group, { value: block.entries, entries: block.entries, state, adapter }) : /* @__PURE__ */ jsx5(Entry, { value: block.entry, entry: block.entry, state, adapter })
|
|
1724
1814
|
] }, block.id)),
|
|
1725
1815
|
pendingMessage ? /* @__PURE__ */ jsxs4("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
|
|
1726
|
-
/* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images }),
|
|
1816
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images, adapter }),
|
|
1727
1817
|
/* @__PURE__ */ jsx5(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
|
|
1728
1818
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: pendingMessage.context }),
|
|
1729
1819
|
/* @__PURE__ */ jsxs4("footer", { children: [
|
|
@@ -1754,7 +1844,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1754
1844
|
}
|
|
1755
1845
|
|
|
1756
1846
|
// src/logo.jsx
|
|
1757
|
-
import { useEffect as
|
|
1847
|
+
import { useEffect as useEffect5 } from "preact/hooks";
|
|
1758
1848
|
import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
1759
1849
|
var LOGOS = {
|
|
1760
1850
|
"claude-code": {
|
|
@@ -1805,7 +1895,7 @@ function hasHarnessLogo(id) {
|
|
|
1805
1895
|
}
|
|
1806
1896
|
function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
1807
1897
|
const logo = LOGOS[id];
|
|
1808
|
-
|
|
1898
|
+
useEffect5(() => {
|
|
1809
1899
|
if (!logo) onMissingLogo?.(id);
|
|
1810
1900
|
}, [id, logo, onMissingLogo]);
|
|
1811
1901
|
if (!logo) return null;
|
|
@@ -1816,10 +1906,10 @@ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
|
1816
1906
|
}
|
|
1817
1907
|
|
|
1818
1908
|
// src/messenger.jsx
|
|
1819
|
-
import { useEffect as
|
|
1909
|
+
import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "preact/hooks";
|
|
1820
1910
|
|
|
1821
1911
|
// src/sessions.jsx
|
|
1822
|
-
import { useEffect as
|
|
1912
|
+
import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef5, useState as useState4 } from "preact/hooks";
|
|
1823
1913
|
import { jsx as jsx7, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
1824
1914
|
var sessionListMemory = /* @__PURE__ */ new Map();
|
|
1825
1915
|
function SessionRow({ row, state, onOpen }) {
|
|
@@ -1841,21 +1931,21 @@ function SessionRow({ row, state, onOpen }) {
|
|
|
1841
1931
|
}
|
|
1842
1932
|
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
|
|
1843
1933
|
const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
|
|
1844
|
-
const [query, setQuery] =
|
|
1845
|
-
const [loadingMore, setLoadingMore] =
|
|
1846
|
-
const root =
|
|
1847
|
-
const rowScroller =
|
|
1934
|
+
const [query, setQuery] = useState4(remembered.query);
|
|
1935
|
+
const [loadingMore, setLoadingMore] = useState4(false);
|
|
1936
|
+
const root = useRef5(null);
|
|
1937
|
+
const rowScroller = useRef5(null);
|
|
1848
1938
|
const rows = filterSessions(state.sessions, query);
|
|
1849
1939
|
const Row = components.SessionRow ?? SessionRow;
|
|
1850
1940
|
useLayoutEffect3(() => {
|
|
1851
1941
|
if (rowScroller.current) rowScroller.current.scrollTop = remembered.top;
|
|
1852
1942
|
}, [memoryKey]);
|
|
1853
|
-
|
|
1943
|
+
useEffect6(() => {
|
|
1854
1944
|
if (!focusKey || !root.current) return;
|
|
1855
1945
|
const target = focusKey === "@new" ? root.current.querySelector('[data-list-focus="new"]') : [...root.current.querySelectorAll("[data-session-key]")].find((element) => element.dataset.sessionKey === focusKey);
|
|
1856
1946
|
(target ?? root.current.querySelector("input,button"))?.focus({ preventScroll: true });
|
|
1857
1947
|
}, [focusKey, rows.length]);
|
|
1858
|
-
|
|
1948
|
+
useEffect6(() => {
|
|
1859
1949
|
if (loadingMore) setLoadingMore(false);
|
|
1860
1950
|
}, [state.error, state.history.hasMoreSessions, state.sessions.length]);
|
|
1861
1951
|
const loadMore = () => {
|
|
@@ -1941,10 +2031,10 @@ function Receipt({ state, adapter }) {
|
|
|
1941
2031
|
return null;
|
|
1942
2032
|
}
|
|
1943
2033
|
function ConversationActions({ state, adapter, actionPending }) {
|
|
1944
|
-
const [open, setOpen] =
|
|
1945
|
-
const root =
|
|
1946
|
-
const panel =
|
|
1947
|
-
const trigger =
|
|
2034
|
+
const [open, setOpen] = useState5(false);
|
|
2035
|
+
const root = useRef6(null);
|
|
2036
|
+
const panel = useRef6(null);
|
|
2037
|
+
const trigger = useRef6(null);
|
|
1948
2038
|
const menuId = useId2();
|
|
1949
2039
|
const targets = state.harnesses.filter((item) => item.startable);
|
|
1950
2040
|
const groups = [
|
|
@@ -1965,7 +2055,7 @@ function ConversationActions({ state, adapter, actionPending }) {
|
|
|
1965
2055
|
items: state.canBranch ? targets.map((target) => ({ key: `branch:${target.id}`, label: target.id === state.harness ? `Fork in ${target.label}` : `Continue with ${target.label}`, intent: { action: "branch", targetHarness: target.id } })) : []
|
|
1966
2056
|
}
|
|
1967
2057
|
].filter((group) => group.items.length);
|
|
1968
|
-
|
|
2058
|
+
useEffect7(() => {
|
|
1969
2059
|
if (!open) return;
|
|
1970
2060
|
panel.current?.querySelector("button:not(:disabled)")?.focus({ preventScroll: true });
|
|
1971
2061
|
const dismiss = (event) => {
|
|
@@ -2015,12 +2105,12 @@ function ConversationActions({ state, adapter, actionPending }) {
|
|
|
2015
2105
|
] });
|
|
2016
2106
|
}
|
|
2017
2107
|
function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose }) {
|
|
2018
|
-
const back =
|
|
2108
|
+
const back = useRef6(null);
|
|
2019
2109
|
const harness = state.attached?.harness ?? state.harness;
|
|
2020
2110
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
2021
2111
|
const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.mode === "mirror" ? "Read-only" : "Ready";
|
|
2022
2112
|
const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce;
|
|
2023
|
-
|
|
2113
|
+
useEffect7(() => {
|
|
2024
2114
|
if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
|
|
2025
2115
|
}, []);
|
|
2026
2116
|
return /* @__PURE__ */ jsxs7("header", { class: "scui-head scui-chat-head", children: [
|
|
@@ -2042,24 +2132,24 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2042
2132
|
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
|
|
2043
2133
|
const Header = slots.header;
|
|
2044
2134
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
2045
|
-
const [pending, setPendingState] =
|
|
2046
|
-
const [pendingAction, setPendingAction] =
|
|
2047
|
-
const [restoreDraft, setRestoreDraft] =
|
|
2048
|
-
const restoreSequence =
|
|
2049
|
-
const actionSequence =
|
|
2050
|
-
const acknowledged =
|
|
2135
|
+
const [pending, setPendingState] = useState5(() => pendingMessageMemory.get(memoryKey) ?? null);
|
|
2136
|
+
const [pendingAction, setPendingAction] = useState5(null);
|
|
2137
|
+
const [restoreDraft, setRestoreDraft] = useState5(null);
|
|
2138
|
+
const restoreSequence = useRef6(0);
|
|
2139
|
+
const actionSequence = useRef6(0);
|
|
2140
|
+
const acknowledged = useRef6(/* @__PURE__ */ new Set());
|
|
2051
2141
|
const setPending = (update) => setPendingState((current) => {
|
|
2052
2142
|
const next = typeof update === "function" ? update(current) : update;
|
|
2053
2143
|
if (next) boundedSet(pendingMessageMemory, memoryKey, next);
|
|
2054
2144
|
else pendingMessageMemory.delete(memoryKey);
|
|
2055
2145
|
return next;
|
|
2056
2146
|
});
|
|
2057
|
-
|
|
2147
|
+
useEffect7(() => {
|
|
2058
2148
|
setPendingState(pendingMessageMemory.get(memoryKey) ?? null);
|
|
2059
2149
|
setPendingAction(null);
|
|
2060
2150
|
setRestoreDraft(null);
|
|
2061
2151
|
}, [memoryKey]);
|
|
2062
|
-
|
|
2152
|
+
useEffect7(() => {
|
|
2063
2153
|
if (!pending) return;
|
|
2064
2154
|
if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
|
|
2065
2155
|
setPending(null);
|
|
@@ -2088,7 +2178,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2088
2178
|
setRestoreDraft({ id: restoreSequence.current, text: pending.text, context: pending.context, images: pending.images });
|
|
2089
2179
|
setPending({ ...pending, status: "editing" });
|
|
2090
2180
|
};
|
|
2091
|
-
|
|
2181
|
+
useEffect7(() => {
|
|
2092
2182
|
const key = state.attached?.key;
|
|
2093
2183
|
if (!key || !state.attention.some((item) => item.key === key)) {
|
|
2094
2184
|
if (key) acknowledged.current.delete(key);
|
|
@@ -2123,7 +2213,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2123
2213
|
return result;
|
|
2124
2214
|
}
|
|
2125
2215
|
}), [adapter, state.error]);
|
|
2126
|
-
|
|
2216
|
+
useEffect7(() => {
|
|
2127
2217
|
if (!pendingAction) return;
|
|
2128
2218
|
if (state.operation && !pendingAction.seenOperation) {
|
|
2129
2219
|
setPendingAction({ ...pendingAction, seenOperation: true });
|
|
@@ -2156,24 +2246,24 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2156
2246
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2157
2247
|
const startableKey = startable.map((item) => item.id).join("\0");
|
|
2158
2248
|
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
|
|
2159
|
-
const [harness, setHarness] =
|
|
2160
|
-
const [draft, setDraft] =
|
|
2161
|
-
const [context, setContext] =
|
|
2162
|
-
const [images, setImages] =
|
|
2163
|
-
const [starting, setStarting] =
|
|
2164
|
-
const [picking, setPicking] =
|
|
2165
|
-
const [dragging, setDragging] =
|
|
2166
|
-
const [pickerError, setPickerError] =
|
|
2167
|
-
const startSequence =
|
|
2168
|
-
const textarea =
|
|
2249
|
+
const [harness, setHarness] = useState5(remembered.harness);
|
|
2250
|
+
const [draft, setDraft] = useState5(remembered.draft);
|
|
2251
|
+
const [context, setContext] = useState5(remembered.context);
|
|
2252
|
+
const [images, setImages] = useState5(remembered.images ?? []);
|
|
2253
|
+
const [starting, setStarting] = useState5(null);
|
|
2254
|
+
const [picking, setPicking] = useState5(false);
|
|
2255
|
+
const [dragging, setDragging] = useState5(false);
|
|
2256
|
+
const [pickerError, setPickerError] = useState5(null);
|
|
2257
|
+
const startSequence = useRef6(0);
|
|
2258
|
+
const textarea = useRef6(null);
|
|
2169
2259
|
useAutosizeTextarea(textarea, draft);
|
|
2170
|
-
|
|
2260
|
+
useEffect7(() => {
|
|
2171
2261
|
if (startable.some((item) => item.id === harness)) return;
|
|
2172
2262
|
const next = startable[0]?.id ?? "";
|
|
2173
2263
|
setHarness(next);
|
|
2174
2264
|
boundedSet(newChatMemory, memoryKey, { harness: next, draft, context, images });
|
|
2175
2265
|
}, [harness, startableKey]);
|
|
2176
|
-
|
|
2266
|
+
useEffect7(() => {
|
|
2177
2267
|
if (!starting) return;
|
|
2178
2268
|
const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
|
|
2179
2269
|
const beganWorking = !starting.busy && state.busy;
|
|
@@ -2181,10 +2271,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2181
2271
|
newChatMemory.delete(memoryKey);
|
|
2182
2272
|
onStarted();
|
|
2183
2273
|
}, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
|
|
2184
|
-
|
|
2274
|
+
useEffect7(() => {
|
|
2185
2275
|
if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
|
|
2186
2276
|
}, [starting, state.busy, state.error, state.operation]);
|
|
2187
|
-
|
|
2277
|
+
useEffect7(() => {
|
|
2188
2278
|
textarea.current?.focus({ preventScroll: true });
|
|
2189
2279
|
}, []);
|
|
2190
2280
|
const pickContext = () => {
|
|
@@ -2318,14 +2408,14 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2318
2408
|
const state = useMemo2(() => normalizeUiState(stateInput), [stateInput]);
|
|
2319
2409
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
2320
2410
|
const memoryKey = state.workspace || "@default";
|
|
2321
|
-
const [view, setViewState] =
|
|
2322
|
-
const [opening, setOpening] =
|
|
2323
|
-
const [listFocus, setListFocus] =
|
|
2411
|
+
const [view, setViewState] = useState5(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
|
|
2412
|
+
const [opening, setOpening] = useState5(null);
|
|
2413
|
+
const [listFocus, setListFocus] = useState5(null);
|
|
2324
2414
|
const setView = (next) => {
|
|
2325
2415
|
boundedSet(messengerViewMemory, memoryKey, next);
|
|
2326
2416
|
setViewState(next);
|
|
2327
2417
|
};
|
|
2328
|
-
|
|
2418
|
+
useEffect7(() => {
|
|
2329
2419
|
if (!opening) return;
|
|
2330
2420
|
if (state.attached?.key === opening.key) {
|
|
2331
2421
|
setOpening(null);
|
|
@@ -2374,7 +2464,9 @@ export {
|
|
|
2374
2464
|
ContinuationBar,
|
|
2375
2465
|
Conversation,
|
|
2376
2466
|
HarnessLogo,
|
|
2467
|
+
ImageViewer,
|
|
2377
2468
|
LoadingStatus,
|
|
2469
|
+
MessageImages,
|
|
2378
2470
|
RequestCard,
|
|
2379
2471
|
SessionDetails,
|
|
2380
2472
|
SessionList,
|