@volter-ai-dev/supercode-ui 0.1.20 → 0.1.22
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 -4
- package/components.mjs +244 -46
- package/composer.mjs +153 -27
- package/controller.mjs +21 -2
- package/conversation.mjs +213 -195
- package/core.mjs +12 -0
- package/embed.mjs +244 -46
- package/icon.mjs +5 -0
- package/index.d.ts +15 -5
- package/messenger.mjs +244 -46
- package/package.json +1 -1
- package/sessions.mjs +45 -36
- package/styles.css +3 -0
package/core.mjs
CHANGED
|
@@ -491,6 +491,18 @@ function readTranscript(value) {
|
|
|
491
491
|
: [];
|
|
492
492
|
});
|
|
493
493
|
}
|
|
494
|
+
if (Array.isArray(item.images)) {
|
|
495
|
+
entry.images = item.images.flatMap((raw) => {
|
|
496
|
+
const image = record(raw);
|
|
497
|
+
return image && typeof image.label === 'string'
|
|
498
|
+
? [{
|
|
499
|
+
...(typeof image.id === 'string' ? { id: image.id } : {}),
|
|
500
|
+
label: image.label,
|
|
501
|
+
...(typeof image.url === 'string' ? { url: image.url } : {}),
|
|
502
|
+
}]
|
|
503
|
+
: [];
|
|
504
|
+
}).slice(0, 4);
|
|
505
|
+
}
|
|
494
506
|
const request = record(item.request);
|
|
495
507
|
if (request && typeof request.requestKind === 'string' && typeof request.payloadText === 'string') {
|
|
496
508
|
entry.request = {
|
package/embed.mjs
CHANGED
|
@@ -504,6 +504,16 @@ function readTranscript(value) {
|
|
|
504
504
|
}] : [];
|
|
505
505
|
});
|
|
506
506
|
}
|
|
507
|
+
if (Array.isArray(item.images)) {
|
|
508
|
+
entry.images = item.images.flatMap((raw) => {
|
|
509
|
+
const image = record(raw);
|
|
510
|
+
return image && typeof image.label === "string" ? [{
|
|
511
|
+
...typeof image.id === "string" ? { id: image.id } : {},
|
|
512
|
+
label: image.label,
|
|
513
|
+
...typeof image.url === "string" ? { url: image.url } : {}
|
|
514
|
+
}] : [];
|
|
515
|
+
}).slice(0, 4);
|
|
516
|
+
}
|
|
507
517
|
const request = record(item.request);
|
|
508
518
|
if (request && typeof request.requestKind === "string" && typeof request.payloadText === "string") {
|
|
509
519
|
entry.request = {
|
|
@@ -780,6 +790,11 @@ var ICONS = {
|
|
|
780
790
|
/* @__PURE__ */ jsx("path", { d: "M9 3.5v10" }),
|
|
781
791
|
/* @__PURE__ */ jsx("path", { d: "m4.75 9.5 4.25 4 4.25-4" })
|
|
782
792
|
] }),
|
|
793
|
+
image: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
794
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "3.5", width: "12", height: "11", rx: "1.75" }),
|
|
795
|
+
/* @__PURE__ */ jsx("circle", { cx: "6.5", cy: "7", r: "1.25" }),
|
|
796
|
+
/* @__PURE__ */ jsx("path", { d: "m4.5 13 3.25-3 2.1 1.85 1.65-1.5L14 13" })
|
|
797
|
+
] }),
|
|
783
798
|
menu: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
784
799
|
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
|
|
785
800
|
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
|
|
@@ -808,6 +823,9 @@ function UiIcon({ name, size = 16, class: className = "" }) {
|
|
|
808
823
|
// src/context.jsx
|
|
809
824
|
import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
810
825
|
var MAX_CONTEXT_ITEMS = 32;
|
|
826
|
+
var MAX_IMAGE_ITEMS = 4;
|
|
827
|
+
var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
828
|
+
var IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
811
829
|
function normalizeContext(value) {
|
|
812
830
|
return (Array.isArray(value) ? value : value ? [value] : []).flatMap((item) => {
|
|
813
831
|
if (!item || typeof item.label !== "string" || typeof item.detail !== "string") return [];
|
|
@@ -834,6 +852,58 @@ function mergeContext(current, picked) {
|
|
|
834
852
|
}
|
|
835
853
|
return next;
|
|
836
854
|
}
|
|
855
|
+
function normalizeImages(value) {
|
|
856
|
+
const seen = /* @__PURE__ */ new Set();
|
|
857
|
+
return (Array.isArray(value) ? value : value ? [value] : []).flatMap((item) => {
|
|
858
|
+
if (!item || typeof item.label !== "string" || typeof item.url !== "string") return [];
|
|
859
|
+
const label = item.label.trim().slice(0, 200);
|
|
860
|
+
const url = item.url;
|
|
861
|
+
if (!label || seen.has(url) || !(url.startsWith("data:image/") || url.startsWith("https://") || url.startsWith("http://"))) return [];
|
|
862
|
+
seen.add(url);
|
|
863
|
+
return [{
|
|
864
|
+
...typeof item.id === "string" && item.id ? { id: item.id.slice(0, 2e3) } : {},
|
|
865
|
+
label,
|
|
866
|
+
url
|
|
867
|
+
}];
|
|
868
|
+
}).slice(0, MAX_IMAGE_ITEMS);
|
|
869
|
+
}
|
|
870
|
+
function mergeImages(current, picked) {
|
|
871
|
+
const next = [...current];
|
|
872
|
+
const seen = new Set(current.map((item) => item.url));
|
|
873
|
+
for (const item of normalizeImages(picked)) {
|
|
874
|
+
if (seen.has(item.url)) continue;
|
|
875
|
+
seen.add(item.url);
|
|
876
|
+
next.push(item);
|
|
877
|
+
if (next.length === MAX_IMAGE_ITEMS) break;
|
|
878
|
+
}
|
|
879
|
+
return next;
|
|
880
|
+
}
|
|
881
|
+
function partitionAttachments(value) {
|
|
882
|
+
const values = Array.isArray(value) ? value : value ? [value] : [];
|
|
883
|
+
const context = [];
|
|
884
|
+
const images = [];
|
|
885
|
+
for (const item of values) {
|
|
886
|
+
if (item && typeof item.label === "string" && item.label.trim() && typeof item.detail === "string" && item.detail) context.push(item);
|
|
887
|
+
else if (item && typeof item.label === "string" && item.label.trim() && typeof item.url === "string" && (item.url.startsWith("data:image/") || item.url.startsWith("https://") || item.url.startsWith("http://"))) images.push(item);
|
|
888
|
+
else throw new Error("The attachment picker returned an invalid item.");
|
|
889
|
+
}
|
|
890
|
+
return { context, images };
|
|
891
|
+
}
|
|
892
|
+
async function imageAttachmentsFromFiles(value) {
|
|
893
|
+
const files = Array.from(value ?? []).filter((file) => file?.type?.startsWith("image/"));
|
|
894
|
+
if (files.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images at a time.`);
|
|
895
|
+
return Promise.all(files.map(async (file) => {
|
|
896
|
+
if (!IMAGE_TYPES.has(file.type)) throw new Error(`${file.name || "That image"} is not PNG, JPEG, GIF, or WebP.`);
|
|
897
|
+
if (file.size > MAX_IMAGE_BYTES) throw new Error(`${file.name || "That image"} is larger than 5 MB.`);
|
|
898
|
+
const url = await new Promise((resolve, reject) => {
|
|
899
|
+
const reader = new FileReader();
|
|
900
|
+
reader.onload = () => resolve(reader.result);
|
|
901
|
+
reader.onerror = () => reject(reader.error ?? new Error(`Could not read ${file.name || "image"}.`));
|
|
902
|
+
reader.readAsDataURL(file);
|
|
903
|
+
});
|
|
904
|
+
return { id: `${file.name}:${file.size}:${file.lastModified}`, label: file.name || "Pasted image", url };
|
|
905
|
+
}));
|
|
906
|
+
}
|
|
837
907
|
function ContextTray({ items, onRemove }) {
|
|
838
908
|
if (!items.length) return null;
|
|
839
909
|
return /* @__PURE__ */ jsx2("div", { class: "scui-compose-context", "aria-label": "Attached context", children: items.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
|
|
@@ -842,6 +912,21 @@ function ContextTray({ items, onRemove }) {
|
|
|
842
912
|
/* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove context ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) })
|
|
843
913
|
] }, item.id ?? `${item.label}:${index}`)) });
|
|
844
914
|
}
|
|
915
|
+
function ImageTray({ items, onRemove }) {
|
|
916
|
+
if (!items.length) return null;
|
|
917
|
+
return /* @__PURE__ */ jsx2("div", { class: "scui-compose-images", "aria-label": "Attached images", children: items.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
|
|
918
|
+
/* @__PURE__ */ jsx2("img", { src: item.url, alt: "" }),
|
|
919
|
+
/* @__PURE__ */ jsx2("strong", { children: item.label }),
|
|
920
|
+
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
|
+
] }, item.id ?? `${item.label}:${index}`)) });
|
|
922
|
+
}
|
|
923
|
+
function MessageImages({ items }) {
|
|
924
|
+
if (!items?.length) return null;
|
|
925
|
+
return /* @__PURE__ */ jsx2("div", { class: "scui-message-images", children: items.map((item, index) => item.url ? /* @__PURE__ */ jsx2("img", { src: item.url, alt: item.label }, item.id ?? `${item.label}:${index}`) : /* @__PURE__ */ jsxs2("span", { children: [
|
|
926
|
+
/* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 14 }),
|
|
927
|
+
item.label
|
|
928
|
+
] }, item.id ?? `${item.label}:${index}`)) });
|
|
929
|
+
}
|
|
845
930
|
|
|
846
931
|
// src/textarea.js
|
|
847
932
|
import { useLayoutEffect } from "preact/hooks";
|
|
@@ -881,19 +966,24 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
881
966
|
] });
|
|
882
967
|
}
|
|
883
968
|
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
|
|
884
|
-
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], queue: [] };
|
|
969
|
+
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
885
970
|
const [draft, setDraft] = useState(remembered.draft);
|
|
886
|
-
const [context, setContext] = useState(remembered.context);
|
|
887
|
-
const [
|
|
971
|
+
const [context, setContext] = useState(remembered.context ?? []);
|
|
972
|
+
const [images, setImages] = useState(remembered.images ?? []);
|
|
973
|
+
const [queue, setQueue] = useState((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
|
|
888
974
|
const [dispatching, setDispatching] = useState(false);
|
|
889
975
|
const [picking, setPicking] = useState(false);
|
|
976
|
+
const [dragging, setDragging] = useState(false);
|
|
890
977
|
const [pickerError, setPickerError] = useState(null);
|
|
891
978
|
const textarea = useRef(null);
|
|
892
979
|
useAutosizeTextarea(textarea, draft);
|
|
893
|
-
const remember = (nextDraft, nextContext, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, queue: nextQueue });
|
|
980
|
+
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
981
|
+
useEffect(() => {
|
|
982
|
+
remember(draft, context, images, queue);
|
|
983
|
+
}, [draft, context, images, memoryKey, queue]);
|
|
894
984
|
const updateQueue = (update) => setQueue((items) => {
|
|
895
985
|
const next = update(items);
|
|
896
|
-
remember(draft, context, next);
|
|
986
|
+
remember(draft, context, images, next);
|
|
897
987
|
return next;
|
|
898
988
|
});
|
|
899
989
|
const queueBlocked = state.busy || pendingStatus !== null || dispatching;
|
|
@@ -903,9 +993,9 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
903
993
|
const [next, ...rest] = queue;
|
|
904
994
|
setDispatching(true);
|
|
905
995
|
setQueue(rest);
|
|
906
|
-
remember(draft, context, rest);
|
|
907
|
-
onPending?.(next.text, next.context);
|
|
908
|
-
adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {} });
|
|
996
|
+
remember(draft, context, images, rest);
|
|
997
|
+
onPending?.(next.text, next.context, next.images);
|
|
998
|
+
adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {}, ...next.images.length ? { images: next.images } : {} });
|
|
909
999
|
}
|
|
910
1000
|
}, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
|
|
911
1001
|
useEffect(() => {
|
|
@@ -918,8 +1008,10 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
918
1008
|
if (!restoreDraft) return;
|
|
919
1009
|
setDraft(restoreDraft.text);
|
|
920
1010
|
const restoredContext = normalizeContext(restoreDraft.context);
|
|
1011
|
+
const restoredImages = normalizeImages(restoreDraft.images);
|
|
921
1012
|
setContext(restoredContext);
|
|
922
|
-
|
|
1013
|
+
setImages(restoredImages);
|
|
1014
|
+
remember(restoreDraft.text, restoredContext, restoredImages, queue);
|
|
923
1015
|
textarea.current?.focus({ preventScroll: true });
|
|
924
1016
|
onDraftRestored?.(restoreDraft.id);
|
|
925
1017
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
@@ -928,30 +1020,67 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
928
1020
|
return () => clearTimeout(timer);
|
|
929
1021
|
}, [adapter, draft]);
|
|
930
1022
|
const pickContext = () => {
|
|
931
|
-
if (!adapter.pickContext || state.mode !== "control" || picking || context.length >= MAX_CONTEXT_ITEMS) return;
|
|
1023
|
+
if (!adapter.pickContext || state.mode !== "control" || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
932
1024
|
setPicking(true);
|
|
933
1025
|
setPickerError(null);
|
|
934
1026
|
Promise.resolve().then(() => adapter.pickContext()).then((picked) => {
|
|
1027
|
+
const attachments = partitionAttachments(picked);
|
|
1028
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
1029
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
935
1030
|
setContext((current) => {
|
|
936
|
-
const next = mergeContext(current,
|
|
937
|
-
remember(draft, next, queue);
|
|
1031
|
+
const next = mergeContext(current, attachments.context);
|
|
1032
|
+
remember(draft, next, images, queue);
|
|
938
1033
|
return next;
|
|
939
1034
|
});
|
|
940
|
-
|
|
1035
|
+
setImages((current) => {
|
|
1036
|
+
const next = mergeImages(current, attachments.images);
|
|
1037
|
+
remember(draft, context, next, queue);
|
|
1038
|
+
return next;
|
|
1039
|
+
});
|
|
1040
|
+
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
1041
|
+
};
|
|
1042
|
+
const addImageFiles = (value, source) => {
|
|
1043
|
+
const allFiles = Array.from(value ?? []);
|
|
1044
|
+
if (!allFiles.length) return false;
|
|
1045
|
+
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
1046
|
+
if (files.length !== allFiles.length) {
|
|
1047
|
+
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
1048
|
+
return true;
|
|
1049
|
+
}
|
|
1050
|
+
if (images.length + files.length > MAX_IMAGE_ITEMS) {
|
|
1051
|
+
setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
1052
|
+
return true;
|
|
1053
|
+
}
|
|
1054
|
+
setPicking(true);
|
|
1055
|
+
setPickerError(null);
|
|
1056
|
+
imageAttachmentsFromFiles(files).then((picked) => setImages((current) => {
|
|
1057
|
+
const next = mergeImages(current, picked);
|
|
1058
|
+
remember(draft, context, next, queue);
|
|
1059
|
+
return next;
|
|
1060
|
+
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
1061
|
+
return true;
|
|
1062
|
+
};
|
|
1063
|
+
const pasteImages = (event) => {
|
|
1064
|
+
if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
|
|
1065
|
+
};
|
|
1066
|
+
const dropImages = (event) => {
|
|
1067
|
+
setDragging(false);
|
|
1068
|
+
if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
|
|
941
1069
|
};
|
|
942
1070
|
const send = () => {
|
|
943
1071
|
const text = draft.trim();
|
|
944
|
-
if (!text) return;
|
|
945
|
-
const message = { text, context };
|
|
1072
|
+
if (!text && !images.length) return;
|
|
1073
|
+
const message = { text, context, images };
|
|
946
1074
|
if (queuesNewMessage) updateQueue((items) => [...items, message]);
|
|
947
1075
|
else if (state.canSend) {
|
|
948
1076
|
if (onPending) setDispatching(true);
|
|
949
|
-
onPending?.(text, context);
|
|
950
|
-
adapter.onIntent({ action: "send", text, ...context.length ? { context } : {} });
|
|
1077
|
+
onPending?.(text, context, images);
|
|
1078
|
+
adapter.onIntent({ action: "send", text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
|
|
951
1079
|
} else return;
|
|
952
1080
|
setDraft("");
|
|
953
1081
|
setContext([]);
|
|
954
|
-
|
|
1082
|
+
setImages([]);
|
|
1083
|
+
remember("", [], [], queuesNewMessage ? [...queue, message] : queue);
|
|
955
1084
|
};
|
|
956
1085
|
return /* @__PURE__ */ jsxs3("div", { class: "scui-compose", children: [
|
|
957
1086
|
queue.length ? /* @__PURE__ */ jsxs3("div", { class: "scui-queue", children: [
|
|
@@ -961,27 +1090,41 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
961
1090
|
] }),
|
|
962
1091
|
queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
|
|
963
1092
|
/* @__PURE__ */ jsxs3("span", { children: [
|
|
964
|
-
item.text,
|
|
965
|
-
item.context.length ? /* @__PURE__ */ jsxs3("small", { children: [
|
|
966
|
-
item.context.length,
|
|
1093
|
+
item.text || "Image attachment",
|
|
1094
|
+
item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
|
|
1095
|
+
item.context.length + item.images.length,
|
|
967
1096
|
" attached"
|
|
968
1097
|
] }) : null
|
|
969
1098
|
] }),
|
|
970
1099
|
/* @__PURE__ */ jsx3("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: /* @__PURE__ */ jsx3(UiIcon, { name: "close", size: 13 }) })
|
|
971
1100
|
] }, `${index}:${item.text}`))
|
|
972
1101
|
] }) : null,
|
|
1102
|
+
/* @__PURE__ */ jsx3(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
|
|
1103
|
+
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
1104
|
+
remember(draft, context, next, queue);
|
|
1105
|
+
return next;
|
|
1106
|
+
}) }),
|
|
973
1107
|
/* @__PURE__ */ jsx3(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
|
|
974
1108
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
975
|
-
remember(draft, next, queue);
|
|
1109
|
+
remember(draft, next, images, queue);
|
|
976
1110
|
return next;
|
|
977
1111
|
}) }),
|
|
978
1112
|
pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
979
|
-
/* @__PURE__ */ jsxs3("div", { class:
|
|
980
|
-
|
|
981
|
-
|
|
1113
|
+
/* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
1114
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
1115
|
+
event.preventDefault();
|
|
1116
|
+
setDragging(true);
|
|
1117
|
+
}
|
|
1118
|
+
}, onDragOver: (event) => {
|
|
1119
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
|
|
1120
|
+
}, onDragLeave: (event) => {
|
|
1121
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
1122
|
+
}, onDrop: dropImages, children: [
|
|
1123
|
+
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,
|
|
1124
|
+
/* @__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) => {
|
|
982
1125
|
const value = event.currentTarget.value;
|
|
983
1126
|
setDraft(value);
|
|
984
|
-
remember(value, context, queue);
|
|
1127
|
+
remember(value, context, images, queue);
|
|
985
1128
|
}, onKeyDown: (event) => {
|
|
986
1129
|
if (isSendKey(event)) {
|
|
987
1130
|
event.preventDefault();
|
|
@@ -990,7 +1133,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
990
1133
|
} }),
|
|
991
1134
|
/* @__PURE__ */ jsxs3("span", { children: [
|
|
992
1135
|
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,
|
|
993
|
-
/* @__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 }) })
|
|
1136
|
+
/* @__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 }) })
|
|
994
1137
|
] })
|
|
995
1138
|
] })
|
|
996
1139
|
] });
|
|
@@ -1383,6 +1526,7 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
1383
1526
|
}
|
|
1384
1527
|
if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx5("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
|
|
1385
1528
|
return /* @__PURE__ */ jsxs4("article", { class: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
|
|
1529
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: entry.images }),
|
|
1386
1530
|
/* @__PURE__ */ jsx5(Markdown, { value: entry.text, copyText: adapter?.copyText }),
|
|
1387
1531
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: entry.context }),
|
|
1388
1532
|
entry.truncated ? /* @__PURE__ */ jsx5("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
|
|
@@ -1585,6 +1729,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1585
1729
|
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 })
|
|
1586
1730
|
] }, block.id)),
|
|
1587
1731
|
pendingMessage ? /* @__PURE__ */ jsxs4("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
|
|
1732
|
+
/* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images }),
|
|
1588
1733
|
/* @__PURE__ */ jsx5(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
|
|
1589
1734
|
/* @__PURE__ */ jsx5(ContextDisclosure, { context: pendingMessage.context }),
|
|
1590
1735
|
/* @__PURE__ */ jsxs4("footer", { children: [
|
|
@@ -1923,9 +2068,10 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
1923
2068
|
if (pending.status === "sending" && state.busy && !pending.seenBusy) setPending({ ...pending, seenBusy: true });
|
|
1924
2069
|
else if (pending.status === "sending" && state.error && !state.busy && !state.operation && (pending.seenBusy || state.error !== pending.initialError)) setPending({ ...pending, status: "failed" });
|
|
1925
2070
|
}, [pending, state.busy, state.error, state.operation, state.transcript]);
|
|
1926
|
-
const beginPending = (text, context = []) => setPending({
|
|
2071
|
+
const beginPending = (text, context = [], images = []) => setPending({
|
|
1927
2072
|
text,
|
|
1928
2073
|
context,
|
|
2074
|
+
images,
|
|
1929
2075
|
status: "sending",
|
|
1930
2076
|
initialError: state.error,
|
|
1931
2077
|
seenBusy: state.busy,
|
|
@@ -1933,13 +2079,13 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
1933
2079
|
});
|
|
1934
2080
|
const retryPending = () => {
|
|
1935
2081
|
if (!pending) return;
|
|
1936
|
-
adapter.onIntent({ action: "send", text: pending.text, ...pending.context?.length ? { context: pending.context } : {} });
|
|
2082
|
+
adapter.onIntent({ action: "send", text: pending.text, ...pending.context?.length ? { context: pending.context } : {}, ...pending.images?.length ? { images: pending.images } : {} });
|
|
1937
2083
|
setPending({ ...pending, status: "sending", initialError: state.error, seenBusy: state.busy });
|
|
1938
2084
|
};
|
|
1939
2085
|
const editPending = () => {
|
|
1940
2086
|
if (!pending) return;
|
|
1941
2087
|
restoreSequence.current += 1;
|
|
1942
|
-
setRestoreDraft({ id: restoreSequence.current, text: pending.text, context: pending.context });
|
|
2088
|
+
setRestoreDraft({ id: restoreSequence.current, text: pending.text, context: pending.context, images: pending.images });
|
|
1943
2089
|
setPending({ ...pending, status: "editing" });
|
|
1944
2090
|
};
|
|
1945
2091
|
useEffect6(() => {
|
|
@@ -1985,7 +2131,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
1985
2131
|
setPendingAction(null);
|
|
1986
2132
|
}
|
|
1987
2133
|
}, [pendingAction, state.error, state.operation]);
|
|
1988
|
-
const pendingMessage = pending && pending.status !== "editing" ? { text: pending.text, context: pending.context, status: pending.status, onRetry: retryPending, onEdit: editPending } : null;
|
|
2134
|
+
const pendingMessage = pending && pending.status !== "editing" ? { text: pending.text, context: pending.context, images: pending.images, status: pending.status, onRetry: retryPending, onEdit: editPending } : null;
|
|
1989
2135
|
const action = state.operation || pendingAction?.action || null;
|
|
1990
2136
|
const actionLabel = operationLabel(action);
|
|
1991
2137
|
const actionState = action ? { ...state, operation: action, canInterrupt: false, canRespond: false } : state;
|
|
@@ -2009,12 +2155,14 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2009
2155
|
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey }) {
|
|
2010
2156
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2011
2157
|
const startableKey = startable.map((item) => item.id).join("\0");
|
|
2012
|
-
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [] };
|
|
2158
|
+
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
|
|
2013
2159
|
const [harness, setHarness] = useState4(remembered.harness);
|
|
2014
2160
|
const [draft, setDraft] = useState4(remembered.draft);
|
|
2015
2161
|
const [context, setContext] = useState4(remembered.context);
|
|
2162
|
+
const [images, setImages] = useState4(remembered.images ?? []);
|
|
2016
2163
|
const [starting, setStarting] = useState4(null);
|
|
2017
2164
|
const [picking, setPicking] = useState4(false);
|
|
2165
|
+
const [dragging, setDragging] = useState4(false);
|
|
2018
2166
|
const [pickerError, setPickerError] = useState4(null);
|
|
2019
2167
|
const startSequence = useRef5(0);
|
|
2020
2168
|
const textarea = useRef5(null);
|
|
@@ -2023,7 +2171,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2023
2171
|
if (startable.some((item) => item.id === harness)) return;
|
|
2024
2172
|
const next = startable[0]?.id ?? "";
|
|
2025
2173
|
setHarness(next);
|
|
2026
|
-
boundedSet(newChatMemory, memoryKey, { harness: next, draft, context });
|
|
2174
|
+
boundedSet(newChatMemory, memoryKey, { harness: next, draft, context, images });
|
|
2027
2175
|
}, [harness, startableKey]);
|
|
2028
2176
|
useEffect6(() => {
|
|
2029
2177
|
if (!starting) return;
|
|
@@ -2040,24 +2188,60 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2040
2188
|
textarea.current?.focus({ preventScroll: true });
|
|
2041
2189
|
}, []);
|
|
2042
2190
|
const pickContext = () => {
|
|
2043
|
-
if (!adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS) return;
|
|
2191
|
+
if (!adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2044
2192
|
setPicking(true);
|
|
2045
2193
|
setPickerError(null);
|
|
2046
2194
|
Promise.resolve().then(() => adapter.pickContext()).then((picked) => {
|
|
2195
|
+
const attachments = partitionAttachments(picked);
|
|
2196
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
2197
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2047
2198
|
setContext((current) => {
|
|
2048
|
-
const next = mergeContext(current,
|
|
2049
|
-
boundedSet(newChatMemory, memoryKey, { harness, draft, context: next });
|
|
2199
|
+
const next = mergeContext(current, attachments.context);
|
|
2200
|
+
boundedSet(newChatMemory, memoryKey, { harness, draft, context: next, images });
|
|
2201
|
+
return next;
|
|
2202
|
+
});
|
|
2203
|
+
setImages((current) => {
|
|
2204
|
+
const next = mergeImages(current, attachments.images);
|
|
2205
|
+
boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
|
|
2050
2206
|
return next;
|
|
2051
2207
|
});
|
|
2052
|
-
}
|
|
2208
|
+
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
2209
|
+
};
|
|
2210
|
+
const addImageFiles = (value, source) => {
|
|
2211
|
+
const allFiles = Array.from(value ?? []);
|
|
2212
|
+
if (!allFiles.length) return false;
|
|
2213
|
+
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
2214
|
+
if (files.length !== allFiles.length) {
|
|
2215
|
+
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
2216
|
+
return true;
|
|
2217
|
+
}
|
|
2218
|
+
if (images.length + files.length > MAX_IMAGE_ITEMS) {
|
|
2219
|
+
setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2220
|
+
return true;
|
|
2221
|
+
}
|
|
2222
|
+
setPicking(true);
|
|
2223
|
+
setPickerError(null);
|
|
2224
|
+
imageAttachmentsFromFiles(files).then((picked) => setImages((current) => {
|
|
2225
|
+
const next = mergeImages(current, picked);
|
|
2226
|
+
boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
|
|
2227
|
+
return next;
|
|
2228
|
+
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
2229
|
+
return true;
|
|
2230
|
+
};
|
|
2231
|
+
const pasteImages = (event) => {
|
|
2232
|
+
if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
|
|
2233
|
+
};
|
|
2234
|
+
const dropImages = (event) => {
|
|
2235
|
+
setDragging(false);
|
|
2236
|
+
if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
|
|
2053
2237
|
};
|
|
2054
2238
|
const send = () => {
|
|
2055
2239
|
const text = draft.trim();
|
|
2056
|
-
if (!text || !harness || starting) return;
|
|
2240
|
+
if (!text && !images.length || !harness || starting) return;
|
|
2057
2241
|
const id = startSequence.current + 1;
|
|
2058
2242
|
startSequence.current = id;
|
|
2059
2243
|
setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
|
|
2060
|
-
const result = adapter.onIntent({ action: "new", harness, text, ...context.length ? { context } : {} });
|
|
2244
|
+
const result = adapter.onIntent({ action: "new", harness, text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
|
|
2061
2245
|
if (result && typeof result.then === "function") {
|
|
2062
2246
|
Promise.resolve(result).catch(() => setStarting((current) => current?.id === id ? null : current));
|
|
2063
2247
|
}
|
|
@@ -2087,31 +2271,45 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2087
2271
|
/* @__PURE__ */ jsx8("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
|
|
2088
2272
|
const value = event.currentTarget.value;
|
|
2089
2273
|
setHarness(value);
|
|
2090
|
-
boundedSet(newChatMemory, memoryKey, { harness: value, draft, context });
|
|
2274
|
+
boundedSet(newChatMemory, memoryKey, { harness: value, draft, context, images });
|
|
2091
2275
|
}, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs7("option", { value: item.id, disabled: !item.startable, children: [
|
|
2092
2276
|
item.label,
|
|
2093
2277
|
item.startable ? "" : " \xB7 unavailable"
|
|
2094
2278
|
] }, item.id)) })
|
|
2095
2279
|
] }),
|
|
2280
|
+
/* @__PURE__ */ jsx8(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
|
|
2281
|
+
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
2282
|
+
boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
|
|
2283
|
+
return next;
|
|
2284
|
+
}) }),
|
|
2096
2285
|
/* @__PURE__ */ jsx8(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
|
|
2097
2286
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
2098
|
-
boundedSet(newChatMemory, memoryKey, { harness, draft, context: next });
|
|
2287
|
+
boundedSet(newChatMemory, memoryKey, { harness, draft, context: next, images });
|
|
2099
2288
|
return next;
|
|
2100
2289
|
}) }),
|
|
2101
2290
|
pickerError ? /* @__PURE__ */ jsx8("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2102
|
-
/* @__PURE__ */ jsxs7("div", { class:
|
|
2103
|
-
|
|
2104
|
-
|
|
2291
|
+
/* @__PURE__ */ jsxs7("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2292
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2293
|
+
event.preventDefault();
|
|
2294
|
+
setDragging(true);
|
|
2295
|
+
}
|
|
2296
|
+
}, onDragOver: (event) => {
|
|
2297
|
+
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
|
|
2298
|
+
}, onDragLeave: (event) => {
|
|
2299
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2300
|
+
}, onDrop: dropImages, children: [
|
|
2301
|
+
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,
|
|
2302
|
+
/* @__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) => {
|
|
2105
2303
|
const value = event.currentTarget.value;
|
|
2106
2304
|
setDraft(value);
|
|
2107
|
-
boundedSet(newChatMemory, memoryKey, { harness, draft: value, context });
|
|
2305
|
+
boundedSet(newChatMemory, memoryKey, { harness, draft: value, context, images });
|
|
2108
2306
|
}, onKeyDown: (event) => {
|
|
2109
2307
|
if (isSendKey(event)) {
|
|
2110
2308
|
event.preventDefault();
|
|
2111
2309
|
send();
|
|
2112
2310
|
}
|
|
2113
2311
|
} }),
|
|
2114
|
-
/* @__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 }) }) })
|
|
2312
|
+
/* @__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 }) }) })
|
|
2115
2313
|
] })
|
|
2116
2314
|
] })
|
|
2117
2315
|
] });
|
package/icon.mjs
CHANGED
|
@@ -17,6 +17,11 @@ var ICONS = {
|
|
|
17
17
|
/* @__PURE__ */ jsx("path", { d: "M9 3.5v10" }),
|
|
18
18
|
/* @__PURE__ */ jsx("path", { d: "m4.75 9.5 4.25 4 4.25-4" })
|
|
19
19
|
] }),
|
|
20
|
+
image: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "3.5", width: "12", height: "11", rx: "1.75" }),
|
|
22
|
+
/* @__PURE__ */ jsx("circle", { cx: "6.5", cy: "7", r: "1.25" }),
|
|
23
|
+
/* @__PURE__ */ jsx("path", { d: "m4.5 13 3.25-3 2.1 1.85 1.65-1.5L14 13" })
|
|
24
|
+
] }),
|
|
20
25
|
menu: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21
26
|
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
|
|
22
27
|
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
|
package/index.d.ts
CHANGED
|
@@ -46,6 +46,14 @@ export interface TranscriptContext {
|
|
|
46
46
|
detail: string;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export interface TranscriptImage {
|
|
50
|
+
id?: string;
|
|
51
|
+
label: string;
|
|
52
|
+
url?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type TranscriptAttachment = TranscriptContext | (TranscriptImage & { url: string });
|
|
56
|
+
|
|
49
57
|
export interface RequestOption {
|
|
50
58
|
optionId: string;
|
|
51
59
|
name: string;
|
|
@@ -78,6 +86,7 @@ export interface TranscriptEntryModel {
|
|
|
78
86
|
request?: TranscriptRequest;
|
|
79
87
|
code?: string;
|
|
80
88
|
context?: TranscriptContext[];
|
|
89
|
+
images?: TranscriptImage[];
|
|
81
90
|
presentation?: ToolPresentationModel;
|
|
82
91
|
}
|
|
83
92
|
|
|
@@ -211,8 +220,8 @@ export type SupercodeUiIntent =
|
|
|
211
220
|
| { action: 'loadSessions' }
|
|
212
221
|
| { action: 'loadEarlier' }
|
|
213
222
|
| { action: 'draft'; text: string }
|
|
214
|
-
| { action: 'send'; text: string; context?: TranscriptContext[] }
|
|
215
|
-
| { action: 'new'; harness: HarnessId; text: string; context?: TranscriptContext[] }
|
|
223
|
+
| { action: 'send'; text: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> }
|
|
224
|
+
| { action: 'new'; harness: HarnessId; text: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> }
|
|
216
225
|
| { action: 'resume' }
|
|
217
226
|
| { action: 'join' }
|
|
218
227
|
| { action: 'detach' }
|
|
@@ -227,8 +236,8 @@ export type SupercodeUiIntent =
|
|
|
227
236
|
|
|
228
237
|
export interface UiAdapter {
|
|
229
238
|
onIntent(intent: SupercodeUiIntent): void | Promise<void>;
|
|
230
|
-
/** Ask the embedding host for explicit user-selected text context. */
|
|
231
|
-
pickContext?():
|
|
239
|
+
/** Ask the embedding host for explicit user-selected text or image context. */
|
|
240
|
+
pickContext?(): TranscriptAttachment | TranscriptAttachment[] | null | Promise<TranscriptAttachment | TranscriptAttachment[] | null>;
|
|
232
241
|
onClose?(): void;
|
|
233
242
|
onOpen?(): void;
|
|
234
243
|
copyText?(value: string): void | Promise<void>;
|
|
@@ -248,6 +257,7 @@ export interface MessengerLabels {
|
|
|
248
257
|
export interface PendingMessageModel {
|
|
249
258
|
text: string;
|
|
250
259
|
context?: TranscriptContext[];
|
|
260
|
+
images?: TranscriptImage[];
|
|
251
261
|
status: 'sending' | 'failed';
|
|
252
262
|
onRetry?(): void;
|
|
253
263
|
onEdit?(): void;
|
|
@@ -358,7 +368,7 @@ export function Conversation(props: { state: SupercodeUiState; adapter: UiAdapte
|
|
|
358
368
|
export function SessionRow(props: { row: SessionRowModel; state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void }): VNode;
|
|
359
369
|
export function SessionList(props: { state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void; onNew?(): void; onClose?(): void; components?: MessengerComponents; labels?: MessengerLabels; focusKey?: string | null; memoryKey?: string }): VNode;
|
|
360
370
|
export function ContinuationBar(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels }): VNode | null;
|
|
361
|
-
export function Composer(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels; memoryKey?: string; pendingStatus?: PendingMessageModel['status'] | 'editing' | null; restoreDraft?: { id: string | number; text: string; context?: TranscriptContext[] } | null; onPending?(text: string, context?: TranscriptContext[]): void; onDraftRestored?(id: string | number): void }): VNode;
|
|
371
|
+
export function Composer(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels; memoryKey?: string; pendingStatus?: PendingMessageModel['status'] | 'editing' | null; restoreDraft?: { id: string | number; text: string; context?: TranscriptContext[]; images?: TranscriptImage[] } | null; onPending?(text: string, context?: TranscriptContext[], images?: TranscriptImage[]): void; onDraftRestored?(id: string | number): void }): VNode;
|
|
362
372
|
export function SupercodeMessenger(props: MessengerProps): VNode;
|
|
363
373
|
|
|
364
374
|
export interface MountedMessenger {
|