@volter-ai-dev/supercode-ui 0.1.21 → 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 +8 -2
- package/components.d.ts +2 -0
- package/components.mjs +261 -125
- package/composer.mjs +49 -26
- package/conversation.d.ts +2 -0
- package/conversation.mjs +136 -44
- package/embed.mjs +259 -125
- package/index.d.ts +2 -0
- package/messenger.mjs +259 -125
- package/package.json +1 -1
- package/sessions.mjs +14 -13
- package/styles.css +3 -1
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,17 +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 [
|
|
971
|
-
const
|
|
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);
|
|
972
1063
|
useAutosizeTextarea(textarea, draft);
|
|
973
1064
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
974
|
-
|
|
1065
|
+
useEffect2(() => {
|
|
975
1066
|
remember(draft, context, images, queue);
|
|
976
1067
|
}, [draft, context, images, memoryKey, queue]);
|
|
977
1068
|
const updateQueue = (update) => setQueue((items) => {
|
|
@@ -981,7 +1072,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
981
1072
|
});
|
|
982
1073
|
const queueBlocked = state.busy || pendingStatus !== null || dispatching;
|
|
983
1074
|
const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
|
|
984
|
-
|
|
1075
|
+
useEffect2(() => {
|
|
985
1076
|
if (!queueBlocked && state.canSend && queue.length) {
|
|
986
1077
|
const [next, ...rest] = queue;
|
|
987
1078
|
setDispatching(true);
|
|
@@ -991,13 +1082,13 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
991
1082
|
adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {}, ...next.images.length ? { images: next.images } : {} });
|
|
992
1083
|
}
|
|
993
1084
|
}, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
|
|
994
|
-
|
|
1085
|
+
useEffect2(() => {
|
|
995
1086
|
if (pendingStatus !== null || state.busy) setDispatching(false);
|
|
996
1087
|
}, [pendingStatus, state.busy]);
|
|
997
|
-
|
|
1088
|
+
useEffect2(() => {
|
|
998
1089
|
textarea.current?.focus({ preventScroll: true });
|
|
999
1090
|
}, [memoryKey]);
|
|
1000
|
-
|
|
1091
|
+
useEffect2(() => {
|
|
1001
1092
|
if (!restoreDraft) return;
|
|
1002
1093
|
setDraft(restoreDraft.text);
|
|
1003
1094
|
const restoredContext = normalizeContext(restoreDraft.context);
|
|
@@ -1008,7 +1099,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1008
1099
|
textarea.current?.focus({ preventScroll: true });
|
|
1009
1100
|
onDraftRestored?.(restoreDraft.id);
|
|
1010
1101
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
1011
|
-
|
|
1102
|
+
useEffect2(() => {
|
|
1012
1103
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
1013
1104
|
return () => clearTimeout(timer);
|
|
1014
1105
|
}, [adapter, draft]);
|
|
@@ -1032,13 +1123,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1032
1123
|
});
|
|
1033
1124
|
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
1034
1125
|
};
|
|
1035
|
-
const
|
|
1036
|
-
const
|
|
1037
|
-
if (!
|
|
1038
|
-
|
|
1126
|
+
const addImageFiles = (value, source) => {
|
|
1127
|
+
const allFiles = Array.from(value ?? []);
|
|
1128
|
+
if (!allFiles.length) return false;
|
|
1129
|
+
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
1130
|
+
if (files.length !== allFiles.length) {
|
|
1131
|
+
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1039
1134
|
if (images.length + files.length > MAX_IMAGE_ITEMS) {
|
|
1040
1135
|
setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
1041
|
-
return;
|
|
1136
|
+
return true;
|
|
1042
1137
|
}
|
|
1043
1138
|
setPicking(true);
|
|
1044
1139
|
setPickerError(null);
|
|
@@ -1046,11 +1141,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1046
1141
|
const next = mergeImages(current, picked);
|
|
1047
1142
|
remember(draft, context, next, queue);
|
|
1048
1143
|
return next;
|
|
1049
|
-
}), (error) => setPickerError(error instanceof Error ? error.message :
|
|
1144
|
+
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
1145
|
+
return true;
|
|
1146
|
+
};
|
|
1147
|
+
const pasteImages = (event) => {
|
|
1148
|
+
if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
|
|
1149
|
+
};
|
|
1150
|
+
const dropImages = (event) => {
|
|
1151
|
+
setDragging(false);
|
|
1152
|
+
if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
|
|
1050
1153
|
};
|
|
1051
1154
|
const send = () => {
|
|
1052
1155
|
const text = draft.trim();
|
|
1053
|
-
if (!text) return;
|
|
1156
|
+
if (!text && !images.length) return;
|
|
1054
1157
|
const message = { text, context, images };
|
|
1055
1158
|
if (queuesNewMessage) updateQueue((items) => [...items, message]);
|
|
1056
1159
|
else if (state.canSend) {
|
|
@@ -1071,7 +1174,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1071
1174
|
] }),
|
|
1072
1175
|
queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
|
|
1073
1176
|
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1074
|
-
item.text,
|
|
1177
|
+
item.text || "Image attachment",
|
|
1075
1178
|
item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
|
|
1076
1179
|
item.context.length + item.images.length,
|
|
1077
1180
|
" attached"
|
|
@@ -1091,7 +1194,16 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1091
1194
|
return next;
|
|
1092
1195
|
}) }),
|
|
1093
1196
|
pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
1094
|
-
/* @__PURE__ */ jsxs3("div", { class:
|
|
1197
|
+
/* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
1198
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
1199
|
+
event.preventDefault();
|
|
1200
|
+
setDragging(true);
|
|
1201
|
+
}
|
|
1202
|
+
}, onDragOver: (event) => {
|
|
1203
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
|
|
1204
|
+
}, onDragLeave: (event) => {
|
|
1205
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
1206
|
+
}, onDrop: dropImages, children: [
|
|
1095
1207
|
adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
1096
1208
|
/* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onPaste: pasteImages, onInput: (event) => {
|
|
1097
1209
|
const value = event.currentTarget.value;
|
|
@@ -1105,19 +1217,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1105
1217
|
} }),
|
|
1106
1218
|
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1107
1219
|
state.busy ? /* @__PURE__ */ jsx3("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx3(UiIcon, { name: "stop", size: 15 }) }) : null,
|
|
1108
|
-
/* @__PURE__ */ jsx3("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx3(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
|
|
1220
|
+
/* @__PURE__ */ jsx3("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() && !images.length || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx3(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
|
|
1109
1221
|
] })
|
|
1110
1222
|
] })
|
|
1111
1223
|
] });
|
|
1112
1224
|
}
|
|
1113
1225
|
|
|
1114
1226
|
// src/conversation.jsx
|
|
1115
|
-
import { Fragment as
|
|
1116
|
-
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";
|
|
1117
1229
|
|
|
1118
1230
|
// src/markdown.jsx
|
|
1119
1231
|
import MarkdownIt from "markdown-it";
|
|
1120
|
-
import { useEffect as
|
|
1232
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "preact/hooks";
|
|
1121
1233
|
import { jsx as jsx4 } from "preact/jsx-runtime";
|
|
1122
1234
|
var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
|
|
1123
1235
|
var LANGUAGE_LABELS = {
|
|
@@ -1179,8 +1291,8 @@ function setCopyState(button, status) {
|
|
|
1179
1291
|
}
|
|
1180
1292
|
function Markdown({ value, copyText }) {
|
|
1181
1293
|
const html = useMemo(() => markdown.render(value), [value]);
|
|
1182
|
-
const resets =
|
|
1183
|
-
|
|
1294
|
+
const resets = useRef3(/* @__PURE__ */ new Map());
|
|
1295
|
+
useEffect3(() => () => {
|
|
1184
1296
|
for (const timer of resets.current.values()) clearTimeout(timer);
|
|
1185
1297
|
resets.current.clear();
|
|
1186
1298
|
}, []);
|
|
@@ -1209,7 +1321,7 @@ function Markdown({ value, copyText }) {
|
|
|
1209
1321
|
}
|
|
1210
1322
|
|
|
1211
1323
|
// src/conversation.jsx
|
|
1212
|
-
import { Fragment as
|
|
1324
|
+
import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
1213
1325
|
function LoadingStatus({ state, compact = false }) {
|
|
1214
1326
|
const copy = {
|
|
1215
1327
|
connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
|
|
@@ -1258,9 +1370,9 @@ function ContextDisclosure({ context }) {
|
|
|
1258
1370
|
] });
|
|
1259
1371
|
}
|
|
1260
1372
|
function MessageMeta({ entry, adapter }) {
|
|
1261
|
-
const [copyState, setCopyState2] =
|
|
1262
|
-
const reset =
|
|
1263
|
-
|
|
1373
|
+
const [copyState, setCopyState2] = useState3("idle");
|
|
1374
|
+
const reset = useRef4(null);
|
|
1375
|
+
useEffect4(() => () => clearTimeout(reset.current), []);
|
|
1264
1376
|
const date = entry.ts === null ? null : new Date(entry.ts);
|
|
1265
1377
|
const validDate = date && Number.isFinite(date.valueOf()) ? date : null;
|
|
1266
1378
|
if (!validDate && (!adapter?.copyText || !entry.text)) return null;
|
|
@@ -1281,33 +1393,33 @@ function MessageMeta({ entry, adapter }) {
|
|
|
1281
1393
|
] });
|
|
1282
1394
|
}
|
|
1283
1395
|
var TOOL_ICONS = {
|
|
1284
|
-
read: () => /* @__PURE__ */ jsxs4(
|
|
1396
|
+
read: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1285
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" }),
|
|
1286
1398
|
/* @__PURE__ */ jsx5("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
|
|
1287
1399
|
] }),
|
|
1288
|
-
search: () => /* @__PURE__ */ jsxs4(
|
|
1400
|
+
search: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1289
1401
|
/* @__PURE__ */ jsx5("circle", { cx: "8", cy: "8", r: "4.5" }),
|
|
1290
1402
|
/* @__PURE__ */ jsx5("path", { d: "m11.5 11.5 3 3" })
|
|
1291
1403
|
] }),
|
|
1292
|
-
edit: () => /* @__PURE__ */ jsxs4(
|
|
1404
|
+
edit: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1293
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" }),
|
|
1294
1406
|
/* @__PURE__ */ jsx5("path", { d: "m10 5 3 3" })
|
|
1295
1407
|
] }),
|
|
1296
|
-
command: () => /* @__PURE__ */ jsx5(
|
|
1297
|
-
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: [
|
|
1298
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" }),
|
|
1299
1411
|
/* @__PURE__ */ jsx5("path", { d: "M5 2.5h8" })
|
|
1300
1412
|
] }),
|
|
1301
|
-
web: () => /* @__PURE__ */ jsxs4(
|
|
1413
|
+
web: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1302
1414
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "6.5" }),
|
|
1303
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" })
|
|
1304
1416
|
] }),
|
|
1305
|
-
agent: () => /* @__PURE__ */ jsxs4(
|
|
1417
|
+
agent: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1306
1418
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "6", r: "2.5" }),
|
|
1307
1419
|
/* @__PURE__ */ jsx5("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
|
|
1308
1420
|
] }),
|
|
1309
|
-
plan: () => /* @__PURE__ */ jsx5(
|
|
1310
|
-
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: [
|
|
1311
1423
|
/* @__PURE__ */ jsx5("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
|
|
1312
1424
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "3.5" })
|
|
1313
1425
|
] })
|
|
@@ -1373,9 +1485,9 @@ function ToolMetrics({ presentation }) {
|
|
|
1373
1485
|
}
|
|
1374
1486
|
function PendingElapsed({ now }) {
|
|
1375
1487
|
const clock = now ?? Date.now;
|
|
1376
|
-
const started =
|
|
1377
|
-
const [elapsed, setElapsed] =
|
|
1378
|
-
|
|
1488
|
+
const started = useRef4(clock());
|
|
1489
|
+
const [elapsed, setElapsed] = useState3(0);
|
|
1490
|
+
useEffect4(() => {
|
|
1379
1491
|
const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
|
|
1380
1492
|
return () => clearInterval(timer);
|
|
1381
1493
|
}, [clock]);
|
|
@@ -1416,7 +1528,7 @@ function SearchPreview({ presentation }) {
|
|
|
1416
1528
|
] }) : null,
|
|
1417
1529
|
lines.length ? /* @__PURE__ */ jsx5("ol", { children: lines.map((line, index) => {
|
|
1418
1530
|
const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
|
|
1419
|
-
return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(
|
|
1531
|
+
return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1420
1532
|
/* @__PURE__ */ jsx5("code", { children: match[1] }),
|
|
1421
1533
|
/* @__PURE__ */ jsxs4("small", { children: [
|
|
1422
1534
|
match[2],
|
|
@@ -1449,9 +1561,9 @@ function ToolPreview({ presentation, entry }) {
|
|
|
1449
1561
|
return presentation.preview ? /* @__PURE__ */ jsx5("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
|
|
1450
1562
|
}
|
|
1451
1563
|
function ToolActions({ presentation, adapter }) {
|
|
1452
|
-
const [copied, setCopied] =
|
|
1453
|
-
const reset =
|
|
1454
|
-
|
|
1564
|
+
const [copied, setCopied] = useState3(false);
|
|
1565
|
+
const reset = useRef4(null);
|
|
1566
|
+
useEffect4(() => () => clearTimeout(reset.current), []);
|
|
1455
1567
|
if (!adapter?.copyText) return null;
|
|
1456
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;
|
|
1457
1569
|
const copy = async () => {
|
|
@@ -1498,7 +1610,7 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
1498
1610
|
}
|
|
1499
1611
|
if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx5("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
|
|
1500
1612
|
return /* @__PURE__ */ jsxs4("article", { class: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
|
|
1501
|
-
/* @__PURE__ */ jsx5(MessageImages, { items: entry.images }),
|
|
1613
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: entry.images, adapter }),
|
|
1502
1614
|
/* @__PURE__ */ jsx5(Markdown, { value: entry.text, copyText: adapter?.copyText }),
|
|
1503
1615
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: entry.context }),
|
|
1504
1616
|
entry.truncated ? /* @__PURE__ */ jsx5("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
|
|
@@ -1507,14 +1619,14 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
1507
1619
|
}
|
|
1508
1620
|
function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
1509
1621
|
const presentation = entry.presentation ?? createToolPresentation(entry);
|
|
1510
|
-
const [expanded, setExpanded] =
|
|
1511
|
-
|
|
1622
|
+
const [expanded, setExpanded] = useState3(open || entry.status === "pending");
|
|
1623
|
+
useEffect4(() => {
|
|
1512
1624
|
if (entry.status === "pending") setExpanded(true);
|
|
1513
1625
|
}, [entry.status]);
|
|
1514
1626
|
const target = compactToolTarget(presentation.target, workspace);
|
|
1515
1627
|
const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
|
|
1516
1628
|
const category = presentation.category ?? toolCategory(entry);
|
|
1517
|
-
const summary = /* @__PURE__ */ jsxs4(
|
|
1629
|
+
const summary = /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1518
1630
|
/* @__PURE__ */ jsx5(ToolIcon, { category }),
|
|
1519
1631
|
/* @__PURE__ */ jsx5("strong", { children: toolAction2(entry, category, presentation) }),
|
|
1520
1632
|
target ? /* @__PURE__ */ jsx5("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
|
|
@@ -1542,9 +1654,9 @@ function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
|
1542
1654
|
function ActivityGroup({ entries, state, adapter }) {
|
|
1543
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 }) });
|
|
1544
1656
|
const active = entries.some((entry) => entry.status === "pending");
|
|
1545
|
-
const [open, setOpen] =
|
|
1657
|
+
const [open, setOpen] = useState3(active);
|
|
1546
1658
|
const id = useId();
|
|
1547
|
-
|
|
1659
|
+
useEffect4(() => {
|
|
1548
1660
|
if (active) setOpen(true);
|
|
1549
1661
|
}, [active]);
|
|
1550
1662
|
return /* @__PURE__ */ jsxs4("section", { class: "scui-activity", children: [
|
|
@@ -1620,9 +1732,9 @@ function SessionDetails({ semantics }) {
|
|
|
1620
1732
|
}
|
|
1621
1733
|
var conversationMemory = /* @__PURE__ */ new Map();
|
|
1622
1734
|
function ConversationAnnouncements({ state }) {
|
|
1623
|
-
const previousBusy =
|
|
1624
|
-
const [announcement, setAnnouncement] =
|
|
1625
|
-
|
|
1735
|
+
const previousBusy = useRef4(state.busy);
|
|
1736
|
+
const [announcement, setAnnouncement] = useState3("");
|
|
1737
|
+
useEffect4(() => {
|
|
1626
1738
|
if (previousBusy.current && !state.busy && !state.error) {
|
|
1627
1739
|
setAnnouncement(`${harnessDisplayName(state.harness) || "Coding agent"} finished working`);
|
|
1628
1740
|
}
|
|
@@ -1631,13 +1743,13 @@ function ConversationAnnouncements({ state }) {
|
|
|
1631
1743
|
return /* @__PURE__ */ jsx5("span", { class: "scui-sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement });
|
|
1632
1744
|
}
|
|
1633
1745
|
function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null, unreadAfterMessages = null }) {
|
|
1634
|
-
const scroller =
|
|
1746
|
+
const scroller = useRef4(null);
|
|
1635
1747
|
const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
|
|
1636
|
-
const [atBottom, setAtBottom] =
|
|
1637
|
-
const earlierAnchor =
|
|
1638
|
-
const restored =
|
|
1748
|
+
const [atBottom, setAtBottom] = useState3(remembered.atBottom);
|
|
1749
|
+
const earlierAnchor = useRef4(null);
|
|
1750
|
+
const restored = useRef4(false);
|
|
1639
1751
|
const blocks = groupConversation(state.transcript);
|
|
1640
|
-
const unreadBoundary =
|
|
1752
|
+
const unreadBoundary = useRef4(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
|
|
1641
1753
|
const unreadBlock = unreadBoundary.current === null ? -1 : blocks.findIndex((block) => {
|
|
1642
1754
|
const entries = block.kind === "activity" ? block.entries : [block.entry];
|
|
1643
1755
|
return entries.some((entry) => Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > unreadBoundary.current);
|
|
@@ -1696,12 +1808,12 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1696
1808
|
}, children: "Load earlier messages" }) : null,
|
|
1697
1809
|
!blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx5(LoadingStatus, { state }) : null,
|
|
1698
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,
|
|
1699
|
-
blocks.map((block, index) => /* @__PURE__ */ jsxs4(
|
|
1811
|
+
blocks.map((block, index) => /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1700
1812
|
index === unreadBlock ? /* @__PURE__ */ jsx5("div", { class: "scui-unread-divider", role: "separator", "aria-label": "New messages", children: /* @__PURE__ */ jsx5("span", { children: "New" }) }) : null,
|
|
1701
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 })
|
|
1702
1814
|
] }, block.id)),
|
|
1703
1815
|
pendingMessage ? /* @__PURE__ */ jsxs4("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
|
|
1704
|
-
/* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images }),
|
|
1816
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images, adapter }),
|
|
1705
1817
|
/* @__PURE__ */ jsx5(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
|
|
1706
1818
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: pendingMessage.context }),
|
|
1707
1819
|
/* @__PURE__ */ jsxs4("footer", { children: [
|
|
@@ -1732,7 +1844,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1732
1844
|
}
|
|
1733
1845
|
|
|
1734
1846
|
// src/logo.jsx
|
|
1735
|
-
import { useEffect as
|
|
1847
|
+
import { useEffect as useEffect5 } from "preact/hooks";
|
|
1736
1848
|
import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
1737
1849
|
var LOGOS = {
|
|
1738
1850
|
"claude-code": {
|
|
@@ -1783,7 +1895,7 @@ function hasHarnessLogo(id) {
|
|
|
1783
1895
|
}
|
|
1784
1896
|
function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
1785
1897
|
const logo = LOGOS[id];
|
|
1786
|
-
|
|
1898
|
+
useEffect5(() => {
|
|
1787
1899
|
if (!logo) onMissingLogo?.(id);
|
|
1788
1900
|
}, [id, logo, onMissingLogo]);
|
|
1789
1901
|
if (!logo) return null;
|
|
@@ -1794,10 +1906,10 @@ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
|
1794
1906
|
}
|
|
1795
1907
|
|
|
1796
1908
|
// src/messenger.jsx
|
|
1797
|
-
import { useEffect as
|
|
1909
|
+
import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "preact/hooks";
|
|
1798
1910
|
|
|
1799
1911
|
// src/sessions.jsx
|
|
1800
|
-
import { useEffect as
|
|
1912
|
+
import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef5, useState as useState4 } from "preact/hooks";
|
|
1801
1913
|
import { jsx as jsx7, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
1802
1914
|
var sessionListMemory = /* @__PURE__ */ new Map();
|
|
1803
1915
|
function SessionRow({ row, state, onOpen }) {
|
|
@@ -1819,21 +1931,21 @@ function SessionRow({ row, state, onOpen }) {
|
|
|
1819
1931
|
}
|
|
1820
1932
|
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
|
|
1821
1933
|
const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
|
|
1822
|
-
const [query, setQuery] =
|
|
1823
|
-
const [loadingMore, setLoadingMore] =
|
|
1824
|
-
const root =
|
|
1825
|
-
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);
|
|
1826
1938
|
const rows = filterSessions(state.sessions, query);
|
|
1827
1939
|
const Row = components.SessionRow ?? SessionRow;
|
|
1828
1940
|
useLayoutEffect3(() => {
|
|
1829
1941
|
if (rowScroller.current) rowScroller.current.scrollTop = remembered.top;
|
|
1830
1942
|
}, [memoryKey]);
|
|
1831
|
-
|
|
1943
|
+
useEffect6(() => {
|
|
1832
1944
|
if (!focusKey || !root.current) return;
|
|
1833
1945
|
const target = focusKey === "@new" ? root.current.querySelector('[data-list-focus="new"]') : [...root.current.querySelectorAll("[data-session-key]")].find((element) => element.dataset.sessionKey === focusKey);
|
|
1834
1946
|
(target ?? root.current.querySelector("input,button"))?.focus({ preventScroll: true });
|
|
1835
1947
|
}, [focusKey, rows.length]);
|
|
1836
|
-
|
|
1948
|
+
useEffect6(() => {
|
|
1837
1949
|
if (loadingMore) setLoadingMore(false);
|
|
1838
1950
|
}, [state.error, state.history.hasMoreSessions, state.sessions.length]);
|
|
1839
1951
|
const loadMore = () => {
|
|
@@ -1919,10 +2031,10 @@ function Receipt({ state, adapter }) {
|
|
|
1919
2031
|
return null;
|
|
1920
2032
|
}
|
|
1921
2033
|
function ConversationActions({ state, adapter, actionPending }) {
|
|
1922
|
-
const [open, setOpen] =
|
|
1923
|
-
const root =
|
|
1924
|
-
const panel =
|
|
1925
|
-
const trigger =
|
|
2034
|
+
const [open, setOpen] = useState5(false);
|
|
2035
|
+
const root = useRef6(null);
|
|
2036
|
+
const panel = useRef6(null);
|
|
2037
|
+
const trigger = useRef6(null);
|
|
1926
2038
|
const menuId = useId2();
|
|
1927
2039
|
const targets = state.harnesses.filter((item) => item.startable);
|
|
1928
2040
|
const groups = [
|
|
@@ -1943,7 +2055,7 @@ function ConversationActions({ state, adapter, actionPending }) {
|
|
|
1943
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 } })) : []
|
|
1944
2056
|
}
|
|
1945
2057
|
].filter((group) => group.items.length);
|
|
1946
|
-
|
|
2058
|
+
useEffect7(() => {
|
|
1947
2059
|
if (!open) return;
|
|
1948
2060
|
panel.current?.querySelector("button:not(:disabled)")?.focus({ preventScroll: true });
|
|
1949
2061
|
const dismiss = (event) => {
|
|
@@ -1993,12 +2105,12 @@ function ConversationActions({ state, adapter, actionPending }) {
|
|
|
1993
2105
|
] });
|
|
1994
2106
|
}
|
|
1995
2107
|
function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose }) {
|
|
1996
|
-
const back =
|
|
2108
|
+
const back = useRef6(null);
|
|
1997
2109
|
const harness = state.attached?.harness ?? state.harness;
|
|
1998
2110
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
1999
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";
|
|
2000
2112
|
const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce;
|
|
2001
|
-
|
|
2113
|
+
useEffect7(() => {
|
|
2002
2114
|
if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
|
|
2003
2115
|
}, []);
|
|
2004
2116
|
return /* @__PURE__ */ jsxs7("header", { class: "scui-head scui-chat-head", children: [
|
|
@@ -2020,24 +2132,24 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2020
2132
|
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
|
|
2021
2133
|
const Header = slots.header;
|
|
2022
2134
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
2023
|
-
const [pending, setPendingState] =
|
|
2024
|
-
const [pendingAction, setPendingAction] =
|
|
2025
|
-
const [restoreDraft, setRestoreDraft] =
|
|
2026
|
-
const restoreSequence =
|
|
2027
|
-
const actionSequence =
|
|
2028
|
-
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());
|
|
2029
2141
|
const setPending = (update) => setPendingState((current) => {
|
|
2030
2142
|
const next = typeof update === "function" ? update(current) : update;
|
|
2031
2143
|
if (next) boundedSet(pendingMessageMemory, memoryKey, next);
|
|
2032
2144
|
else pendingMessageMemory.delete(memoryKey);
|
|
2033
2145
|
return next;
|
|
2034
2146
|
});
|
|
2035
|
-
|
|
2147
|
+
useEffect7(() => {
|
|
2036
2148
|
setPendingState(pendingMessageMemory.get(memoryKey) ?? null);
|
|
2037
2149
|
setPendingAction(null);
|
|
2038
2150
|
setRestoreDraft(null);
|
|
2039
2151
|
}, [memoryKey]);
|
|
2040
|
-
|
|
2152
|
+
useEffect7(() => {
|
|
2041
2153
|
if (!pending) return;
|
|
2042
2154
|
if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
|
|
2043
2155
|
setPending(null);
|
|
@@ -2066,7 +2178,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2066
2178
|
setRestoreDraft({ id: restoreSequence.current, text: pending.text, context: pending.context, images: pending.images });
|
|
2067
2179
|
setPending({ ...pending, status: "editing" });
|
|
2068
2180
|
};
|
|
2069
|
-
|
|
2181
|
+
useEffect7(() => {
|
|
2070
2182
|
const key = state.attached?.key;
|
|
2071
2183
|
if (!key || !state.attention.some((item) => item.key === key)) {
|
|
2072
2184
|
if (key) acknowledged.current.delete(key);
|
|
@@ -2101,7 +2213,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2101
2213
|
return result;
|
|
2102
2214
|
}
|
|
2103
2215
|
}), [adapter, state.error]);
|
|
2104
|
-
|
|
2216
|
+
useEffect7(() => {
|
|
2105
2217
|
if (!pendingAction) return;
|
|
2106
2218
|
if (state.operation && !pendingAction.seenOperation) {
|
|
2107
2219
|
setPendingAction({ ...pendingAction, seenOperation: true });
|
|
@@ -2134,23 +2246,24 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2134
2246
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2135
2247
|
const startableKey = startable.map((item) => item.id).join("\0");
|
|
2136
2248
|
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
|
|
2137
|
-
const [harness, setHarness] =
|
|
2138
|
-
const [draft, setDraft] =
|
|
2139
|
-
const [context, setContext] =
|
|
2140
|
-
const [images, setImages] =
|
|
2141
|
-
const [starting, setStarting] =
|
|
2142
|
-
const [picking, setPicking] =
|
|
2143
|
-
const [
|
|
2144
|
-
const
|
|
2145
|
-
const
|
|
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);
|
|
2146
2259
|
useAutosizeTextarea(textarea, draft);
|
|
2147
|
-
|
|
2260
|
+
useEffect7(() => {
|
|
2148
2261
|
if (startable.some((item) => item.id === harness)) return;
|
|
2149
2262
|
const next = startable[0]?.id ?? "";
|
|
2150
2263
|
setHarness(next);
|
|
2151
2264
|
boundedSet(newChatMemory, memoryKey, { harness: next, draft, context, images });
|
|
2152
2265
|
}, [harness, startableKey]);
|
|
2153
|
-
|
|
2266
|
+
useEffect7(() => {
|
|
2154
2267
|
if (!starting) return;
|
|
2155
2268
|
const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
|
|
2156
2269
|
const beganWorking = !starting.busy && state.busy;
|
|
@@ -2158,10 +2271,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2158
2271
|
newChatMemory.delete(memoryKey);
|
|
2159
2272
|
onStarted();
|
|
2160
2273
|
}, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
|
|
2161
|
-
|
|
2274
|
+
useEffect7(() => {
|
|
2162
2275
|
if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
|
|
2163
2276
|
}, [starting, state.busy, state.error, state.operation]);
|
|
2164
|
-
|
|
2277
|
+
useEffect7(() => {
|
|
2165
2278
|
textarea.current?.focus({ preventScroll: true });
|
|
2166
2279
|
}, []);
|
|
2167
2280
|
const pickContext = () => {
|
|
@@ -2184,13 +2297,17 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2184
2297
|
});
|
|
2185
2298
|
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
2186
2299
|
};
|
|
2187
|
-
const
|
|
2188
|
-
const
|
|
2189
|
-
if (!
|
|
2190
|
-
|
|
2300
|
+
const addImageFiles = (value, source) => {
|
|
2301
|
+
const allFiles = Array.from(value ?? []);
|
|
2302
|
+
if (!allFiles.length) return false;
|
|
2303
|
+
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
2304
|
+
if (files.length !== allFiles.length) {
|
|
2305
|
+
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
2306
|
+
return true;
|
|
2307
|
+
}
|
|
2191
2308
|
if (images.length + files.length > MAX_IMAGE_ITEMS) {
|
|
2192
2309
|
setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2193
|
-
return;
|
|
2310
|
+
return true;
|
|
2194
2311
|
}
|
|
2195
2312
|
setPicking(true);
|
|
2196
2313
|
setPickerError(null);
|
|
@@ -2198,11 +2315,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2198
2315
|
const next = mergeImages(current, picked);
|
|
2199
2316
|
boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
|
|
2200
2317
|
return next;
|
|
2201
|
-
}), (error) => setPickerError(error instanceof Error ? error.message :
|
|
2318
|
+
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
2319
|
+
return true;
|
|
2320
|
+
};
|
|
2321
|
+
const pasteImages = (event) => {
|
|
2322
|
+
if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
|
|
2323
|
+
};
|
|
2324
|
+
const dropImages = (event) => {
|
|
2325
|
+
setDragging(false);
|
|
2326
|
+
if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
|
|
2202
2327
|
};
|
|
2203
2328
|
const send = () => {
|
|
2204
2329
|
const text = draft.trim();
|
|
2205
|
-
if (!text || !harness || starting) return;
|
|
2330
|
+
if (!text && !images.length || !harness || starting) return;
|
|
2206
2331
|
const id = startSequence.current + 1;
|
|
2207
2332
|
startSequence.current = id;
|
|
2208
2333
|
setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
|
|
@@ -2253,7 +2378,16 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2253
2378
|
return next;
|
|
2254
2379
|
}) }),
|
|
2255
2380
|
pickerError ? /* @__PURE__ */ jsx8("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2256
|
-
/* @__PURE__ */ jsxs7("div", { class:
|
|
2381
|
+
/* @__PURE__ */ jsxs7("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2382
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2383
|
+
event.preventDefault();
|
|
2384
|
+
setDragging(true);
|
|
2385
|
+
}
|
|
2386
|
+
}, onDragOver: (event) => {
|
|
2387
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
|
|
2388
|
+
}, onDragLeave: (event) => {
|
|
2389
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2390
|
+
}, onDrop: dropImages, children: [
|
|
2257
2391
|
adapter.pickContext ? /* @__PURE__ */ jsx8("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx8("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx8(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2258
2392
|
/* @__PURE__ */ jsx8("textarea", { ref: textarea, rows: 3, "aria-label": "Message coding agent", placeholder: startable.length ? "What should the agent do?" : "No coding harness is available", value: draft, disabled: !startable.length || Boolean(starting), onPaste: pasteImages, onInput: (event) => {
|
|
2259
2393
|
const value = event.currentTarget.value;
|
|
@@ -2265,7 +2399,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2265
2399
|
send();
|
|
2266
2400
|
}
|
|
2267
2401
|
} }),
|
|
2268
|
-
/* @__PURE__ */ jsx8("span", { children: /* @__PURE__ */ jsx8("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx8("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx8(UiIcon, { name: "send", size: 17 }) }) })
|
|
2402
|
+
/* @__PURE__ */ jsx8("span", { children: /* @__PURE__ */ jsx8("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx8("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx8(UiIcon, { name: "send", size: 17 }) }) })
|
|
2269
2403
|
] })
|
|
2270
2404
|
] })
|
|
2271
2405
|
] });
|
|
@@ -2274,14 +2408,14 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2274
2408
|
const state = useMemo2(() => normalizeUiState(stateInput), [stateInput]);
|
|
2275
2409
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
2276
2410
|
const memoryKey = state.workspace || "@default";
|
|
2277
|
-
const [view, setViewState] =
|
|
2278
|
-
const [opening, setOpening] =
|
|
2279
|
-
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);
|
|
2280
2414
|
const setView = (next) => {
|
|
2281
2415
|
boundedSet(messengerViewMemory, memoryKey, next);
|
|
2282
2416
|
setViewState(next);
|
|
2283
2417
|
};
|
|
2284
|
-
|
|
2418
|
+
useEffect7(() => {
|
|
2285
2419
|
if (!opening) return;
|
|
2286
2420
|
if (state.attached?.key === opening.key) {
|
|
2287
2421
|
setOpening(null);
|
|
@@ -2330,7 +2464,9 @@ export {
|
|
|
2330
2464
|
ContinuationBar,
|
|
2331
2465
|
Conversation,
|
|
2332
2466
|
HarnessLogo,
|
|
2467
|
+
ImageViewer,
|
|
2333
2468
|
LoadingStatus,
|
|
2469
|
+
MessageImages,
|
|
2334
2470
|
RequestCard,
|
|
2335
2471
|
SessionDetails,
|
|
2336
2472
|
SessionList,
|