@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/messenger.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/messenger.jsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "preact/hooks";
|
|
3
3
|
|
|
4
4
|
// core.mjs
|
|
5
5
|
var HARNESS_NAMES = Object.freeze({
|
|
@@ -758,7 +758,7 @@ function isSendKey(event) {
|
|
|
758
758
|
}
|
|
759
759
|
|
|
760
760
|
// src/composer.jsx
|
|
761
|
-
import { useEffect, useRef, useState } from "preact/hooks";
|
|
761
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "preact/hooks";
|
|
762
762
|
|
|
763
763
|
// src/memory.js
|
|
764
764
|
var MEMORY_LIMIT = 100;
|
|
@@ -818,7 +818,8 @@ function UiIcon({ name, size = 16, class: className = "" }) {
|
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
// src/context.jsx
|
|
821
|
-
import {
|
|
821
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
822
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
822
823
|
var MAX_CONTEXT_ITEMS = 32;
|
|
823
824
|
var MAX_IMAGE_ITEMS = 4;
|
|
824
825
|
var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
@@ -917,12 +918,101 @@ function ImageTray({ items, onRemove }) {
|
|
|
917
918
|
onRemove ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove image ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) }) : null
|
|
918
919
|
] }, item.id ?? `${item.label}:${index}`)) });
|
|
919
920
|
}
|
|
920
|
-
function
|
|
921
|
+
function imageFilename(label) {
|
|
922
|
+
const value = label.trim().replace(/[\\/:*?"<>|]+/g, "-");
|
|
923
|
+
return value || "image";
|
|
924
|
+
}
|
|
925
|
+
function ImageViewer({ items, index, adapter, onChange, onClose }) {
|
|
926
|
+
const dialog = useRef(null);
|
|
927
|
+
const reset = useRef(null);
|
|
928
|
+
const [copyState, setCopyState2] = useState("idle");
|
|
929
|
+
const item = items[index];
|
|
930
|
+
const remote = item?.url?.startsWith("http://") || item?.url?.startsWith("https://");
|
|
931
|
+
useEffect(() => {
|
|
932
|
+
if (!dialog.current?.open) dialog.current?.showModal();
|
|
933
|
+
return () => clearTimeout(reset.current);
|
|
934
|
+
}, []);
|
|
935
|
+
useEffect(() => {
|
|
936
|
+
clearTimeout(reset.current);
|
|
937
|
+
setCopyState2("idle");
|
|
938
|
+
}, [index]);
|
|
939
|
+
if (!item?.url) return null;
|
|
940
|
+
const move = (amount) => onChange((index + amount + items.length) % items.length);
|
|
941
|
+
const copy = async () => {
|
|
942
|
+
try {
|
|
943
|
+
await adapter.copyText(item.url);
|
|
944
|
+
setCopyState2("copied");
|
|
945
|
+
} catch {
|
|
946
|
+
setCopyState2("failed");
|
|
947
|
+
}
|
|
948
|
+
clearTimeout(reset.current);
|
|
949
|
+
reset.current = setTimeout(() => setCopyState2("idle"), 1500);
|
|
950
|
+
};
|
|
951
|
+
const close = () => dialog.current?.close();
|
|
952
|
+
return /* @__PURE__ */ jsxs2(
|
|
953
|
+
"dialog",
|
|
954
|
+
{
|
|
955
|
+
ref: dialog,
|
|
956
|
+
class: "scui-image-viewer",
|
|
957
|
+
"aria-label": `Image preview: ${item.label}`,
|
|
958
|
+
onClose,
|
|
959
|
+
onClick: (event) => {
|
|
960
|
+
if (event.target === event.currentTarget) close();
|
|
961
|
+
},
|
|
962
|
+
onKeyDown: (event) => {
|
|
963
|
+
if (items.length < 2 || !["ArrowLeft", "ArrowRight"].includes(event.key)) return;
|
|
964
|
+
event.preventDefault();
|
|
965
|
+
move(event.key === "ArrowLeft" ? -1 : 1);
|
|
966
|
+
},
|
|
967
|
+
children: [
|
|
968
|
+
/* @__PURE__ */ jsxs2("header", { children: [
|
|
969
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
970
|
+
/* @__PURE__ */ jsx2("strong", { children: item.label }),
|
|
971
|
+
items.length > 1 ? /* @__PURE__ */ jsxs2("small", { children: [
|
|
972
|
+
index + 1,
|
|
973
|
+
" of ",
|
|
974
|
+
items.length
|
|
975
|
+
] }) : null
|
|
976
|
+
] }),
|
|
977
|
+
/* @__PURE__ */ jsxs2("nav", { "aria-label": "Image actions", children: [
|
|
978
|
+
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,
|
|
979
|
+
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 }) }),
|
|
980
|
+
/* @__PURE__ */ jsx2("button", { type: "button", "aria-label": "Close image preview", title: "Close", onClick: close, children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 17 }) })
|
|
981
|
+
] })
|
|
982
|
+
] }),
|
|
983
|
+
/* @__PURE__ */ jsxs2("figure", { children: [
|
|
984
|
+
/* @__PURE__ */ jsx2("img", { src: item.url, alt: item.label }),
|
|
985
|
+
items.length > 1 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
986
|
+
/* @__PURE__ */ jsx2("button", { type: "button", class: "scui-image-previous", "aria-label": "Previous image", onClick: () => move(-1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) }),
|
|
987
|
+
/* @__PURE__ */ jsx2("button", { type: "button", class: "scui-image-next", "aria-label": "Next image", onClick: () => move(1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) })
|
|
988
|
+
] }) : null
|
|
989
|
+
] })
|
|
990
|
+
]
|
|
991
|
+
}
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
function MessageImages({ items, adapter }) {
|
|
995
|
+
const [active, setActive] = useState(null);
|
|
996
|
+
const opener = useRef(null);
|
|
921
997
|
if (!items?.length) return null;
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
998
|
+
const viewable = items.filter((item) => item.url);
|
|
999
|
+
const close = () => {
|
|
1000
|
+
setActive(null);
|
|
1001
|
+
requestAnimationFrame(() => opener.current?.focus({ preventScroll: true }));
|
|
1002
|
+
};
|
|
1003
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1004
|
+
/* @__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) => {
|
|
1005
|
+
opener.current = event.currentTarget;
|
|
1006
|
+
setActive(viewable.indexOf(item));
|
|
1007
|
+
}, children: /* @__PURE__ */ jsx2("img", { src: item.url, alt: "" }) }, item.id ?? `${item.label}:${index}`) : /* @__PURE__ */ jsxs2("span", { "data-unavailable": "true", children: [
|
|
1008
|
+
/* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 14 }),
|
|
1009
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
1010
|
+
/* @__PURE__ */ jsx2("strong", { children: item.label }),
|
|
1011
|
+
/* @__PURE__ */ jsx2("small", { children: "Preview unavailable" })
|
|
1012
|
+
] })
|
|
1013
|
+
] }, item.id ?? `${item.label}:${index}`)) }),
|
|
1014
|
+
active !== null && viewable[active] ? /* @__PURE__ */ jsx2(ImageViewer, { items: viewable, index: active, adapter, onChange: setActive, onClose: close }) : null
|
|
1015
|
+
] });
|
|
926
1016
|
}
|
|
927
1017
|
|
|
928
1018
|
// src/textarea.js
|
|
@@ -964,17 +1054,18 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
964
1054
|
}
|
|
965
1055
|
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
|
|
966
1056
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
967
|
-
const [draft, setDraft] =
|
|
968
|
-
const [context, setContext] =
|
|
969
|
-
const [images, setImages] =
|
|
970
|
-
const [queue, setQueue] =
|
|
971
|
-
const [dispatching, setDispatching] =
|
|
972
|
-
const [picking, setPicking] =
|
|
973
|
-
const [
|
|
974
|
-
const
|
|
1057
|
+
const [draft, setDraft] = useState2(remembered.draft);
|
|
1058
|
+
const [context, setContext] = useState2(remembered.context ?? []);
|
|
1059
|
+
const [images, setImages] = useState2(remembered.images ?? []);
|
|
1060
|
+
const [queue, setQueue] = useState2((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
|
|
1061
|
+
const [dispatching, setDispatching] = useState2(false);
|
|
1062
|
+
const [picking, setPicking] = useState2(false);
|
|
1063
|
+
const [dragging, setDragging] = useState2(false);
|
|
1064
|
+
const [pickerError, setPickerError] = useState2(null);
|
|
1065
|
+
const textarea = useRef2(null);
|
|
975
1066
|
useAutosizeTextarea(textarea, draft);
|
|
976
1067
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
977
|
-
|
|
1068
|
+
useEffect2(() => {
|
|
978
1069
|
remember(draft, context, images, queue);
|
|
979
1070
|
}, [draft, context, images, memoryKey, queue]);
|
|
980
1071
|
const updateQueue = (update) => setQueue((items) => {
|
|
@@ -984,7 +1075,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
984
1075
|
});
|
|
985
1076
|
const queueBlocked = state.busy || pendingStatus !== null || dispatching;
|
|
986
1077
|
const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
|
|
987
|
-
|
|
1078
|
+
useEffect2(() => {
|
|
988
1079
|
if (!queueBlocked && state.canSend && queue.length) {
|
|
989
1080
|
const [next, ...rest] = queue;
|
|
990
1081
|
setDispatching(true);
|
|
@@ -994,13 +1085,13 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
994
1085
|
adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {}, ...next.images.length ? { images: next.images } : {} });
|
|
995
1086
|
}
|
|
996
1087
|
}, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
|
|
997
|
-
|
|
1088
|
+
useEffect2(() => {
|
|
998
1089
|
if (pendingStatus !== null || state.busy) setDispatching(false);
|
|
999
1090
|
}, [pendingStatus, state.busy]);
|
|
1000
|
-
|
|
1091
|
+
useEffect2(() => {
|
|
1001
1092
|
textarea.current?.focus({ preventScroll: true });
|
|
1002
1093
|
}, [memoryKey]);
|
|
1003
|
-
|
|
1094
|
+
useEffect2(() => {
|
|
1004
1095
|
if (!restoreDraft) return;
|
|
1005
1096
|
setDraft(restoreDraft.text);
|
|
1006
1097
|
const restoredContext = normalizeContext(restoreDraft.context);
|
|
@@ -1011,7 +1102,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1011
1102
|
textarea.current?.focus({ preventScroll: true });
|
|
1012
1103
|
onDraftRestored?.(restoreDraft.id);
|
|
1013
1104
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
1014
|
-
|
|
1105
|
+
useEffect2(() => {
|
|
1015
1106
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
1016
1107
|
return () => clearTimeout(timer);
|
|
1017
1108
|
}, [adapter, draft]);
|
|
@@ -1035,13 +1126,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1035
1126
|
});
|
|
1036
1127
|
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
1037
1128
|
};
|
|
1038
|
-
const
|
|
1039
|
-
const
|
|
1040
|
-
if (!
|
|
1041
|
-
|
|
1129
|
+
const addImageFiles = (value, source) => {
|
|
1130
|
+
const allFiles = Array.from(value ?? []);
|
|
1131
|
+
if (!allFiles.length) return false;
|
|
1132
|
+
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
1133
|
+
if (files.length !== allFiles.length) {
|
|
1134
|
+
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
1135
|
+
return true;
|
|
1136
|
+
}
|
|
1042
1137
|
if (images.length + files.length > MAX_IMAGE_ITEMS) {
|
|
1043
1138
|
setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
1044
|
-
return;
|
|
1139
|
+
return true;
|
|
1045
1140
|
}
|
|
1046
1141
|
setPicking(true);
|
|
1047
1142
|
setPickerError(null);
|
|
@@ -1049,11 +1144,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1049
1144
|
const next = mergeImages(current, picked);
|
|
1050
1145
|
remember(draft, context, next, queue);
|
|
1051
1146
|
return next;
|
|
1052
|
-
}), (error) => setPickerError(error instanceof Error ? error.message :
|
|
1147
|
+
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
1148
|
+
return true;
|
|
1149
|
+
};
|
|
1150
|
+
const pasteImages = (event) => {
|
|
1151
|
+
if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
|
|
1152
|
+
};
|
|
1153
|
+
const dropImages = (event) => {
|
|
1154
|
+
setDragging(false);
|
|
1155
|
+
if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
|
|
1053
1156
|
};
|
|
1054
1157
|
const send = () => {
|
|
1055
1158
|
const text = draft.trim();
|
|
1056
|
-
if (!text) return;
|
|
1159
|
+
if (!text && !images.length) return;
|
|
1057
1160
|
const message = { text, context, images };
|
|
1058
1161
|
if (queuesNewMessage) updateQueue((items) => [...items, message]);
|
|
1059
1162
|
else if (state.canSend) {
|
|
@@ -1074,7 +1177,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1074
1177
|
] }),
|
|
1075
1178
|
queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
|
|
1076
1179
|
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1077
|
-
item.text,
|
|
1180
|
+
item.text || "Image attachment",
|
|
1078
1181
|
item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
|
|
1079
1182
|
item.context.length + item.images.length,
|
|
1080
1183
|
" attached"
|
|
@@ -1094,7 +1197,16 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1094
1197
|
return next;
|
|
1095
1198
|
}) }),
|
|
1096
1199
|
pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
1097
|
-
/* @__PURE__ */ jsxs3("div", { class:
|
|
1200
|
+
/* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
1201
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
1202
|
+
event.preventDefault();
|
|
1203
|
+
setDragging(true);
|
|
1204
|
+
}
|
|
1205
|
+
}, onDragOver: (event) => {
|
|
1206
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
|
|
1207
|
+
}, onDragLeave: (event) => {
|
|
1208
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
1209
|
+
}, onDrop: dropImages, children: [
|
|
1098
1210
|
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,
|
|
1099
1211
|
/* @__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) => {
|
|
1100
1212
|
const value = event.currentTarget.value;
|
|
@@ -1108,19 +1220,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1108
1220
|
} }),
|
|
1109
1221
|
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1110
1222
|
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,
|
|
1111
|
-
/* @__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 }) })
|
|
1223
|
+
/* @__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 }) })
|
|
1112
1224
|
] })
|
|
1113
1225
|
] })
|
|
1114
1226
|
] });
|
|
1115
1227
|
}
|
|
1116
1228
|
|
|
1117
1229
|
// src/conversation.jsx
|
|
1118
|
-
import { Fragment as
|
|
1119
|
-
import { useEffect as
|
|
1230
|
+
import { Fragment as Fragment3 } from "preact";
|
|
1231
|
+
import { useEffect as useEffect4, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState3 } from "preact/hooks";
|
|
1120
1232
|
|
|
1121
1233
|
// src/markdown.jsx
|
|
1122
1234
|
import MarkdownIt from "markdown-it";
|
|
1123
|
-
import { useEffect as
|
|
1235
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "preact/hooks";
|
|
1124
1236
|
import { jsx as jsx4 } from "preact/jsx-runtime";
|
|
1125
1237
|
var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
|
|
1126
1238
|
var LANGUAGE_LABELS = {
|
|
@@ -1182,8 +1294,8 @@ function setCopyState(button, status) {
|
|
|
1182
1294
|
}
|
|
1183
1295
|
function Markdown({ value, copyText }) {
|
|
1184
1296
|
const html = useMemo(() => markdown.render(value), [value]);
|
|
1185
|
-
const resets =
|
|
1186
|
-
|
|
1297
|
+
const resets = useRef3(/* @__PURE__ */ new Map());
|
|
1298
|
+
useEffect3(() => () => {
|
|
1187
1299
|
for (const timer of resets.current.values()) clearTimeout(timer);
|
|
1188
1300
|
resets.current.clear();
|
|
1189
1301
|
}, []);
|
|
@@ -1212,7 +1324,7 @@ function Markdown({ value, copyText }) {
|
|
|
1212
1324
|
}
|
|
1213
1325
|
|
|
1214
1326
|
// src/conversation.jsx
|
|
1215
|
-
import { Fragment as
|
|
1327
|
+
import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
1216
1328
|
function LoadingStatus({ state, compact = false }) {
|
|
1217
1329
|
const copy = {
|
|
1218
1330
|
connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
|
|
@@ -1261,9 +1373,9 @@ function ContextDisclosure({ context }) {
|
|
|
1261
1373
|
] });
|
|
1262
1374
|
}
|
|
1263
1375
|
function MessageMeta({ entry, adapter }) {
|
|
1264
|
-
const [copyState, setCopyState2] =
|
|
1265
|
-
const reset =
|
|
1266
|
-
|
|
1376
|
+
const [copyState, setCopyState2] = useState3("idle");
|
|
1377
|
+
const reset = useRef4(null);
|
|
1378
|
+
useEffect4(() => () => clearTimeout(reset.current), []);
|
|
1267
1379
|
const date = entry.ts === null ? null : new Date(entry.ts);
|
|
1268
1380
|
const validDate = date && Number.isFinite(date.valueOf()) ? date : null;
|
|
1269
1381
|
if (!validDate && (!adapter?.copyText || !entry.text)) return null;
|
|
@@ -1284,33 +1396,33 @@ function MessageMeta({ entry, adapter }) {
|
|
|
1284
1396
|
] });
|
|
1285
1397
|
}
|
|
1286
1398
|
var TOOL_ICONS = {
|
|
1287
|
-
read: () => /* @__PURE__ */ jsxs4(
|
|
1399
|
+
read: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1288
1400
|
/* @__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" }),
|
|
1289
1401
|
/* @__PURE__ */ jsx5("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
|
|
1290
1402
|
] }),
|
|
1291
|
-
search: () => /* @__PURE__ */ jsxs4(
|
|
1403
|
+
search: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1292
1404
|
/* @__PURE__ */ jsx5("circle", { cx: "8", cy: "8", r: "4.5" }),
|
|
1293
1405
|
/* @__PURE__ */ jsx5("path", { d: "m11.5 11.5 3 3" })
|
|
1294
1406
|
] }),
|
|
1295
|
-
edit: () => /* @__PURE__ */ jsxs4(
|
|
1407
|
+
edit: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1296
1408
|
/* @__PURE__ */ jsx5("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
|
|
1297
1409
|
/* @__PURE__ */ jsx5("path", { d: "m10 5 3 3" })
|
|
1298
1410
|
] }),
|
|
1299
|
-
command: () => /* @__PURE__ */ jsx5(
|
|
1300
|
-
test: () => /* @__PURE__ */ jsxs4(
|
|
1411
|
+
command: () => /* @__PURE__ */ jsx5(Fragment4, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
|
|
1412
|
+
test: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1301
1413
|
/* @__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" }),
|
|
1302
1414
|
/* @__PURE__ */ jsx5("path", { d: "M5 2.5h8" })
|
|
1303
1415
|
] }),
|
|
1304
|
-
web: () => /* @__PURE__ */ jsxs4(
|
|
1416
|
+
web: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1305
1417
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "6.5" }),
|
|
1306
1418
|
/* @__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" })
|
|
1307
1419
|
] }),
|
|
1308
|
-
agent: () => /* @__PURE__ */ jsxs4(
|
|
1420
|
+
agent: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1309
1421
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "6", r: "2.5" }),
|
|
1310
1422
|
/* @__PURE__ */ jsx5("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
|
|
1311
1423
|
] }),
|
|
1312
|
-
plan: () => /* @__PURE__ */ jsx5(
|
|
1313
|
-
other: () => /* @__PURE__ */ jsxs4(
|
|
1424
|
+
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" }) }),
|
|
1425
|
+
other: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1314
1426
|
/* @__PURE__ */ jsx5("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
|
|
1315
1427
|
/* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "3.5" })
|
|
1316
1428
|
] })
|
|
@@ -1376,9 +1488,9 @@ function ToolMetrics({ presentation }) {
|
|
|
1376
1488
|
}
|
|
1377
1489
|
function PendingElapsed({ now }) {
|
|
1378
1490
|
const clock = now ?? Date.now;
|
|
1379
|
-
const started =
|
|
1380
|
-
const [elapsed, setElapsed] =
|
|
1381
|
-
|
|
1491
|
+
const started = useRef4(clock());
|
|
1492
|
+
const [elapsed, setElapsed] = useState3(0);
|
|
1493
|
+
useEffect4(() => {
|
|
1382
1494
|
const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
|
|
1383
1495
|
return () => clearInterval(timer);
|
|
1384
1496
|
}, [clock]);
|
|
@@ -1419,7 +1531,7 @@ function SearchPreview({ presentation }) {
|
|
|
1419
1531
|
] }) : null,
|
|
1420
1532
|
lines.length ? /* @__PURE__ */ jsx5("ol", { children: lines.map((line, index) => {
|
|
1421
1533
|
const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
|
|
1422
|
-
return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(
|
|
1534
|
+
return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1423
1535
|
/* @__PURE__ */ jsx5("code", { children: match[1] }),
|
|
1424
1536
|
/* @__PURE__ */ jsxs4("small", { children: [
|
|
1425
1537
|
match[2],
|
|
@@ -1452,9 +1564,9 @@ function ToolPreview({ presentation, entry }) {
|
|
|
1452
1564
|
return presentation.preview ? /* @__PURE__ */ jsx5("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
|
|
1453
1565
|
}
|
|
1454
1566
|
function ToolActions({ presentation, adapter }) {
|
|
1455
|
-
const [copied, setCopied] =
|
|
1456
|
-
const reset =
|
|
1457
|
-
|
|
1567
|
+
const [copied, setCopied] = useState3(false);
|
|
1568
|
+
const reset = useRef4(null);
|
|
1569
|
+
useEffect4(() => () => clearTimeout(reset.current), []);
|
|
1458
1570
|
if (!adapter?.copyText) return null;
|
|
1459
1571
|
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;
|
|
1460
1572
|
const copy = async () => {
|
|
@@ -1501,7 +1613,7 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
1501
1613
|
}
|
|
1502
1614
|
if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx5("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
|
|
1503
1615
|
return /* @__PURE__ */ jsxs4("article", { class: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
|
|
1504
|
-
/* @__PURE__ */ jsx5(MessageImages, { items: entry.images }),
|
|
1616
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: entry.images, adapter }),
|
|
1505
1617
|
/* @__PURE__ */ jsx5(Markdown, { value: entry.text, copyText: adapter?.copyText }),
|
|
1506
1618
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: entry.context }),
|
|
1507
1619
|
entry.truncated ? /* @__PURE__ */ jsx5("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
|
|
@@ -1510,14 +1622,14 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
1510
1622
|
}
|
|
1511
1623
|
function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
1512
1624
|
const presentation = entry.presentation ?? createToolPresentation(entry);
|
|
1513
|
-
const [expanded, setExpanded] =
|
|
1514
|
-
|
|
1625
|
+
const [expanded, setExpanded] = useState3(open || entry.status === "pending");
|
|
1626
|
+
useEffect4(() => {
|
|
1515
1627
|
if (entry.status === "pending") setExpanded(true);
|
|
1516
1628
|
}, [entry.status]);
|
|
1517
1629
|
const target = compactToolTarget(presentation.target, workspace);
|
|
1518
1630
|
const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
|
|
1519
1631
|
const category = presentation.category ?? toolCategory(entry);
|
|
1520
|
-
const summary = /* @__PURE__ */ jsxs4(
|
|
1632
|
+
const summary = /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1521
1633
|
/* @__PURE__ */ jsx5(ToolIcon, { category }),
|
|
1522
1634
|
/* @__PURE__ */ jsx5("strong", { children: toolAction2(entry, category, presentation) }),
|
|
1523
1635
|
target ? /* @__PURE__ */ jsx5("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
|
|
@@ -1545,9 +1657,9 @@ function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
|
1545
1657
|
function ActivityGroup({ entries, state, adapter }) {
|
|
1546
1658
|
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 }) });
|
|
1547
1659
|
const active = entries.some((entry) => entry.status === "pending");
|
|
1548
|
-
const [open, setOpen] =
|
|
1660
|
+
const [open, setOpen] = useState3(active);
|
|
1549
1661
|
const id = useId();
|
|
1550
|
-
|
|
1662
|
+
useEffect4(() => {
|
|
1551
1663
|
if (active) setOpen(true);
|
|
1552
1664
|
}, [active]);
|
|
1553
1665
|
return /* @__PURE__ */ jsxs4("section", { class: "scui-activity", children: [
|
|
@@ -1623,9 +1735,9 @@ function SessionDetails({ semantics }) {
|
|
|
1623
1735
|
}
|
|
1624
1736
|
var conversationMemory = /* @__PURE__ */ new Map();
|
|
1625
1737
|
function ConversationAnnouncements({ state }) {
|
|
1626
|
-
const previousBusy =
|
|
1627
|
-
const [announcement, setAnnouncement] =
|
|
1628
|
-
|
|
1738
|
+
const previousBusy = useRef4(state.busy);
|
|
1739
|
+
const [announcement, setAnnouncement] = useState3("");
|
|
1740
|
+
useEffect4(() => {
|
|
1629
1741
|
if (previousBusy.current && !state.busy && !state.error) {
|
|
1630
1742
|
setAnnouncement(`${harnessDisplayName(state.harness) || "Coding agent"} finished working`);
|
|
1631
1743
|
}
|
|
@@ -1634,13 +1746,13 @@ function ConversationAnnouncements({ state }) {
|
|
|
1634
1746
|
return /* @__PURE__ */ jsx5("span", { class: "scui-sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement });
|
|
1635
1747
|
}
|
|
1636
1748
|
function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null, unreadAfterMessages = null }) {
|
|
1637
|
-
const scroller =
|
|
1749
|
+
const scroller = useRef4(null);
|
|
1638
1750
|
const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
|
|
1639
|
-
const [atBottom, setAtBottom] =
|
|
1640
|
-
const earlierAnchor =
|
|
1641
|
-
const restored =
|
|
1751
|
+
const [atBottom, setAtBottom] = useState3(remembered.atBottom);
|
|
1752
|
+
const earlierAnchor = useRef4(null);
|
|
1753
|
+
const restored = useRef4(false);
|
|
1642
1754
|
const blocks = groupConversation(state.transcript);
|
|
1643
|
-
const unreadBoundary =
|
|
1755
|
+
const unreadBoundary = useRef4(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
|
|
1644
1756
|
const unreadBlock = unreadBoundary.current === null ? -1 : blocks.findIndex((block) => {
|
|
1645
1757
|
const entries = block.kind === "activity" ? block.entries : [block.entry];
|
|
1646
1758
|
return entries.some((entry) => Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > unreadBoundary.current);
|
|
@@ -1699,12 +1811,12 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1699
1811
|
}, children: "Load earlier messages" }) : null,
|
|
1700
1812
|
!blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx5(LoadingStatus, { state }) : null,
|
|
1701
1813
|
!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,
|
|
1702
|
-
blocks.map((block, index) => /* @__PURE__ */ jsxs4(
|
|
1814
|
+
blocks.map((block, index) => /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1703
1815
|
index === unreadBlock ? /* @__PURE__ */ jsx5("div", { class: "scui-unread-divider", role: "separator", "aria-label": "New messages", children: /* @__PURE__ */ jsx5("span", { children: "New" }) }) : null,
|
|
1704
1816
|
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 })
|
|
1705
1817
|
] }, block.id)),
|
|
1706
1818
|
pendingMessage ? /* @__PURE__ */ jsxs4("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
|
|
1707
|
-
/* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images }),
|
|
1819
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images, adapter }),
|
|
1708
1820
|
/* @__PURE__ */ jsx5(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
|
|
1709
1821
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: pendingMessage.context }),
|
|
1710
1822
|
/* @__PURE__ */ jsxs4("footer", { children: [
|
|
@@ -1735,7 +1847,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1735
1847
|
}
|
|
1736
1848
|
|
|
1737
1849
|
// src/logo.jsx
|
|
1738
|
-
import { useEffect as
|
|
1850
|
+
import { useEffect as useEffect5 } from "preact/hooks";
|
|
1739
1851
|
import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
1740
1852
|
var LOGOS = {
|
|
1741
1853
|
"claude-code": {
|
|
@@ -1783,7 +1895,7 @@ var LOGOS = {
|
|
|
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,7 +1906,7 @@ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
|
1794
1906
|
}
|
|
1795
1907
|
|
|
1796
1908
|
// src/sessions.jsx
|
|
1797
|
-
import { useEffect as
|
|
1909
|
+
import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef5, useState as useState4 } from "preact/hooks";
|
|
1798
1910
|
import { jsx as jsx7, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
1799
1911
|
var sessionListMemory = /* @__PURE__ */ new Map();
|
|
1800
1912
|
function SessionRow({ row, state, onOpen }) {
|
|
@@ -1816,21 +1928,21 @@ function SessionRow({ row, state, onOpen }) {
|
|
|
1816
1928
|
}
|
|
1817
1929
|
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
|
|
1818
1930
|
const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
|
|
1819
|
-
const [query, setQuery] =
|
|
1820
|
-
const [loadingMore, setLoadingMore] =
|
|
1821
|
-
const root =
|
|
1822
|
-
const rowScroller =
|
|
1931
|
+
const [query, setQuery] = useState4(remembered.query);
|
|
1932
|
+
const [loadingMore, setLoadingMore] = useState4(false);
|
|
1933
|
+
const root = useRef5(null);
|
|
1934
|
+
const rowScroller = useRef5(null);
|
|
1823
1935
|
const rows = filterSessions(state.sessions, query);
|
|
1824
1936
|
const Row = components.SessionRow ?? SessionRow;
|
|
1825
1937
|
useLayoutEffect3(() => {
|
|
1826
1938
|
if (rowScroller.current) rowScroller.current.scrollTop = remembered.top;
|
|
1827
1939
|
}, [memoryKey]);
|
|
1828
|
-
|
|
1940
|
+
useEffect6(() => {
|
|
1829
1941
|
if (!focusKey || !root.current) return;
|
|
1830
1942
|
const target = focusKey === "@new" ? root.current.querySelector('[data-list-focus="new"]') : [...root.current.querySelectorAll("[data-session-key]")].find((element) => element.dataset.sessionKey === focusKey);
|
|
1831
1943
|
(target ?? root.current.querySelector("input,button"))?.focus({ preventScroll: true });
|
|
1832
1944
|
}, [focusKey, rows.length]);
|
|
1833
|
-
|
|
1945
|
+
useEffect6(() => {
|
|
1834
1946
|
if (loadingMore) setLoadingMore(false);
|
|
1835
1947
|
}, [state.error, state.history.hasMoreSessions, state.sessions.length]);
|
|
1836
1948
|
const loadMore = () => {
|
|
@@ -1916,10 +2028,10 @@ function Receipt({ state, adapter }) {
|
|
|
1916
2028
|
return null;
|
|
1917
2029
|
}
|
|
1918
2030
|
function ConversationActions({ state, adapter, actionPending }) {
|
|
1919
|
-
const [open, setOpen] =
|
|
1920
|
-
const root =
|
|
1921
|
-
const panel =
|
|
1922
|
-
const trigger =
|
|
2031
|
+
const [open, setOpen] = useState5(false);
|
|
2032
|
+
const root = useRef6(null);
|
|
2033
|
+
const panel = useRef6(null);
|
|
2034
|
+
const trigger = useRef6(null);
|
|
1923
2035
|
const menuId = useId2();
|
|
1924
2036
|
const targets = state.harnesses.filter((item) => item.startable);
|
|
1925
2037
|
const groups = [
|
|
@@ -1940,7 +2052,7 @@ function ConversationActions({ state, adapter, actionPending }) {
|
|
|
1940
2052
|
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 } })) : []
|
|
1941
2053
|
}
|
|
1942
2054
|
].filter((group) => group.items.length);
|
|
1943
|
-
|
|
2055
|
+
useEffect7(() => {
|
|
1944
2056
|
if (!open) return;
|
|
1945
2057
|
panel.current?.querySelector("button:not(:disabled)")?.focus({ preventScroll: true });
|
|
1946
2058
|
const dismiss = (event) => {
|
|
@@ -1990,12 +2102,12 @@ function ConversationActions({ state, adapter, actionPending }) {
|
|
|
1990
2102
|
] });
|
|
1991
2103
|
}
|
|
1992
2104
|
function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose }) {
|
|
1993
|
-
const back =
|
|
2105
|
+
const back = useRef6(null);
|
|
1994
2106
|
const harness = state.attached?.harness ?? state.harness;
|
|
1995
2107
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
1996
2108
|
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";
|
|
1997
2109
|
const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce;
|
|
1998
|
-
|
|
2110
|
+
useEffect7(() => {
|
|
1999
2111
|
if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
|
|
2000
2112
|
}, []);
|
|
2001
2113
|
return /* @__PURE__ */ jsxs7("header", { class: "scui-head scui-chat-head", children: [
|
|
@@ -2017,24 +2129,24 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2017
2129
|
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
|
|
2018
2130
|
const Header = slots.header;
|
|
2019
2131
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
2020
|
-
const [pending, setPendingState] =
|
|
2021
|
-
const [pendingAction, setPendingAction] =
|
|
2022
|
-
const [restoreDraft, setRestoreDraft] =
|
|
2023
|
-
const restoreSequence =
|
|
2024
|
-
const actionSequence =
|
|
2025
|
-
const acknowledged =
|
|
2132
|
+
const [pending, setPendingState] = useState5(() => pendingMessageMemory.get(memoryKey) ?? null);
|
|
2133
|
+
const [pendingAction, setPendingAction] = useState5(null);
|
|
2134
|
+
const [restoreDraft, setRestoreDraft] = useState5(null);
|
|
2135
|
+
const restoreSequence = useRef6(0);
|
|
2136
|
+
const actionSequence = useRef6(0);
|
|
2137
|
+
const acknowledged = useRef6(/* @__PURE__ */ new Set());
|
|
2026
2138
|
const setPending = (update) => setPendingState((current) => {
|
|
2027
2139
|
const next = typeof update === "function" ? update(current) : update;
|
|
2028
2140
|
if (next) boundedSet(pendingMessageMemory, memoryKey, next);
|
|
2029
2141
|
else pendingMessageMemory.delete(memoryKey);
|
|
2030
2142
|
return next;
|
|
2031
2143
|
});
|
|
2032
|
-
|
|
2144
|
+
useEffect7(() => {
|
|
2033
2145
|
setPendingState(pendingMessageMemory.get(memoryKey) ?? null);
|
|
2034
2146
|
setPendingAction(null);
|
|
2035
2147
|
setRestoreDraft(null);
|
|
2036
2148
|
}, [memoryKey]);
|
|
2037
|
-
|
|
2149
|
+
useEffect7(() => {
|
|
2038
2150
|
if (!pending) return;
|
|
2039
2151
|
if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
|
|
2040
2152
|
setPending(null);
|
|
@@ -2063,7 +2175,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2063
2175
|
setRestoreDraft({ id: restoreSequence.current, text: pending.text, context: pending.context, images: pending.images });
|
|
2064
2176
|
setPending({ ...pending, status: "editing" });
|
|
2065
2177
|
};
|
|
2066
|
-
|
|
2178
|
+
useEffect7(() => {
|
|
2067
2179
|
const key = state.attached?.key;
|
|
2068
2180
|
if (!key || !state.attention.some((item) => item.key === key)) {
|
|
2069
2181
|
if (key) acknowledged.current.delete(key);
|
|
@@ -2098,7 +2210,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2098
2210
|
return result;
|
|
2099
2211
|
}
|
|
2100
2212
|
}), [adapter, state.error]);
|
|
2101
|
-
|
|
2213
|
+
useEffect7(() => {
|
|
2102
2214
|
if (!pendingAction) return;
|
|
2103
2215
|
if (state.operation && !pendingAction.seenOperation) {
|
|
2104
2216
|
setPendingAction({ ...pendingAction, seenOperation: true });
|
|
@@ -2131,23 +2243,24 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2131
2243
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2132
2244
|
const startableKey = startable.map((item) => item.id).join("\0");
|
|
2133
2245
|
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
|
|
2134
|
-
const [harness, setHarness] =
|
|
2135
|
-
const [draft, setDraft] =
|
|
2136
|
-
const [context, setContext] =
|
|
2137
|
-
const [images, setImages] =
|
|
2138
|
-
const [starting, setStarting] =
|
|
2139
|
-
const [picking, setPicking] =
|
|
2140
|
-
const [
|
|
2141
|
-
const
|
|
2142
|
-
const
|
|
2246
|
+
const [harness, setHarness] = useState5(remembered.harness);
|
|
2247
|
+
const [draft, setDraft] = useState5(remembered.draft);
|
|
2248
|
+
const [context, setContext] = useState5(remembered.context);
|
|
2249
|
+
const [images, setImages] = useState5(remembered.images ?? []);
|
|
2250
|
+
const [starting, setStarting] = useState5(null);
|
|
2251
|
+
const [picking, setPicking] = useState5(false);
|
|
2252
|
+
const [dragging, setDragging] = useState5(false);
|
|
2253
|
+
const [pickerError, setPickerError] = useState5(null);
|
|
2254
|
+
const startSequence = useRef6(0);
|
|
2255
|
+
const textarea = useRef6(null);
|
|
2143
2256
|
useAutosizeTextarea(textarea, draft);
|
|
2144
|
-
|
|
2257
|
+
useEffect7(() => {
|
|
2145
2258
|
if (startable.some((item) => item.id === harness)) return;
|
|
2146
2259
|
const next = startable[0]?.id ?? "";
|
|
2147
2260
|
setHarness(next);
|
|
2148
2261
|
boundedSet(newChatMemory, memoryKey, { harness: next, draft, context, images });
|
|
2149
2262
|
}, [harness, startableKey]);
|
|
2150
|
-
|
|
2263
|
+
useEffect7(() => {
|
|
2151
2264
|
if (!starting) return;
|
|
2152
2265
|
const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
|
|
2153
2266
|
const beganWorking = !starting.busy && state.busy;
|
|
@@ -2155,10 +2268,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2155
2268
|
newChatMemory.delete(memoryKey);
|
|
2156
2269
|
onStarted();
|
|
2157
2270
|
}, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
|
|
2158
|
-
|
|
2271
|
+
useEffect7(() => {
|
|
2159
2272
|
if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
|
|
2160
2273
|
}, [starting, state.busy, state.error, state.operation]);
|
|
2161
|
-
|
|
2274
|
+
useEffect7(() => {
|
|
2162
2275
|
textarea.current?.focus({ preventScroll: true });
|
|
2163
2276
|
}, []);
|
|
2164
2277
|
const pickContext = () => {
|
|
@@ -2181,13 +2294,17 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2181
2294
|
});
|
|
2182
2295
|
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
2183
2296
|
};
|
|
2184
|
-
const
|
|
2185
|
-
const
|
|
2186
|
-
if (!
|
|
2187
|
-
|
|
2297
|
+
const addImageFiles = (value, source) => {
|
|
2298
|
+
const allFiles = Array.from(value ?? []);
|
|
2299
|
+
if (!allFiles.length) return false;
|
|
2300
|
+
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
2301
|
+
if (files.length !== allFiles.length) {
|
|
2302
|
+
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
2303
|
+
return true;
|
|
2304
|
+
}
|
|
2188
2305
|
if (images.length + files.length > MAX_IMAGE_ITEMS) {
|
|
2189
2306
|
setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2190
|
-
return;
|
|
2307
|
+
return true;
|
|
2191
2308
|
}
|
|
2192
2309
|
setPicking(true);
|
|
2193
2310
|
setPickerError(null);
|
|
@@ -2195,11 +2312,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2195
2312
|
const next = mergeImages(current, picked);
|
|
2196
2313
|
boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
|
|
2197
2314
|
return next;
|
|
2198
|
-
}), (error) => setPickerError(error instanceof Error ? error.message :
|
|
2315
|
+
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
2316
|
+
return true;
|
|
2317
|
+
};
|
|
2318
|
+
const pasteImages = (event) => {
|
|
2319
|
+
if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
|
|
2320
|
+
};
|
|
2321
|
+
const dropImages = (event) => {
|
|
2322
|
+
setDragging(false);
|
|
2323
|
+
if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
|
|
2199
2324
|
};
|
|
2200
2325
|
const send = () => {
|
|
2201
2326
|
const text = draft.trim();
|
|
2202
|
-
if (!text || !harness || starting) return;
|
|
2327
|
+
if (!text && !images.length || !harness || starting) return;
|
|
2203
2328
|
const id = startSequence.current + 1;
|
|
2204
2329
|
startSequence.current = id;
|
|
2205
2330
|
setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
|
|
@@ -2250,7 +2375,16 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2250
2375
|
return next;
|
|
2251
2376
|
}) }),
|
|
2252
2377
|
pickerError ? /* @__PURE__ */ jsx8("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2253
|
-
/* @__PURE__ */ jsxs7("div", { class:
|
|
2378
|
+
/* @__PURE__ */ jsxs7("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2379
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2380
|
+
event.preventDefault();
|
|
2381
|
+
setDragging(true);
|
|
2382
|
+
}
|
|
2383
|
+
}, onDragOver: (event) => {
|
|
2384
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
|
|
2385
|
+
}, onDragLeave: (event) => {
|
|
2386
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2387
|
+
}, onDrop: dropImages, children: [
|
|
2254
2388
|
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,
|
|
2255
2389
|
/* @__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) => {
|
|
2256
2390
|
const value = event.currentTarget.value;
|
|
@@ -2262,7 +2396,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2262
2396
|
send();
|
|
2263
2397
|
}
|
|
2264
2398
|
} }),
|
|
2265
|
-
/* @__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 }) }) })
|
|
2399
|
+
/* @__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 }) }) })
|
|
2266
2400
|
] })
|
|
2267
2401
|
] })
|
|
2268
2402
|
] });
|
|
@@ -2271,14 +2405,14 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2271
2405
|
const state = useMemo2(() => normalizeUiState(stateInput), [stateInput]);
|
|
2272
2406
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
2273
2407
|
const memoryKey = state.workspace || "@default";
|
|
2274
|
-
const [view, setViewState] =
|
|
2275
|
-
const [opening, setOpening] =
|
|
2276
|
-
const [listFocus, setListFocus] =
|
|
2408
|
+
const [view, setViewState] = useState5(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
|
|
2409
|
+
const [opening, setOpening] = useState5(null);
|
|
2410
|
+
const [listFocus, setListFocus] = useState5(null);
|
|
2277
2411
|
const setView = (next) => {
|
|
2278
2412
|
boundedSet(messengerViewMemory, memoryKey, next);
|
|
2279
2413
|
setViewState(next);
|
|
2280
2414
|
};
|
|
2281
|
-
|
|
2415
|
+
useEffect7(() => {
|
|
2282
2416
|
if (!opening) return;
|
|
2283
2417
|
if (state.attached?.key === opening.key) {
|
|
2284
2418
|
setOpening(null);
|