cortena-ui 1.6.0 → 1.7.0
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/CHANGELOG.md +76 -0
- package/README.md +36 -0
- package/dist/agent-chat/session.js +21 -7
- package/dist/agent-chat/session.js.map +1 -1
- package/dist/agent-chat/store.d.ts +55 -4
- package/dist/agent-chat/store.js +87 -22
- package/dist/agent-chat/store.js.map +1 -1
- package/dist/agent-chat/types.d.ts +11 -0
- package/dist/agent-chat/types.js.map +1 -1
- package/dist/agent-chat.d.ts +3 -3
- package/dist/components/agent-chat-popup.d.ts +2 -2
- package/dist/components/agent-chat-popup.js.map +1 -1
- package/dist/components/agent-chat.d.ts +169 -13
- package/dist/components/agent-chat.js +200 -27
- package/dist/components/agent-chat.js.map +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/toast.d.ts +1 -1
- package/package.json +1 -1
- package/src/agent-chat/session.ts +35 -5
- package/src/agent-chat/store.ts +193 -21
- package/src/agent-chat/types.ts +11 -0
- package/src/components/agent-chat-popup.tsx +3 -3
- package/src/components/agent-chat.tsx +576 -149
- package/src/entries/agent-chat.ts +12 -0
package/src/agent-chat/store.ts
CHANGED
|
@@ -44,6 +44,7 @@ import type {
|
|
|
44
44
|
AgentChatEvent,
|
|
45
45
|
AgentChatHistoryResponse,
|
|
46
46
|
AgentChatMessage,
|
|
47
|
+
AgentChatSendInput,
|
|
47
48
|
AgentChatStep,
|
|
48
49
|
AgentSession,
|
|
49
50
|
AgentSessionsListResponse,
|
|
@@ -141,7 +142,18 @@ type Action =
|
|
|
141
142
|
| { kind: "approval/denied"; id: string }
|
|
142
143
|
| { kind: "approval/failed"; request: AgentApprovalRequest; message: string }
|
|
143
144
|
| { kind: "session"; key: string | null }
|
|
144
|
-
| {
|
|
145
|
+
| {
|
|
146
|
+
kind: "send";
|
|
147
|
+
message: AgentChatMessage | null;
|
|
148
|
+
runId: string;
|
|
149
|
+
sessionKey: string;
|
|
150
|
+
/**
|
|
151
|
+
* A regenerate: no new bubble, but the user turn being asked again is
|
|
152
|
+
* moved onto this run by id, so the transcript still reads Q then A.
|
|
153
|
+
*/
|
|
154
|
+
reanchor?: string;
|
|
155
|
+
}
|
|
156
|
+
| { kind: "message/remove"; id: string }
|
|
145
157
|
| { kind: "send/failed"; message: string }
|
|
146
158
|
| { kind: "abort" }
|
|
147
159
|
| { kind: "history/loading" }
|
|
@@ -152,7 +164,13 @@ type Action =
|
|
|
152
164
|
hasMore: boolean;
|
|
153
165
|
records: number;
|
|
154
166
|
}
|
|
155
|
-
| {
|
|
167
|
+
| {
|
|
168
|
+
kind: "history/older";
|
|
169
|
+
sessionKey: string;
|
|
170
|
+
messages: AgentChatMessage[];
|
|
171
|
+
hasMore: boolean;
|
|
172
|
+
records: number;
|
|
173
|
+
}
|
|
156
174
|
| { kind: "history/failed" }
|
|
157
175
|
| { kind: "sessions/loading" }
|
|
158
176
|
| { kind: "sessions/loaded"; sessions: AgentSession[] }
|
|
@@ -218,7 +236,13 @@ function reduce(state: AgentChatState, action: Action): AgentChatState {
|
|
|
218
236
|
return {
|
|
219
237
|
...state,
|
|
220
238
|
sessionKey: action.sessionKey,
|
|
221
|
-
messages: action.message
|
|
239
|
+
messages: action.message
|
|
240
|
+
? [...state.messages, action.message]
|
|
241
|
+
: action.reanchor
|
|
242
|
+
? state.messages.map((m) =>
|
|
243
|
+
m.id === action.reanchor ? { ...m, runId: action.runId } : m,
|
|
244
|
+
)
|
|
245
|
+
: state.messages,
|
|
222
246
|
streaming: true,
|
|
223
247
|
streamText: "",
|
|
224
248
|
streamThinkingText: "",
|
|
@@ -237,6 +261,10 @@ function reduce(state: AgentChatState, action: Action): AgentChatState {
|
|
|
237
261
|
approvals: [],
|
|
238
262
|
error: null,
|
|
239
263
|
};
|
|
264
|
+
case "message/remove":
|
|
265
|
+
// The bubble a regenerate replaces. Dropped before the new run starts so
|
|
266
|
+
// the answer takes its place rather than appending a second one.
|
|
267
|
+
return { ...state, messages: state.messages.filter((m) => m.id !== action.id) };
|
|
240
268
|
case "send/failed":
|
|
241
269
|
return {
|
|
242
270
|
...state,
|
|
@@ -273,6 +301,16 @@ function reduce(state: AgentChatState, action: Action): AgentChatState {
|
|
|
273
301
|
case "history/loading":
|
|
274
302
|
return { ...state, loadingHistory: true };
|
|
275
303
|
case "history/loaded":
|
|
304
|
+
// A load for a session that is no longer the one on screen. Two loads
|
|
305
|
+
// race whenever a host switches sessions quickly — resume A, resume B,
|
|
306
|
+
// A's reply lands last — and applying it put A's transcript under B's
|
|
307
|
+
// key. The load that matters is the one for the CURRENT key; every other
|
|
308
|
+
// reply is stale and is dropped whole. `null` is included on purpose: a
|
|
309
|
+
// host that cleared the session while a load was in flight must not be
|
|
310
|
+
// put back into it by the reply.
|
|
311
|
+
if (action.sessionKey !== state.sessionKey) {
|
|
312
|
+
return { ...state, loadingHistory: false };
|
|
313
|
+
}
|
|
276
314
|
if (
|
|
277
315
|
wouldShrinkHistory({
|
|
278
316
|
currentSessionKey: state.sessionKey,
|
|
@@ -303,6 +341,13 @@ function reduce(state: AgentChatState, action: Action): AgentChatState {
|
|
|
303
341
|
streamA2ui: [],
|
|
304
342
|
};
|
|
305
343
|
case "history/older":
|
|
344
|
+
// The same rule as `history/loaded`: a page that was asked for under a
|
|
345
|
+
// key that is no longer the current one — the host switched sessions
|
|
346
|
+
// while it was in flight — must not be prepended to the transcript
|
|
347
|
+
// that is on screen now.
|
|
348
|
+
if (action.sessionKey !== state.sessionKey) {
|
|
349
|
+
return { ...state, loadingHistory: false };
|
|
350
|
+
}
|
|
306
351
|
return {
|
|
307
352
|
...state,
|
|
308
353
|
messages: [...action.messages, ...state.messages],
|
|
@@ -929,16 +974,68 @@ export interface UseAgentChatOptions {
|
|
|
929
974
|
/** Skip reading and writing `sessionStorage`; for a transient mount. */
|
|
930
975
|
ephemeral?: boolean;
|
|
931
976
|
thinking?: "low" | "medium" | "high";
|
|
977
|
+
/**
|
|
978
|
+
* Mint the key for a brand-new session. Defaults to `mintSessionKey(client.agentId)`,
|
|
979
|
+
* which binds the session to this client's agent template. A host whose
|
|
980
|
+
* sessions are bound some other way — cortenaweb lets the user pick an agent
|
|
981
|
+
* per chat, and a chat with no pick is bound server-side to the default —
|
|
982
|
+
* supplies its own.
|
|
983
|
+
*/
|
|
984
|
+
mintSessionKey?: () => string;
|
|
985
|
+
/**
|
|
986
|
+
* Read the stored session key back on mount. Default `true`. A host whose
|
|
987
|
+
* URL names the session — `/chat/<key>` — is the source of truth itself and
|
|
988
|
+
* passes `false`: the key is still WRITTEN, so the rule "one window, one
|
|
989
|
+
* session" holds, but nothing is restored under the URL's feet and no
|
|
990
|
+
* history is fetched for a session the page is about to leave.
|
|
991
|
+
*/
|
|
992
|
+
restoreSession?: boolean;
|
|
993
|
+
/**
|
|
994
|
+
* Fetch `sessions.list` on mount and after each send. Default `true`. A host
|
|
995
|
+
* with its own session list — cortenaweb's sidebar — passes `false` and the
|
|
996
|
+
* hook makes no session calls at all; `sessions` stays empty.
|
|
997
|
+
*/
|
|
998
|
+
listSessions?: boolean;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/** What `send` accepts beside the text and the attachments. */
|
|
1002
|
+
export interface AgentChatSendOptions {
|
|
1003
|
+
/** The model for this run; the composer's selector. */
|
|
1004
|
+
model?: AgentChatSendInput["model"];
|
|
1005
|
+
/** Overrides the hook-level `thinking` for this run. */
|
|
1006
|
+
thinking?: AgentChatSendInput["thinking"];
|
|
932
1007
|
}
|
|
933
1008
|
|
|
934
1009
|
export interface UseAgentChatResult extends AgentChatState {
|
|
935
|
-
send: (text: string, attachments?: unknown[]) => void;
|
|
1010
|
+
send: (text: string, attachments?: unknown[], options?: AgentChatSendOptions) => void;
|
|
936
1011
|
sendAction: (action: NonNullable<Parameters<AgentChatClient["send"]>[0]["a2uiAction"]>) => void;
|
|
1012
|
+
/** A click inside a rendered MCP App, as the next run's input. No user bubble. */
|
|
1013
|
+
sendMcpAppAction: (action: NonNullable<AgentChatSendInput["mcpAppAction"]>) => void;
|
|
937
1014
|
abort: () => void;
|
|
938
1015
|
resolveApproval: (id: string, decision: AgentApprovalDecision) => void;
|
|
939
1016
|
newSession: () => void;
|
|
940
1017
|
resumeSession: (key: string) => void;
|
|
941
|
-
|
|
1018
|
+
/**
|
|
1019
|
+
* Leave the session without starting another. The transcript empties, the
|
|
1020
|
+
* stored key is removed and the next `send` mints a fresh key — which is
|
|
1021
|
+
* what a host's "New chat" wants when the URL should not change until the
|
|
1022
|
+
* first message goes out. `newSession` mints immediately; this does not.
|
|
1023
|
+
*/
|
|
1024
|
+
clearSession: () => void;
|
|
1025
|
+
/**
|
|
1026
|
+
* Ask again for the message this one answered: the bubble is dropped and the
|
|
1027
|
+
* user turn before it is sent again, so the new answer takes its place. Also
|
|
1028
|
+
* the Retry on a failed turn. A no-op while a run is live or when nothing
|
|
1029
|
+
* precedes the message.
|
|
1030
|
+
*/
|
|
1031
|
+
regenerate: (messageId: string) => void;
|
|
1032
|
+
/**
|
|
1033
|
+
* Ask for the next page of older history. `true` when a load was started;
|
|
1034
|
+
* `false` when there is nothing to do — no session, a load already in
|
|
1035
|
+
* flight, or no more history — so a caller that saves scroll position
|
|
1036
|
+
* before a prepend knows whether one is coming.
|
|
1037
|
+
*/
|
|
1038
|
+
loadOlder: () => boolean;
|
|
942
1039
|
refreshSessions: () => void;
|
|
943
1040
|
}
|
|
944
1041
|
|
|
@@ -947,6 +1044,9 @@ export function useAgentChat({
|
|
|
947
1044
|
storageScope,
|
|
948
1045
|
ephemeral,
|
|
949
1046
|
thinking,
|
|
1047
|
+
mintSessionKey: mintKey,
|
|
1048
|
+
restoreSession = true,
|
|
1049
|
+
listSessions = true,
|
|
950
1050
|
}: UseAgentChatOptions): UseAgentChatResult {
|
|
951
1051
|
const [state, dispatch] = React.useReducer(reduce, EMPTY);
|
|
952
1052
|
const stateRef = React.useRef(state);
|
|
@@ -973,6 +1073,9 @@ export function useAgentChat({
|
|
|
973
1073
|
}, [client]);
|
|
974
1074
|
|
|
975
1075
|
const refreshSessions = React.useCallback(() => {
|
|
1076
|
+
if (!listSessions) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
976
1079
|
dispatch({ kind: "sessions/loading" });
|
|
977
1080
|
void client
|
|
978
1081
|
.request<AgentSessionsListResponse>("sessions.list", { includeDerivedTitles: true })
|
|
@@ -988,7 +1091,7 @@ export function useAgentChat({
|
|
|
988
1091
|
dispatch({ kind: "sessions/loaded", sessions: [] });
|
|
989
1092
|
dispatch({ kind: "error", message: errorText(err) });
|
|
990
1093
|
});
|
|
991
|
-
}, [client]);
|
|
1094
|
+
}, [client, listSessions]);
|
|
992
1095
|
|
|
993
1096
|
const loadHistory = React.useCallback(
|
|
994
1097
|
(sessionKey: string) => {
|
|
@@ -1018,7 +1121,7 @@ export function useAgentChat({
|
|
|
1018
1121
|
return;
|
|
1019
1122
|
}
|
|
1020
1123
|
restored.current = true;
|
|
1021
|
-
const stored = ephemeral ? null : readStoredSessionKey(storageScope);
|
|
1124
|
+
const stored = ephemeral || !restoreSession ? null : readStoredSessionKey(storageScope);
|
|
1022
1125
|
const key = stored && isSessionOfAgent(stored, client.agentId) ? stored : null;
|
|
1023
1126
|
if (key) {
|
|
1024
1127
|
dispatch({ kind: "session", key });
|
|
@@ -1032,7 +1135,7 @@ export function useAgentChat({
|
|
|
1032
1135
|
loadHistory(key);
|
|
1033
1136
|
}
|
|
1034
1137
|
refreshSessions();
|
|
1035
|
-
}, [client, ephemeral, loadHistory, refreshSessions, storageScope]);
|
|
1138
|
+
}, [client, ephemeral, loadHistory, refreshSessions, restoreSession, storageScope]);
|
|
1036
1139
|
|
|
1037
1140
|
/**
|
|
1038
1141
|
* Keep the per-window store in step with the strip, on a debounce.
|
|
@@ -1086,7 +1189,12 @@ export function useAgentChat({
|
|
|
1086
1189
|
);
|
|
1087
1190
|
|
|
1088
1191
|
const run = React.useCallback(
|
|
1089
|
-
(
|
|
1192
|
+
(
|
|
1193
|
+
text: string,
|
|
1194
|
+
extra: Partial<Parameters<AgentChatClient["send"]>[0]> = {},
|
|
1195
|
+
/** The user message this run asks again, instead of a new bubble. */
|
|
1196
|
+
reanchor?: string,
|
|
1197
|
+
) => {
|
|
1090
1198
|
const current = stateRef.current;
|
|
1091
1199
|
// One run at a time. A second `send` on a streaming session opens a
|
|
1092
1200
|
// second stream into one store: two sets of deltas overwrite each
|
|
@@ -1097,7 +1205,8 @@ export function useAgentChat({
|
|
|
1097
1205
|
if (current.streaming) {
|
|
1098
1206
|
return;
|
|
1099
1207
|
}
|
|
1100
|
-
const sessionKey =
|
|
1208
|
+
const sessionKey =
|
|
1209
|
+
current.sessionKey ?? (mintKey ? mintKey() : mintSessionKey(client.agentId));
|
|
1101
1210
|
if (!current.sessionKey && !ephemeral) {
|
|
1102
1211
|
writeStoredSessionKey(storageScope, sessionKey);
|
|
1103
1212
|
}
|
|
@@ -1106,10 +1215,19 @@ export function useAgentChat({
|
|
|
1106
1215
|
// for it would put `{"name":"createTask"}` in the transcript as though
|
|
1107
1216
|
// the user had said it.
|
|
1108
1217
|
const message: AgentChatMessage | null =
|
|
1109
|
-
text || (!extra.a2uiAction && !extra.mcpAppAction)
|
|
1110
|
-
? {
|
|
1218
|
+
!reanchor && (text || (!extra.a2uiAction && !extra.mcpAppAction))
|
|
1219
|
+
? {
|
|
1220
|
+
id: runId,
|
|
1221
|
+
role: "user",
|
|
1222
|
+
text,
|
|
1223
|
+
timestamp: Date.now(),
|
|
1224
|
+
runId,
|
|
1225
|
+
// On the bubble from the start, so the host's strip draws what
|
|
1226
|
+
// was attached before the transcript is persisted.
|
|
1227
|
+
...(extra.attachments?.length ? { attachmentItems: extra.attachments } : {}),
|
|
1228
|
+
}
|
|
1111
1229
|
: null;
|
|
1112
|
-
dispatch({ kind: "send", message, runId, sessionKey });
|
|
1230
|
+
dispatch({ kind: "send", message, runId, sessionKey, ...(reanchor ? { reanchor } : {}) });
|
|
1113
1231
|
void client
|
|
1114
1232
|
.send({
|
|
1115
1233
|
sessionKey,
|
|
@@ -1121,12 +1239,16 @@ export function useAgentChat({
|
|
|
1121
1239
|
.then(() => refreshSessions())
|
|
1122
1240
|
.catch((err: unknown) => dispatch({ kind: "send/failed", message: errorText(err) }));
|
|
1123
1241
|
},
|
|
1124
|
-
[client, ephemeral, refreshSessions, storageScope, thinking],
|
|
1242
|
+
[client, ephemeral, mintKey, refreshSessions, storageScope, thinking],
|
|
1125
1243
|
);
|
|
1126
1244
|
|
|
1127
1245
|
const send = React.useCallback(
|
|
1128
|
-
(text: string, attachments?: unknown[]) => {
|
|
1129
|
-
run(text,
|
|
1246
|
+
(text: string, attachments?: unknown[], options?: AgentChatSendOptions) => {
|
|
1247
|
+
run(text, {
|
|
1248
|
+
...(attachments?.length ? { attachments } : {}),
|
|
1249
|
+
...(options?.model ? { model: options.model } : {}),
|
|
1250
|
+
...(options?.thinking ? { thinking: options.thinking } : {}),
|
|
1251
|
+
});
|
|
1130
1252
|
},
|
|
1131
1253
|
[run],
|
|
1132
1254
|
);
|
|
@@ -1138,6 +1260,43 @@ export function useAgentChat({
|
|
|
1138
1260
|
[run],
|
|
1139
1261
|
);
|
|
1140
1262
|
|
|
1263
|
+
const sendMcpAppAction = React.useCallback(
|
|
1264
|
+
(mcpAppAction: NonNullable<AgentChatSendInput["mcpAppAction"]>) => {
|
|
1265
|
+
run("", { mcpAppAction });
|
|
1266
|
+
},
|
|
1267
|
+
[run],
|
|
1268
|
+
);
|
|
1269
|
+
|
|
1270
|
+
const regenerate = React.useCallback(
|
|
1271
|
+
(messageId: string) => {
|
|
1272
|
+
const { messages, streaming } = stateRef.current;
|
|
1273
|
+
if (streaming) {
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
const index = messages.findIndex((m) => m.id === messageId);
|
|
1277
|
+
if (index < 1) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
let question: AgentChatMessage | null = null;
|
|
1281
|
+
for (let i = index - 1; i >= 0; i -= 1) {
|
|
1282
|
+
const candidate = messages[i];
|
|
1283
|
+
if (candidate?.role === "user" && !candidate.isToolMessage) {
|
|
1284
|
+
question = candidate;
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (question === null) {
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
dispatch({ kind: "message/remove", id: messageId });
|
|
1292
|
+
// The question is already in the transcript. It is re-sent, not
|
|
1293
|
+
// re-drawn: the existing bubble is moved onto the new run so the
|
|
1294
|
+
// transcript reads Q then the new A, not Q, Q, A.
|
|
1295
|
+
run(question.text, {}, question.id);
|
|
1296
|
+
},
|
|
1297
|
+
[run],
|
|
1298
|
+
);
|
|
1299
|
+
|
|
1141
1300
|
const abort = React.useCallback(() => {
|
|
1142
1301
|
const { runId, sessionKey } = stateRef.current;
|
|
1143
1302
|
dispatch({ kind: "abort" });
|
|
@@ -1180,8 +1339,15 @@ export function useAgentChat({
|
|
|
1180
1339
|
);
|
|
1181
1340
|
|
|
1182
1341
|
const newSession = React.useCallback(() => {
|
|
1183
|
-
setSession(mintSessionKey(client.agentId));
|
|
1184
|
-
}, [client, setSession]);
|
|
1342
|
+
setSession(mintKey ? mintKey() : mintSessionKey(client.agentId));
|
|
1343
|
+
}, [client, mintKey, setSession]);
|
|
1344
|
+
|
|
1345
|
+
const clearSession = React.useCallback(() => {
|
|
1346
|
+
dispatch({ kind: "session", key: null });
|
|
1347
|
+
if (!ephemeral) {
|
|
1348
|
+
writeStoredSessionKey(storageScope, null);
|
|
1349
|
+
}
|
|
1350
|
+
}, [ephemeral, storageScope]);
|
|
1185
1351
|
|
|
1186
1352
|
const resumeSession = React.useCallback(
|
|
1187
1353
|
(key: string) => {
|
|
@@ -1191,15 +1357,16 @@ export function useAgentChat({
|
|
|
1191
1357
|
[loadHistory, setSession],
|
|
1192
1358
|
);
|
|
1193
1359
|
|
|
1194
|
-
const loadOlder = React.useCallback(() => {
|
|
1360
|
+
const loadOlder = React.useCallback((): boolean => {
|
|
1195
1361
|
const current = stateRef.current;
|
|
1196
1362
|
if (!current.sessionKey || current.loadingHistory || !current.hasMoreHistory) {
|
|
1197
|
-
return;
|
|
1363
|
+
return false;
|
|
1198
1364
|
}
|
|
1365
|
+
const sessionKey = current.sessionKey;
|
|
1199
1366
|
dispatch({ kind: "history/loading" });
|
|
1200
1367
|
void client
|
|
1201
1368
|
.request<AgentChatHistoryResponse>("chat.history", {
|
|
1202
|
-
sessionKey
|
|
1369
|
+
sessionKey,
|
|
1203
1370
|
limit: 100,
|
|
1204
1371
|
// Records, not bubbles. See `historyCount`.
|
|
1205
1372
|
offset: current.historyCount,
|
|
@@ -1208,22 +1375,27 @@ export function useAgentChat({
|
|
|
1208
1375
|
const records = response?.messages ?? [];
|
|
1209
1376
|
dispatch({
|
|
1210
1377
|
kind: "history/older",
|
|
1378
|
+
sessionKey,
|
|
1211
1379
|
messages: parseHistoryMessages(records),
|
|
1212
1380
|
hasMore: response?.hasMore ?? false,
|
|
1213
1381
|
records: records.length,
|
|
1214
1382
|
});
|
|
1215
1383
|
})
|
|
1216
1384
|
.catch(() => dispatch({ kind: "history/failed" }));
|
|
1385
|
+
return true;
|
|
1217
1386
|
}, [client]);
|
|
1218
1387
|
|
|
1219
1388
|
return {
|
|
1220
1389
|
...state,
|
|
1221
1390
|
send,
|
|
1222
1391
|
sendAction,
|
|
1392
|
+
sendMcpAppAction,
|
|
1223
1393
|
abort,
|
|
1224
1394
|
resolveApproval,
|
|
1225
1395
|
newSession,
|
|
1226
1396
|
resumeSession,
|
|
1397
|
+
clearSession,
|
|
1398
|
+
regenerate,
|
|
1227
1399
|
loadOlder,
|
|
1228
1400
|
refreshSessions,
|
|
1229
1401
|
};
|
package/src/agent-chat/types.ts
CHANGED
|
@@ -57,7 +57,18 @@ export interface AgentChatMessage {
|
|
|
57
57
|
role: "user" | "assistant";
|
|
58
58
|
text: string;
|
|
59
59
|
timestamp: number;
|
|
60
|
+
/** Attachment names or references, as a transcript record carries them. */
|
|
60
61
|
attachments?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* What the user attached to this turn, as the host's composer shaped it.
|
|
64
|
+
* `unknown[]` because the shape is the host's — cortenaweb sends
|
|
65
|
+
* `{ fileName, mimeType, content }` objects — and this package only asks
|
|
66
|
+
* whether there are any. Set on the user message `send` adds, so the bubble
|
|
67
|
+
* can draw the strip before the transcript is persisted. Separate from
|
|
68
|
+
* `attachments` so that field keeps its `string[]` shape for every consumer
|
|
69
|
+
* already reading it.
|
|
70
|
+
*/
|
|
71
|
+
attachmentItems?: unknown[];
|
|
61
72
|
/** The chain of thought behind this turn, collapsed by default. */
|
|
62
73
|
thinkingText?: string;
|
|
63
74
|
isError?: boolean;
|
|
@@ -40,7 +40,7 @@ import { Maximize2, Minimize2, X } from "lucide-react";
|
|
|
40
40
|
import * as React from "react";
|
|
41
41
|
import { BrandMark } from "@/components/app-shell";
|
|
42
42
|
import { Button } from "@/components/button";
|
|
43
|
-
import { AgentChat, type
|
|
43
|
+
import { AgentChat, type AgentChatOwnedProps } from "@/components/agent-chat";
|
|
44
44
|
import { viewStorageKey, windowSessionStorage } from "@/agent-chat/session";
|
|
45
45
|
import type { AgentChatClient } from "@/agent-chat/types";
|
|
46
46
|
import { cn } from "@/lib/cn";
|
|
@@ -56,8 +56,8 @@ export interface AgentChatPopupExtension {
|
|
|
56
56
|
|
|
57
57
|
export interface AgentChatPopupProps
|
|
58
58
|
extends Omit<
|
|
59
|
-
|
|
60
|
-
"agentName" | "storageScope" | "className" | "headerSlot" | "client"
|
|
59
|
+
AgentChatOwnedProps,
|
|
60
|
+
"agentName" | "storageScope" | "className" | "headerSlot" | "client" | "chat"
|
|
61
61
|
> {
|
|
62
62
|
extension: AgentChatPopupExtension;
|
|
63
63
|
/** The transport. Its `agentId` is the template slug the sessions belong to. */
|