@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/embed.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { render } from "preact";
|
|
3
3
|
|
|
4
4
|
// src/messenger.jsx
|
|
5
|
-
import { useEffect as
|
|
5
|
+
import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "preact/hooks";
|
|
6
6
|
|
|
7
7
|
// core.mjs
|
|
8
8
|
var HARNESS_NAMES = Object.freeze({
|
|
@@ -761,7 +761,7 @@ function isSendKey(event) {
|
|
|
761
761
|
}
|
|
762
762
|
|
|
763
763
|
// src/composer.jsx
|
|
764
|
-
import { useEffect, useRef, useState } from "preact/hooks";
|
|
764
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "preact/hooks";
|
|
765
765
|
|
|
766
766
|
// src/memory.js
|
|
767
767
|
var MEMORY_LIMIT = 100;
|
|
@@ -821,7 +821,8 @@ function UiIcon({ name, size = 16, class: className = "" }) {
|
|
|
821
821
|
}
|
|
822
822
|
|
|
823
823
|
// src/context.jsx
|
|
824
|
-
import {
|
|
824
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
825
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
825
826
|
var MAX_CONTEXT_ITEMS = 32;
|
|
826
827
|
var MAX_IMAGE_ITEMS = 4;
|
|
827
828
|
var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
@@ -920,12 +921,101 @@ function ImageTray({ items, onRemove }) {
|
|
|
920
921
|
onRemove ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove image ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) }) : null
|
|
921
922
|
] }, item.id ?? `${item.label}:${index}`)) });
|
|
922
923
|
}
|
|
923
|
-
function
|
|
924
|
+
function imageFilename(label) {
|
|
925
|
+
const value = label.trim().replace(/[\\/:*?"<>|]+/g, "-");
|
|
926
|
+
return value || "image";
|
|
927
|
+
}
|
|
928
|
+
function ImageViewer({ items, index, adapter, onChange, onClose }) {
|
|
929
|
+
const dialog = useRef(null);
|
|
930
|
+
const reset = useRef(null);
|
|
931
|
+
const [copyState, setCopyState2] = useState("idle");
|
|
932
|
+
const item = items[index];
|
|
933
|
+
const remote = item?.url?.startsWith("http://") || item?.url?.startsWith("https://");
|
|
934
|
+
useEffect(() => {
|
|
935
|
+
if (!dialog.current?.open) dialog.current?.showModal();
|
|
936
|
+
return () => clearTimeout(reset.current);
|
|
937
|
+
}, []);
|
|
938
|
+
useEffect(() => {
|
|
939
|
+
clearTimeout(reset.current);
|
|
940
|
+
setCopyState2("idle");
|
|
941
|
+
}, [index]);
|
|
942
|
+
if (!item?.url) return null;
|
|
943
|
+
const move = (amount) => onChange((index + amount + items.length) % items.length);
|
|
944
|
+
const copy = async () => {
|
|
945
|
+
try {
|
|
946
|
+
await adapter.copyText(item.url);
|
|
947
|
+
setCopyState2("copied");
|
|
948
|
+
} catch {
|
|
949
|
+
setCopyState2("failed");
|
|
950
|
+
}
|
|
951
|
+
clearTimeout(reset.current);
|
|
952
|
+
reset.current = setTimeout(() => setCopyState2("idle"), 1500);
|
|
953
|
+
};
|
|
954
|
+
const close = () => dialog.current?.close();
|
|
955
|
+
return /* @__PURE__ */ jsxs2(
|
|
956
|
+
"dialog",
|
|
957
|
+
{
|
|
958
|
+
ref: dialog,
|
|
959
|
+
class: "scui-image-viewer",
|
|
960
|
+
"aria-label": `Image preview: ${item.label}`,
|
|
961
|
+
onClose,
|
|
962
|
+
onClick: (event) => {
|
|
963
|
+
if (event.target === event.currentTarget) close();
|
|
964
|
+
},
|
|
965
|
+
onKeyDown: (event) => {
|
|
966
|
+
if (items.length < 2 || !["ArrowLeft", "ArrowRight"].includes(event.key)) return;
|
|
967
|
+
event.preventDefault();
|
|
968
|
+
move(event.key === "ArrowLeft" ? -1 : 1);
|
|
969
|
+
},
|
|
970
|
+
children: [
|
|
971
|
+
/* @__PURE__ */ jsxs2("header", { children: [
|
|
972
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
973
|
+
/* @__PURE__ */ jsx2("strong", { children: item.label }),
|
|
974
|
+
items.length > 1 ? /* @__PURE__ */ jsxs2("small", { children: [
|
|
975
|
+
index + 1,
|
|
976
|
+
" of ",
|
|
977
|
+
items.length
|
|
978
|
+
] }) : null
|
|
979
|
+
] }),
|
|
980
|
+
/* @__PURE__ */ jsxs2("nav", { "aria-label": "Image actions", children: [
|
|
981
|
+
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,
|
|
982
|
+
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 }) }),
|
|
983
|
+
/* @__PURE__ */ jsx2("button", { type: "button", "aria-label": "Close image preview", title: "Close", onClick: close, children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 17 }) })
|
|
984
|
+
] })
|
|
985
|
+
] }),
|
|
986
|
+
/* @__PURE__ */ jsxs2("figure", { children: [
|
|
987
|
+
/* @__PURE__ */ jsx2("img", { src: item.url, alt: item.label }),
|
|
988
|
+
items.length > 1 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
989
|
+
/* @__PURE__ */ jsx2("button", { type: "button", class: "scui-image-previous", "aria-label": "Previous image", onClick: () => move(-1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) }),
|
|
990
|
+
/* @__PURE__ */ jsx2("button", { type: "button", class: "scui-image-next", "aria-label": "Next image", onClick: () => move(1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) })
|
|
991
|
+
] }) : null
|
|
992
|
+
] })
|
|
993
|
+
]
|
|
994
|
+
}
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
function MessageImages({ items, adapter }) {
|
|
998
|
+
const [active, setActive] = useState(null);
|
|
999
|
+
const opener = useRef(null);
|
|
924
1000
|
if (!items?.length) return null;
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1001
|
+
const viewable = items.filter((item) => item.url);
|
|
1002
|
+
const close = () => {
|
|
1003
|
+
setActive(null);
|
|
1004
|
+
requestAnimationFrame(() => opener.current?.focus({ preventScroll: true }));
|
|
1005
|
+
};
|
|
1006
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1007
|
+
/* @__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) => {
|
|
1008
|
+
opener.current = event.currentTarget;
|
|
1009
|
+
setActive(viewable.indexOf(item));
|
|
1010
|
+
}, children: /* @__PURE__ */ jsx2("img", { src: item.url, alt: "" }) }, item.id ?? `${item.label}:${index}`) : /* @__PURE__ */ jsxs2("span", { "data-unavailable": "true", children: [
|
|
1011
|
+
/* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 14 }),
|
|
1012
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
1013
|
+
/* @__PURE__ */ jsx2("strong", { children: item.label }),
|
|
1014
|
+
/* @__PURE__ */ jsx2("small", { children: "Preview unavailable" })
|
|
1015
|
+
] })
|
|
1016
|
+
] }, item.id ?? `${item.label}:${index}`)) }),
|
|
1017
|
+
active !== null && viewable[active] ? /* @__PURE__ */ jsx2(ImageViewer, { items: viewable, index: active, adapter, onChange: setActive, onClose: close }) : null
|
|
1018
|
+
] });
|
|
929
1019
|
}
|
|
930
1020
|
|
|
931
1021
|
// src/textarea.js
|
|
@@ -967,17 +1057,18 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
967
1057
|
}
|
|
968
1058
|
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
|
|
969
1059
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
970
|
-
const [draft, setDraft] =
|
|
971
|
-
const [context, setContext] =
|
|
972
|
-
const [images, setImages] =
|
|
973
|
-
const [queue, setQueue] =
|
|
974
|
-
const [dispatching, setDispatching] =
|
|
975
|
-
const [picking, setPicking] =
|
|
976
|
-
const [
|
|
977
|
-
const
|
|
1060
|
+
const [draft, setDraft] = useState2(remembered.draft);
|
|
1061
|
+
const [context, setContext] = useState2(remembered.context ?? []);
|
|
1062
|
+
const [images, setImages] = useState2(remembered.images ?? []);
|
|
1063
|
+
const [queue, setQueue] = useState2((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
|
|
1064
|
+
const [dispatching, setDispatching] = useState2(false);
|
|
1065
|
+
const [picking, setPicking] = useState2(false);
|
|
1066
|
+
const [dragging, setDragging] = useState2(false);
|
|
1067
|
+
const [pickerError, setPickerError] = useState2(null);
|
|
1068
|
+
const textarea = useRef2(null);
|
|
978
1069
|
useAutosizeTextarea(textarea, draft);
|
|
979
1070
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
980
|
-
|
|
1071
|
+
useEffect2(() => {
|
|
981
1072
|
remember(draft, context, images, queue);
|
|
982
1073
|
}, [draft, context, images, memoryKey, queue]);
|
|
983
1074
|
const updateQueue = (update) => setQueue((items) => {
|
|
@@ -987,7 +1078,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
987
1078
|
});
|
|
988
1079
|
const queueBlocked = state.busy || pendingStatus !== null || dispatching;
|
|
989
1080
|
const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
|
|
990
|
-
|
|
1081
|
+
useEffect2(() => {
|
|
991
1082
|
if (!queueBlocked && state.canSend && queue.length) {
|
|
992
1083
|
const [next, ...rest] = queue;
|
|
993
1084
|
setDispatching(true);
|
|
@@ -997,13 +1088,13 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
997
1088
|
adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {}, ...next.images.length ? { images: next.images } : {} });
|
|
998
1089
|
}
|
|
999
1090
|
}, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
|
|
1000
|
-
|
|
1091
|
+
useEffect2(() => {
|
|
1001
1092
|
if (pendingStatus !== null || state.busy) setDispatching(false);
|
|
1002
1093
|
}, [pendingStatus, state.busy]);
|
|
1003
|
-
|
|
1094
|
+
useEffect2(() => {
|
|
1004
1095
|
textarea.current?.focus({ preventScroll: true });
|
|
1005
1096
|
}, [memoryKey]);
|
|
1006
|
-
|
|
1097
|
+
useEffect2(() => {
|
|
1007
1098
|
if (!restoreDraft) return;
|
|
1008
1099
|
setDraft(restoreDraft.text);
|
|
1009
1100
|
const restoredContext = normalizeContext(restoreDraft.context);
|
|
@@ -1014,7 +1105,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1014
1105
|
textarea.current?.focus({ preventScroll: true });
|
|
1015
1106
|
onDraftRestored?.(restoreDraft.id);
|
|
1016
1107
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
1017
|
-
|
|
1108
|
+
useEffect2(() => {
|
|
1018
1109
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
1019
1110
|
return () => clearTimeout(timer);
|
|
1020
1111
|
}, [adapter, draft]);
|
|
@@ -1038,13 +1129,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1038
1129
|
});
|
|
1039
1130
|
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
1040
1131
|
};
|
|
1041
|
-
const
|
|
1042
|
-
const
|
|
1043
|
-
if (!
|
|
1044
|
-
|
|
1132
|
+
const addImageFiles = (value, source) => {
|
|
1133
|
+
const allFiles = Array.from(value ?? []);
|
|
1134
|
+
if (!allFiles.length) return false;
|
|
1135
|
+
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
1136
|
+
if (files.length !== allFiles.length) {
|
|
1137
|
+
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
1138
|
+
return true;
|
|
1139
|
+
}
|
|
1045
1140
|
if (images.length + files.length > MAX_IMAGE_ITEMS) {
|
|
1046
1141
|
setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
1047
|
-
return;
|
|
1142
|
+
return true;
|
|
1048
1143
|
}
|
|
1049
1144
|
setPicking(true);
|
|
1050
1145
|
setPickerError(null);
|
|
@@ -1052,11 +1147,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1052
1147
|
const next = mergeImages(current, picked);
|
|
1053
1148
|
remember(draft, context, next, queue);
|
|
1054
1149
|
return next;
|
|
1055
|
-
}), (error) => setPickerError(error instanceof Error ? error.message :
|
|
1150
|
+
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
1151
|
+
return true;
|
|
1152
|
+
};
|
|
1153
|
+
const pasteImages = (event) => {
|
|
1154
|
+
if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
|
|
1155
|
+
};
|
|
1156
|
+
const dropImages = (event) => {
|
|
1157
|
+
setDragging(false);
|
|
1158
|
+
if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
|
|
1056
1159
|
};
|
|
1057
1160
|
const send = () => {
|
|
1058
1161
|
const text = draft.trim();
|
|
1059
|
-
if (!text) return;
|
|
1162
|
+
if (!text && !images.length) return;
|
|
1060
1163
|
const message = { text, context, images };
|
|
1061
1164
|
if (queuesNewMessage) updateQueue((items) => [...items, message]);
|
|
1062
1165
|
else if (state.canSend) {
|
|
@@ -1077,7 +1180,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1077
1180
|
] }),
|
|
1078
1181
|
queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
|
|
1079
1182
|
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1080
|
-
item.text,
|
|
1183
|
+
item.text || "Image attachment",
|
|
1081
1184
|
item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
|
|
1082
1185
|
item.context.length + item.images.length,
|
|
1083
1186
|
" attached"
|
|
@@ -1097,7 +1200,16 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1097
1200
|
return next;
|
|
1098
1201
|
}) }),
|
|
1099
1202
|
pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
1100
|
-
/* @__PURE__ */ jsxs3("div", { class:
|
|
1203
|
+
/* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
1204
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
1205
|
+
event.preventDefault();
|
|
1206
|
+
setDragging(true);
|
|
1207
|
+
}
|
|
1208
|
+
}, onDragOver: (event) => {
|
|
1209
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
|
|
1210
|
+
}, onDragLeave: (event) => {
|
|
1211
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
1212
|
+
}, onDrop: dropImages, children: [
|
|
1101
1213
|
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,
|
|
1102
1214
|
/* @__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) => {
|
|
1103
1215
|
const value = event.currentTarget.value;
|
|
@@ -1111,19 +1223,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1111
1223
|
} }),
|
|
1112
1224
|
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1113
1225
|
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,
|
|
1114
|
-
/* @__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 }) })
|
|
1226
|
+
/* @__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 }) })
|
|
1115
1227
|
] })
|
|
1116
1228
|
] })
|
|
1117
1229
|
] });
|
|
1118
1230
|
}
|
|
1119
1231
|
|
|
1120
1232
|
// src/conversation.jsx
|
|
1121
|
-
import { Fragment as
|
|
1122
|
-
import { useEffect as
|
|
1233
|
+
import { Fragment as Fragment3 } from "preact";
|
|
1234
|
+
import { useEffect as useEffect4, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState3 } from "preact/hooks";
|
|
1123
1235
|
|
|
1124
1236
|
// src/markdown.jsx
|
|
1125
1237
|
import MarkdownIt from "markdown-it";
|
|
1126
|
-
import { useEffect as
|
|
1238
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "preact/hooks";
|
|
1127
1239
|
import { jsx as jsx4 } from "preact/jsx-runtime";
|
|
1128
1240
|
var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
|
|
1129
1241
|
var LANGUAGE_LABELS = {
|
|
@@ -1185,8 +1297,8 @@ function setCopyState(button, status) {
|
|
|
1185
1297
|
}
|
|
1186
1298
|
function Markdown({ value, copyText }) {
|
|
1187
1299
|
const html = useMemo(() => markdown.render(value), [value]);
|
|
1188
|
-
const resets =
|
|
1189
|
-
|
|
1300
|
+
const resets = useRef3(/* @__PURE__ */ new Map());
|
|
1301
|
+
useEffect3(() => () => {
|
|
1190
1302
|
for (const timer of resets.current.values()) clearTimeout(timer);
|
|
1191
1303
|
resets.current.clear();
|
|
1192
1304
|
}, []);
|
|
@@ -1215,7 +1327,7 @@ function Markdown({ value, copyText }) {
|
|
|
1215
1327
|
}
|
|
1216
1328
|
|
|
1217
1329
|
// src/conversation.jsx
|
|
1218
|
-
import { Fragment as
|
|
1330
|
+
import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
1219
1331
|
function LoadingStatus({ state, compact = false }) {
|
|
1220
1332
|
const copy = {
|
|
1221
1333
|
connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
|
|
@@ -1264,9 +1376,9 @@ function ContextDisclosure({ context }) {
|
|
|
1264
1376
|
] });
|
|
1265
1377
|
}
|
|
1266
1378
|
function MessageMeta({ entry, adapter }) {
|
|
1267
|
-
const [copyState, setCopyState2] =
|
|
1268
|
-
const reset =
|
|
1269
|
-
|
|
1379
|
+
const [copyState, setCopyState2] = useState3("idle");
|
|
1380
|
+
const reset = useRef4(null);
|
|
1381
|
+
useEffect4(() => () => clearTimeout(reset.current), []);
|
|
1270
1382
|
const date = entry.ts === null ? null : new Date(entry.ts);
|
|
1271
1383
|
const validDate = date && Number.isFinite(date.valueOf()) ? date : null;
|
|
1272
1384
|
if (!validDate && (!adapter?.copyText || !entry.text)) return null;
|
|
@@ -1287,33 +1399,33 @@ function MessageMeta({ entry, adapter }) {
|
|
|
1287
1399
|
] });
|
|
1288
1400
|
}
|
|
1289
1401
|
var TOOL_ICONS = {
|
|
1290
|
-
read: () => /* @__PURE__ */ jsxs4(
|
|
1402
|
+
read: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1291
1403
|
/* @__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" }),
|
|
1292
1404
|
/* @__PURE__ */ jsx5("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
|
|
1293
1405
|
] }),
|
|
1294
|
-
search: () => /* @__PURE__ */ jsxs4(
|
|
1406
|
+
search: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1295
1407
|
/* @__PURE__ */ jsx5("circle", { cx: "8", cy: "8", r: "4.5" }),
|
|
1296
1408
|
/* @__PURE__ */ jsx5("path", { d: "m11.5 11.5 3 3" })
|
|
1297
1409
|
] }),
|
|
1298
|
-
edit: () => /* @__PURE__ */ jsxs4(
|
|
1410
|
+
edit: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1299
1411
|
/* @__PURE__ */ jsx5("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
|
|
1300
1412
|
/* @__PURE__ */ jsx5("path", { d: "m10 5 3 3" })
|
|
1301
1413
|
] }),
|
|
1302
|
-
command: () => /* @__PURE__ */ jsx5(
|
|
1303
|
-
test: () => /* @__PURE__ */ jsxs4(
|
|
1414
|
+
command: () => /* @__PURE__ */ jsx5(Fragment4, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
|
|
1415
|
+
test: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1304
1416
|
/* @__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" }),
|
|
1305
1417
|
/* @__PURE__ */ jsx5("path", { d: "M5 2.5h8" })
|
|
1306
1418
|
] }),
|
|
1307
|
-
web: () => /* @__PURE__ */ jsxs4(
|
|
1419
|
+
web: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1308
1420
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "6.5" }),
|
|
1309
1421
|
/* @__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" })
|
|
1310
1422
|
] }),
|
|
1311
|
-
agent: () => /* @__PURE__ */ jsxs4(
|
|
1423
|
+
agent: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1312
1424
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "6", r: "2.5" }),
|
|
1313
1425
|
/* @__PURE__ */ jsx5("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
|
|
1314
1426
|
] }),
|
|
1315
|
-
plan: () => /* @__PURE__ */ jsx5(
|
|
1316
|
-
other: () => /* @__PURE__ */ jsxs4(
|
|
1427
|
+
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" }) }),
|
|
1428
|
+
other: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1317
1429
|
/* @__PURE__ */ jsx5("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
|
|
1318
1430
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "3.5" })
|
|
1319
1431
|
] })
|
|
@@ -1379,9 +1491,9 @@ function ToolMetrics({ presentation }) {
|
|
|
1379
1491
|
}
|
|
1380
1492
|
function PendingElapsed({ now }) {
|
|
1381
1493
|
const clock = now ?? Date.now;
|
|
1382
|
-
const started =
|
|
1383
|
-
const [elapsed, setElapsed] =
|
|
1384
|
-
|
|
1494
|
+
const started = useRef4(clock());
|
|
1495
|
+
const [elapsed, setElapsed] = useState3(0);
|
|
1496
|
+
useEffect4(() => {
|
|
1385
1497
|
const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
|
|
1386
1498
|
return () => clearInterval(timer);
|
|
1387
1499
|
}, [clock]);
|
|
@@ -1422,7 +1534,7 @@ function SearchPreview({ presentation }) {
|
|
|
1422
1534
|
] }) : null,
|
|
1423
1535
|
lines.length ? /* @__PURE__ */ jsx5("ol", { children: lines.map((line, index) => {
|
|
1424
1536
|
const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
|
|
1425
|
-
return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(
|
|
1537
|
+
return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1426
1538
|
/* @__PURE__ */ jsx5("code", { children: match[1] }),
|
|
1427
1539
|
/* @__PURE__ */ jsxs4("small", { children: [
|
|
1428
1540
|
match[2],
|
|
@@ -1455,9 +1567,9 @@ function ToolPreview({ presentation, entry }) {
|
|
|
1455
1567
|
return presentation.preview ? /* @__PURE__ */ jsx5("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
|
|
1456
1568
|
}
|
|
1457
1569
|
function ToolActions({ presentation, adapter }) {
|
|
1458
|
-
const [copied, setCopied] =
|
|
1459
|
-
const reset =
|
|
1460
|
-
|
|
1570
|
+
const [copied, setCopied] = useState3(false);
|
|
1571
|
+
const reset = useRef4(null);
|
|
1572
|
+
useEffect4(() => () => clearTimeout(reset.current), []);
|
|
1461
1573
|
if (!adapter?.copyText) return null;
|
|
1462
1574
|
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;
|
|
1463
1575
|
const copy = async () => {
|
|
@@ -1504,7 +1616,7 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
1504
1616
|
}
|
|
1505
1617
|
if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx5("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
|
|
1506
1618
|
return /* @__PURE__ */ jsxs4("article", { class: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
|
|
1507
|
-
/* @__PURE__ */ jsx5(MessageImages, { items: entry.images }),
|
|
1619
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: entry.images, adapter }),
|
|
1508
1620
|
/* @__PURE__ */ jsx5(Markdown, { value: entry.text, copyText: adapter?.copyText }),
|
|
1509
1621
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: entry.context }),
|
|
1510
1622
|
entry.truncated ? /* @__PURE__ */ jsx5("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
|
|
@@ -1513,14 +1625,14 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
1513
1625
|
}
|
|
1514
1626
|
function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
1515
1627
|
const presentation = entry.presentation ?? createToolPresentation(entry);
|
|
1516
|
-
const [expanded, setExpanded] =
|
|
1517
|
-
|
|
1628
|
+
const [expanded, setExpanded] = useState3(open || entry.status === "pending");
|
|
1629
|
+
useEffect4(() => {
|
|
1518
1630
|
if (entry.status === "pending") setExpanded(true);
|
|
1519
1631
|
}, [entry.status]);
|
|
1520
1632
|
const target = compactToolTarget(presentation.target, workspace);
|
|
1521
1633
|
const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
|
|
1522
1634
|
const category = presentation.category ?? toolCategory(entry);
|
|
1523
|
-
const summary = /* @__PURE__ */ jsxs4(
|
|
1635
|
+
const summary = /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1524
1636
|
/* @__PURE__ */ jsx5(ToolIcon, { category }),
|
|
1525
1637
|
/* @__PURE__ */ jsx5("strong", { children: toolAction2(entry, category, presentation) }),
|
|
1526
1638
|
target ? /* @__PURE__ */ jsx5("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
|
|
@@ -1548,9 +1660,9 @@ function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
|
1548
1660
|
function ActivityGroup({ entries, state, adapter }) {
|
|
1549
1661
|
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 }) });
|
|
1550
1662
|
const active = entries.some((entry) => entry.status === "pending");
|
|
1551
|
-
const [open, setOpen] =
|
|
1663
|
+
const [open, setOpen] = useState3(active);
|
|
1552
1664
|
const id = useId();
|
|
1553
|
-
|
|
1665
|
+
useEffect4(() => {
|
|
1554
1666
|
if (active) setOpen(true);
|
|
1555
1667
|
}, [active]);
|
|
1556
1668
|
return /* @__PURE__ */ jsxs4("section", { class: "scui-activity", children: [
|
|
@@ -1626,9 +1738,9 @@ function SessionDetails({ semantics }) {
|
|
|
1626
1738
|
}
|
|
1627
1739
|
var conversationMemory = /* @__PURE__ */ new Map();
|
|
1628
1740
|
function ConversationAnnouncements({ state }) {
|
|
1629
|
-
const previousBusy =
|
|
1630
|
-
const [announcement, setAnnouncement] =
|
|
1631
|
-
|
|
1741
|
+
const previousBusy = useRef4(state.busy);
|
|
1742
|
+
const [announcement, setAnnouncement] = useState3("");
|
|
1743
|
+
useEffect4(() => {
|
|
1632
1744
|
if (previousBusy.current && !state.busy && !state.error) {
|
|
1633
1745
|
setAnnouncement(`${harnessDisplayName(state.harness) || "Coding agent"} finished working`);
|
|
1634
1746
|
}
|
|
@@ -1637,13 +1749,13 @@ function ConversationAnnouncements({ state }) {
|
|
|
1637
1749
|
return /* @__PURE__ */ jsx5("span", { class: "scui-sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement });
|
|
1638
1750
|
}
|
|
1639
1751
|
function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null, unreadAfterMessages = null }) {
|
|
1640
|
-
const scroller =
|
|
1752
|
+
const scroller = useRef4(null);
|
|
1641
1753
|
const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
|
|
1642
|
-
const [atBottom, setAtBottom] =
|
|
1643
|
-
const earlierAnchor =
|
|
1644
|
-
const restored =
|
|
1754
|
+
const [atBottom, setAtBottom] = useState3(remembered.atBottom);
|
|
1755
|
+
const earlierAnchor = useRef4(null);
|
|
1756
|
+
const restored = useRef4(false);
|
|
1645
1757
|
const blocks = groupConversation(state.transcript);
|
|
1646
|
-
const unreadBoundary =
|
|
1758
|
+
const unreadBoundary = useRef4(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
|
|
1647
1759
|
const unreadBlock = unreadBoundary.current === null ? -1 : blocks.findIndex((block) => {
|
|
1648
1760
|
const entries = block.kind === "activity" ? block.entries : [block.entry];
|
|
1649
1761
|
return entries.some((entry) => Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > unreadBoundary.current);
|
|
@@ -1702,12 +1814,12 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1702
1814
|
}, children: "Load earlier messages" }) : null,
|
|
1703
1815
|
!blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx5(LoadingStatus, { state }) : null,
|
|
1704
1816
|
!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,
|
|
1705
|
-
blocks.map((block, index) => /* @__PURE__ */ jsxs4(
|
|
1817
|
+
blocks.map((block, index) => /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1706
1818
|
index === unreadBlock ? /* @__PURE__ */ jsx5("div", { class: "scui-unread-divider", role: "separator", "aria-label": "New messages", children: /* @__PURE__ */ jsx5("span", { children: "New" }) }) : null,
|
|
1707
1819
|
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 })
|
|
1708
1820
|
] }, block.id)),
|
|
1709
1821
|
pendingMessage ? /* @__PURE__ */ jsxs4("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
|
|
1710
|
-
/* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images }),
|
|
1822
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images, adapter }),
|
|
1711
1823
|
/* @__PURE__ */ jsx5(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
|
|
1712
1824
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: pendingMessage.context }),
|
|
1713
1825
|
/* @__PURE__ */ jsxs4("footer", { children: [
|
|
@@ -1738,7 +1850,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1738
1850
|
}
|
|
1739
1851
|
|
|
1740
1852
|
// src/logo.jsx
|
|
1741
|
-
import { useEffect as
|
|
1853
|
+
import { useEffect as useEffect5 } from "preact/hooks";
|
|
1742
1854
|
import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
1743
1855
|
var LOGOS = {
|
|
1744
1856
|
"claude-code": {
|
|
@@ -1786,7 +1898,7 @@ var LOGOS = {
|
|
|
1786
1898
|
};
|
|
1787
1899
|
function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
1788
1900
|
const logo = LOGOS[id];
|
|
1789
|
-
|
|
1901
|
+
useEffect5(() => {
|
|
1790
1902
|
if (!logo) onMissingLogo?.(id);
|
|
1791
1903
|
}, [id, logo, onMissingLogo]);
|
|
1792
1904
|
if (!logo) return null;
|
|
@@ -1797,7 +1909,7 @@ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
|
1797
1909
|
}
|
|
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);
|