@drewswiredin/backstage-plugin-assistants 0.5.4 → 0.6.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/dist/AssistantsNavIcon.esm.js +35 -0
- package/dist/AssistantsNavIcon.esm.js.map +1 -0
- package/dist/collapsible/AssistantsList.esm.js +12 -5
- package/dist/collapsible/AssistantsList.esm.js.map +1 -1
- package/dist/collapsible/CollapsiblePage.esm.js +269 -342
- package/dist/collapsible/CollapsiblePage.esm.js.map +1 -1
- package/dist/collapsible/ConversationsPanel.esm.js +13 -7
- package/dist/collapsible/ConversationsPanel.esm.js.map +1 -1
- package/dist/collapsible/SidePane.esm.js +3 -2
- package/dist/collapsible/SidePane.esm.js.map +1 -1
- package/dist/collapsible/surface/ConversationSurface.esm.js +2 -12
- package/dist/collapsible/surface/ConversationSurface.esm.js.map +1 -1
- package/dist/collapsible/surface/parts.esm.js +35 -8
- package/dist/collapsible/surface/parts.esm.js.map +1 -1
- package/dist/collapsible/threadListAdapter.esm.js +153 -0
- package/dist/collapsible/threadListAdapter.esm.js.map +1 -0
- package/dist/collapsible/useAssistantRuntime.esm.js +66 -0
- package/dist/collapsible/useAssistantRuntime.esm.js.map +1 -0
- package/dist/collapsible/useThreadStatus.esm.js +108 -0
- package/dist/collapsible/useThreadStatus.esm.js.map +1 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/package.json +4 -2
- package/dist/collapsible/unreadStore.esm.js +0 -79
- package/dist/collapsible/unreadStore.esm.js.map +0 -1
- package/dist/collapsible/useConversations.esm.js +0 -171
- package/dist/collapsible/useConversations.esm.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import './surface/styles/assistant-ui.css.esm.js';
|
|
3
3
|
import './surface/styles/assistant-ui-markdown.css.esm.js';
|
|
4
|
-
import {
|
|
4
|
+
import { useRef, useMemo, useState, useEffect, useCallback } from 'react';
|
|
5
5
|
import { useSearchParams } from 'react-router-dom';
|
|
6
6
|
import useAsync from 'react-use/lib/useAsync';
|
|
7
7
|
import { useApi } from '@backstage/core-plugin-api';
|
|
@@ -13,16 +13,15 @@ import ChatBubbleOutlineIcon from '@material-ui/icons/ChatBubbleOutline';
|
|
|
13
13
|
import CheckIcon from '@material-ui/icons/Check';
|
|
14
14
|
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
|
|
15
15
|
import StarIcon from '@material-ui/icons/Star';
|
|
16
|
-
import { AssistantRuntimeProvider,
|
|
17
|
-
import { useChatRuntime } from '@assistant-ui/react-ai-sdk';
|
|
18
|
-
import { DefaultChatTransport } from 'ai';
|
|
16
|
+
import { useRemoteThreadListRuntime, AssistantRuntimeProvider, useAssistantRuntime } from '@assistant-ui/react';
|
|
19
17
|
import { assistantsApiRef } from '../api/AssistantsApi.esm.js';
|
|
20
18
|
import { ConversationSurface } from './surface/ConversationSurface.esm.js';
|
|
21
19
|
import { AssistantAvatar } from './surface/AssistantAvatar.esm.js';
|
|
22
20
|
import { SidePane } from './SidePane.esm.js';
|
|
23
21
|
import { FullHeightRegion } from './FullHeightRegion.esm.js';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
22
|
+
import { createThreadListAdapter, patchThread } from './threadListAdapter.esm.js';
|
|
23
|
+
import { makeRuntimeHook } from './useAssistantRuntime.esm.js';
|
|
24
|
+
import { useThreadStatus } from './useThreadStatus.esm.js';
|
|
26
25
|
|
|
27
26
|
const SIDEPANE_COLLAPSED_KEY = "ai-chat-sidepane-collapsed";
|
|
28
27
|
function loadSidePaneCollapsed() {
|
|
@@ -33,8 +32,6 @@ function loadSidePaneCollapsed() {
|
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
34
|
const useStyles = makeStyles((theme) => ({
|
|
36
|
-
// Flex-column fill inside the measured FullHeightRegion (mirrors the native
|
|
37
|
-
// page): lets the shell own the remaining height without a nested <Page>.
|
|
38
35
|
content: {
|
|
39
36
|
flex: 1,
|
|
40
37
|
minHeight: 0,
|
|
@@ -47,10 +44,6 @@ const useStyles = makeStyles((theme) => ({
|
|
|
47
44
|
minHeight: 0,
|
|
48
45
|
overflow: "hidden",
|
|
49
46
|
backgroundColor: theme.palette.background.default,
|
|
50
|
-
// No top gutter so the card tucks flush under the page header (reclaims the
|
|
51
|
-
// gap there); 8px right/bottom keep it enclosed on those sides, 0 left since
|
|
52
|
-
// it abuts the sidebar. Composer breathing room lives on the Thread's own
|
|
53
|
-
// footer (see ConversationSurface) — same bg, no seam.
|
|
54
47
|
paddingTop: 0,
|
|
55
48
|
paddingRight: theme.spacing(1),
|
|
56
49
|
paddingBottom: theme.spacing(1),
|
|
@@ -137,7 +130,6 @@ const useStyles = makeStyles((theme) => ({
|
|
|
137
130
|
boxShadow: theme.shadows[1],
|
|
138
131
|
overflow: "hidden"
|
|
139
132
|
},
|
|
140
|
-
// Slim, solid header.
|
|
141
133
|
threadHeader: {
|
|
142
134
|
display: "flex",
|
|
143
135
|
alignItems: "center",
|
|
@@ -194,11 +186,6 @@ const useStyles = makeStyles((theme) => ({
|
|
|
194
186
|
right: theme.spacing(0.5)
|
|
195
187
|
}
|
|
196
188
|
},
|
|
197
|
-
// Trigger label: vendor in muted text, model name emphasized.
|
|
198
|
-
modelTriggerVendor: {
|
|
199
|
-
color: theme.palette.text.hint,
|
|
200
|
-
marginRight: theme.spacing(0.5)
|
|
201
|
-
},
|
|
202
189
|
modelGroupLabel: {
|
|
203
190
|
lineHeight: 2,
|
|
204
191
|
fontSize: theme.typography.caption.fontSize,
|
|
@@ -226,25 +213,17 @@ const useStyles = makeStyles((theme) => ({
|
|
|
226
213
|
minHeight: 0,
|
|
227
214
|
display: "flex",
|
|
228
215
|
flexDirection: "column"
|
|
229
|
-
// NOTE: no paddingBottom here — the Thread fills this box with its own
|
|
230
|
-
// --aui-background; padding would expose the card's paper bg and create a
|
|
231
|
-
// two-tone seam. Composer breathing room lives on .aui-thread-viewport-footer
|
|
232
|
-
// (same bg) in ConversationSurface.
|
|
233
216
|
},
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
217
|
+
// The "generating" indicator: a pulsing dot, distinct from the solid unread dot.
|
|
218
|
+
"@keyframes auiPulse": {
|
|
219
|
+
"0%": { transform: "scale(1)", opacity: 1 },
|
|
220
|
+
"50%": { transform: "scale(1.5)", opacity: 0.45 },
|
|
221
|
+
"100%": { transform: "scale(1)", opacity: 1 }
|
|
222
|
+
},
|
|
223
|
+
pulseDot: {
|
|
224
|
+
animation: "$auiPulse 1.2s ease-in-out infinite"
|
|
237
225
|
}
|
|
238
226
|
}));
|
|
239
|
-
function RunningReporter({
|
|
240
|
-
onRunningChange
|
|
241
|
-
}) {
|
|
242
|
-
const isRunning = useThread((t) => t.isRunning);
|
|
243
|
-
useEffect(() => {
|
|
244
|
-
onRunningChange?.(isRunning);
|
|
245
|
-
}, [isRunning, onRunningChange]);
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
227
|
function splitModel(id, pool) {
|
|
249
228
|
const opt = pool.find((m) => m.id === id);
|
|
250
229
|
const raw = opt?.model ?? id;
|
|
@@ -257,262 +236,245 @@ function splitModel(id, pool) {
|
|
|
257
236
|
function modelLabel(id, pool) {
|
|
258
237
|
return splitModel(id, pool).name;
|
|
259
238
|
}
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
239
|
+
function CollapsiblePage() {
|
|
240
|
+
const api = useApi(assistantsApiRef);
|
|
241
|
+
const [searchParams] = useSearchParams();
|
|
242
|
+
const requestedAssistant = searchParams.get("assistant");
|
|
243
|
+
const status = useAsync(() => api.getStatus(), [api]);
|
|
244
|
+
if (status.loading) {
|
|
245
|
+
return /* @__PURE__ */ jsx(Progress, {});
|
|
246
|
+
}
|
|
247
|
+
if (status.error) {
|
|
248
|
+
return /* @__PURE__ */ jsx(ResponseErrorPanel, { error: status.error });
|
|
249
|
+
}
|
|
250
|
+
if (!status.value) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const assistants = status.value.assistants;
|
|
254
|
+
if (assistants.length === 0) {
|
|
255
|
+
return /* @__PURE__ */ jsx(
|
|
256
|
+
ResponseErrorPanel,
|
|
257
|
+
{
|
|
258
|
+
error: new Error("No assistants are available to you.")
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const assistant = assistants.find((a) => a.id === requestedAssistant) ?? assistants[0];
|
|
263
|
+
return /* @__PURE__ */ jsx(CollapsibleChat, { status: status.value, assistant }, assistant.id);
|
|
264
|
+
}
|
|
265
|
+
function CollapsibleChat({
|
|
266
|
+
status,
|
|
267
|
+
assistant
|
|
275
268
|
}) {
|
|
276
|
-
const
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
assistantId: selectionRef.current.assistantId,
|
|
285
|
-
modelId: selectionRef.current.modelId
|
|
286
|
-
})
|
|
287
|
-
}),
|
|
288
|
-
[authFetch, baseUrl]
|
|
289
|
-
);
|
|
290
|
-
const onFinishRef = useRef(onFinish);
|
|
291
|
-
onFinishRef.current = onFinish;
|
|
292
|
-
const stableOnFinish = useCallback(({ messages }) => {
|
|
293
|
-
onFinishRef.current?.(messages);
|
|
294
|
-
}, []);
|
|
295
|
-
const runtime = useChatRuntime({
|
|
296
|
-
transport,
|
|
297
|
-
messages: initialMessages,
|
|
298
|
-
onFinish: stableOnFinish
|
|
299
|
-
});
|
|
300
|
-
return /* @__PURE__ */ jsxs(AssistantRuntimeProvider, { runtime, children: [
|
|
301
|
-
/* @__PURE__ */ jsx(RunningReporter, { onRunningChange }),
|
|
302
|
-
/* @__PURE__ */ jsxs(
|
|
303
|
-
"main",
|
|
269
|
+
const api = useApi(assistantsApiRef);
|
|
270
|
+
const baseUrl = useAsync(() => api.getBaseUrl(), [api]);
|
|
271
|
+
if (baseUrl.loading) {
|
|
272
|
+
return /* @__PURE__ */ jsx(Progress, {});
|
|
273
|
+
}
|
|
274
|
+
if (baseUrl.error || !baseUrl.value) {
|
|
275
|
+
return /* @__PURE__ */ jsx(
|
|
276
|
+
ResponseErrorPanel,
|
|
304
277
|
{
|
|
305
|
-
|
|
306
|
-
"aria-label": "AI chat thread",
|
|
307
|
-
"aria-hidden": hidden,
|
|
308
|
-
children: [
|
|
309
|
-
/* @__PURE__ */ jsxs("div", { className: classes.threadHeader, children: [
|
|
310
|
-
/* @__PURE__ */ jsxs("div", { className: classes.threadIdentity, children: [
|
|
311
|
-
/* @__PURE__ */ jsx(AssistantAvatar, { color: assistantColor, size: 22 }),
|
|
312
|
-
/* @__PURE__ */ jsx(Typography, { variant: "subtitle2", className: classes.assistantName, children: assistantName }),
|
|
313
|
-
title && /* @__PURE__ */ jsxs(
|
|
314
|
-
Typography,
|
|
315
|
-
{
|
|
316
|
-
variant: "body2",
|
|
317
|
-
className: classes.threadTitle,
|
|
318
|
-
title,
|
|
319
|
-
children: [
|
|
320
|
-
"\xB7 ",
|
|
321
|
-
title
|
|
322
|
-
]
|
|
323
|
-
}
|
|
324
|
-
)
|
|
325
|
-
] }),
|
|
326
|
-
headerRight
|
|
327
|
-
] }),
|
|
328
|
-
/* @__PURE__ */ jsx("div", { className: classes.threadBody, children: /* @__PURE__ */ jsx(
|
|
329
|
-
ConversationSurface,
|
|
330
|
-
{
|
|
331
|
-
composerPlaceholder,
|
|
332
|
-
suggestions,
|
|
333
|
-
assistantColor
|
|
334
|
-
}
|
|
335
|
-
) })
|
|
336
|
-
]
|
|
278
|
+
error: baseUrl.error ?? new Error("Failed to resolve backend URL")
|
|
337
279
|
}
|
|
338
|
-
)
|
|
339
|
-
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return /* @__PURE__ */ jsx(
|
|
283
|
+
ChatRuntime,
|
|
284
|
+
{
|
|
285
|
+
status,
|
|
286
|
+
assistant,
|
|
287
|
+
api,
|
|
288
|
+
baseUrl: baseUrl.value
|
|
289
|
+
}
|
|
290
|
+
);
|
|
340
291
|
}
|
|
341
|
-
function
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
292
|
+
function ChatRuntime({
|
|
293
|
+
status,
|
|
294
|
+
assistant,
|
|
295
|
+
api,
|
|
296
|
+
baseUrl
|
|
297
|
+
}) {
|
|
298
|
+
const defaultModel = assistant.defaultModel ?? status.defaultModel;
|
|
299
|
+
const modelIdRef = useRef(defaultModel);
|
|
300
|
+
const adapter = useMemo(
|
|
301
|
+
() => createThreadListAdapter(api, assistant.id),
|
|
302
|
+
[api, assistant.id]
|
|
345
303
|
);
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (keep.has(d.convId)) {
|
|
362
|
-
next.push(byId.get(d.convId) ?? d);
|
|
363
|
-
keep.delete(d.convId);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
for (const id of keep) {
|
|
367
|
-
const d = byId.get(id);
|
|
368
|
-
if (d) {
|
|
369
|
-
next.push(d);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
const unchanged = next.length === prev.length && next.every((d, i) => d.convId === prev[i].convId);
|
|
373
|
-
return unchanged ? prev : next;
|
|
374
|
-
});
|
|
375
|
-
}, [active?.convId, running]);
|
|
376
|
-
const setThreadRunning = useCallback((convId, isRunning) => {
|
|
377
|
-
setRunning((prev) => {
|
|
378
|
-
const has = prev.has(convId);
|
|
379
|
-
if (isRunning === has) {
|
|
380
|
-
return prev;
|
|
381
|
-
}
|
|
382
|
-
const next = new Set(prev);
|
|
383
|
-
if (isRunning) {
|
|
384
|
-
next.add(convId);
|
|
385
|
-
} else {
|
|
386
|
-
next.delete(convId);
|
|
387
|
-
}
|
|
388
|
-
return next;
|
|
389
|
-
});
|
|
390
|
-
}, []);
|
|
391
|
-
const threads = active && !mounted.some((d) => d.convId === active.convId) ? [...mounted, active] : mounted;
|
|
392
|
-
return { threads, setThreadRunning };
|
|
304
|
+
const runtimeHook = useMemo(
|
|
305
|
+
() => makeRuntimeHook({ api, baseUrl, assistantId: assistant.id, modelIdRef }),
|
|
306
|
+
[api, baseUrl, assistant.id]
|
|
307
|
+
);
|
|
308
|
+
const runtime = useRemoteThreadListRuntime({ adapter, runtimeHook });
|
|
309
|
+
return /* @__PURE__ */ jsx(AssistantRuntimeProvider, { runtime, children: /* @__PURE__ */ jsx(
|
|
310
|
+
ChatChrome,
|
|
311
|
+
{
|
|
312
|
+
status,
|
|
313
|
+
assistant,
|
|
314
|
+
api,
|
|
315
|
+
modelIdRef,
|
|
316
|
+
defaultModel
|
|
317
|
+
}
|
|
318
|
+
) });
|
|
393
319
|
}
|
|
394
|
-
function
|
|
320
|
+
function ChatChrome({
|
|
321
|
+
status,
|
|
322
|
+
assistant,
|
|
323
|
+
api,
|
|
324
|
+
modelIdRef,
|
|
325
|
+
defaultModel
|
|
326
|
+
}) {
|
|
395
327
|
const classes = useStyles();
|
|
396
|
-
const
|
|
397
|
-
useUnreadVersion();
|
|
328
|
+
const runtime = useAssistantRuntime();
|
|
398
329
|
const [, setSearchParams] = useSearchParams();
|
|
399
|
-
const
|
|
400
|
-
(
|
|
401
|
-
if (id !== assistant.id) {
|
|
402
|
-
setSearchParams({ assistant: id });
|
|
403
|
-
}
|
|
404
|
-
},
|
|
405
|
-
[assistant.id, setSearchParams]
|
|
330
|
+
const [threadList, setThreadList] = useState(
|
|
331
|
+
() => runtime.threads.getState()
|
|
406
332
|
);
|
|
407
|
-
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
setThreadList(runtime.threads.getState());
|
|
335
|
+
return runtime.threads.subscribe(
|
|
336
|
+
() => setThreadList(runtime.threads.getState())
|
|
337
|
+
);
|
|
338
|
+
}, [runtime]);
|
|
339
|
+
const activeId = threadList.mainThreadId;
|
|
340
|
+
const activeItem = threadList.threadItems[activeId];
|
|
341
|
+
const activeRemoteId = activeItem?.remoteId;
|
|
342
|
+
const activeTitle = activeItem?.title ?? "";
|
|
343
|
+
const { statusOf, agentStatus, markRead, finishedTick } = useThreadStatus(
|
|
344
|
+
api,
|
|
345
|
+
activeRemoteId
|
|
346
|
+
);
|
|
347
|
+
const didSelectInitial = useRef(false);
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
if (didSelectInitial.current || threadList.isLoading) return;
|
|
350
|
+
didSelectInitial.current = true;
|
|
351
|
+
if (activeId !== threadList.newThreadId) return;
|
|
352
|
+
const mostRecent = [...threadList.threadIds].map((id) => ({
|
|
353
|
+
id,
|
|
354
|
+
updatedAt: threadList.threadItems[id]?.custom?.updatedAt ?? ""
|
|
355
|
+
})).sort((a, b) => a.updatedAt < b.updatedAt ? 1 : -1)[0]?.id;
|
|
356
|
+
if (mostRecent) {
|
|
357
|
+
void runtime.threads.switchToThread(mostRecent);
|
|
358
|
+
}
|
|
359
|
+
}, [threadList.isLoading]);
|
|
360
|
+
const conversations = useMemo(
|
|
361
|
+
() => threadList.threadIds.map((id) => {
|
|
362
|
+
const item = threadList.threadItems[id];
|
|
363
|
+
const custom = item?.custom;
|
|
364
|
+
const st = id === activeId ? "read" : statusOf(item?.remoteId);
|
|
365
|
+
return {
|
|
366
|
+
id,
|
|
367
|
+
remoteId: item?.remoteId,
|
|
368
|
+
title: item?.title ?? "New Chat",
|
|
369
|
+
pinned: custom?.pinned ?? false,
|
|
370
|
+
unread: st === "unread",
|
|
371
|
+
generating: st === "working"
|
|
372
|
+
};
|
|
373
|
+
}),
|
|
374
|
+
[threadList, activeId, statusOf]
|
|
375
|
+
);
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
if (finishedTick > 0) {
|
|
378
|
+
void runtime.threads.reload();
|
|
379
|
+
}
|
|
380
|
+
}, [finishedTick, runtime]);
|
|
408
381
|
const allowedModels = useMemo(
|
|
409
382
|
() => assistant.models ?? status.models.map((m) => m.id),
|
|
410
383
|
[assistant.models, status.models]
|
|
411
384
|
);
|
|
412
|
-
const defaultModel = assistant.defaultModel ?? status.defaultModel;
|
|
413
385
|
const modelGroups = useMemo(() => {
|
|
414
386
|
const byVendor = /* @__PURE__ */ new Map();
|
|
415
387
|
for (const id of allowedModels) {
|
|
416
388
|
const { vendor } = splitModel(id, status.models);
|
|
417
389
|
const list = byVendor.get(vendor);
|
|
418
|
-
if (list)
|
|
419
|
-
|
|
420
|
-
} else {
|
|
421
|
-
byVendor.set(vendor, [id]);
|
|
422
|
-
}
|
|
390
|
+
if (list) list.push(id);
|
|
391
|
+
else byVendor.set(vendor, [id]);
|
|
423
392
|
}
|
|
424
393
|
return [...byVendor.entries()];
|
|
425
394
|
}, [allowedModels, status.models]);
|
|
426
|
-
const convState = useConversations(assistant.id);
|
|
427
395
|
const resolveModel = useCallback(
|
|
428
396
|
(stored) => stored && allowedModels.includes(stored) ? stored : defaultModel,
|
|
429
397
|
[allowedModels, defaultModel]
|
|
430
398
|
);
|
|
431
399
|
const [modelId, setModelId] = useState(
|
|
432
|
-
() => resolveModel(
|
|
400
|
+
() => resolveModel(activeItem?.custom?.model)
|
|
433
401
|
);
|
|
434
402
|
useEffect(() => {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
[convState]
|
|
445
|
-
);
|
|
446
|
-
const [sidePaneCollapsed, setSidePaneCollapsed] = useState(
|
|
447
|
-
loadSidePaneCollapsed
|
|
448
|
-
);
|
|
403
|
+
const stored = threadList.threadItems[activeId]?.custom?.model;
|
|
404
|
+
const next = resolveModel(stored);
|
|
405
|
+
setModelId(next);
|
|
406
|
+
modelIdRef.current = next;
|
|
407
|
+
}, [activeId]);
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
if (activeRemoteId) markRead(activeRemoteId);
|
|
410
|
+
}, [activeRemoteId]);
|
|
411
|
+
const [sidePaneCollapsed, setSidePaneCollapsed] = useState(loadSidePaneCollapsed);
|
|
449
412
|
useEffect(() => {
|
|
450
413
|
try {
|
|
451
414
|
localStorage.setItem(SIDEPANE_COLLAPSED_KEY, String(sidePaneCollapsed));
|
|
452
415
|
} catch {
|
|
453
416
|
}
|
|
454
417
|
}, [sidePaneCollapsed]);
|
|
418
|
+
const handleSelectAssistant = useCallback(
|
|
419
|
+
(id) => {
|
|
420
|
+
if (id !== assistant.id) setSearchParams({ assistant: id });
|
|
421
|
+
},
|
|
422
|
+
[assistant.id, setSearchParams]
|
|
423
|
+
);
|
|
455
424
|
const handleNew = useCallback(() => {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (sameAgent) {
|
|
463
|
-
convState.updateMessages(descriptor.convId, messages);
|
|
464
|
-
} else {
|
|
465
|
-
persistConversationMessages(
|
|
466
|
-
descriptor.agentId,
|
|
467
|
-
descriptor.convId,
|
|
468
|
-
messages
|
|
469
|
-
);
|
|
425
|
+
void (async () => {
|
|
426
|
+
await runtime.threads.switchToNewThread();
|
|
427
|
+
try {
|
|
428
|
+
await runtime.threads.mainItem.initialize();
|
|
429
|
+
await runtime.threads.reload();
|
|
430
|
+
} catch {
|
|
470
431
|
}
|
|
471
|
-
|
|
472
|
-
|
|
432
|
+
})();
|
|
433
|
+
}, [runtime]);
|
|
434
|
+
const handleSelect = useCallback(
|
|
435
|
+
(id) => {
|
|
436
|
+
if (id) void runtime.threads.switchToThread(id);
|
|
437
|
+
},
|
|
438
|
+
[runtime]
|
|
439
|
+
);
|
|
440
|
+
const handleRename = useCallback(
|
|
441
|
+
(id, title) => {
|
|
442
|
+
void (async () => {
|
|
443
|
+
await runtime.threads.getItemById(id).rename(title);
|
|
444
|
+
await runtime.threads.reload();
|
|
445
|
+
})();
|
|
446
|
+
},
|
|
447
|
+
[runtime]
|
|
448
|
+
);
|
|
449
|
+
const handleDelete = useCallback(
|
|
450
|
+
(id) => {
|
|
451
|
+
void runtime.threads.getItemById(id).delete();
|
|
452
|
+
},
|
|
453
|
+
[runtime]
|
|
454
|
+
);
|
|
455
|
+
const handlePin = useCallback(
|
|
456
|
+
async (id) => {
|
|
457
|
+
const item = threadList.threadItems[id];
|
|
458
|
+
if (!item?.remoteId) return;
|
|
459
|
+
const pinned = item.custom?.pinned ?? false;
|
|
460
|
+
try {
|
|
461
|
+
await patchThread(api, item.remoteId, { pinned: !pinned });
|
|
462
|
+
await runtime.threads.reload();
|
|
463
|
+
} catch {
|
|
473
464
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
messages
|
|
484
|
-
}).then((generated) => {
|
|
485
|
-
if (generated && generated !== "New Chat") {
|
|
486
|
-
convState.renameConversation(descriptor.convId, generated);
|
|
487
|
-
}
|
|
488
|
-
}).catch(() => {
|
|
489
|
-
});
|
|
490
|
-
}
|
|
465
|
+
},
|
|
466
|
+
[api, runtime, threadList]
|
|
467
|
+
);
|
|
468
|
+
const handleModelChange = useCallback(
|
|
469
|
+
(next) => {
|
|
470
|
+
setModelId(next);
|
|
471
|
+
modelIdRef.current = next;
|
|
472
|
+
if (activeRemoteId) {
|
|
473
|
+
void patchThread(api, activeRemoteId, { model: next });
|
|
491
474
|
}
|
|
492
475
|
},
|
|
493
|
-
[api,
|
|
476
|
+
[api, activeRemoteId, modelIdRef]
|
|
494
477
|
);
|
|
495
|
-
useEffect(() => {
|
|
496
|
-
if (!convState.activeId) {
|
|
497
|
-
convState.createConversation();
|
|
498
|
-
}
|
|
499
|
-
}, []);
|
|
500
|
-
useEffect(() => {
|
|
501
|
-
if (convState.activeId) {
|
|
502
|
-
clearUnread(assistant.id, convState.activeId);
|
|
503
|
-
}
|
|
504
|
-
}, [assistant.id, convState.activeId]);
|
|
505
|
-
const activeDescriptor = convState.activeId ? {
|
|
506
|
-
convId: convState.activeId,
|
|
507
|
-
agentId: assistant.id,
|
|
508
|
-
assistantTitle: assistant.title,
|
|
509
|
-
assistantColor: assistant.color,
|
|
510
|
-
modelId,
|
|
511
|
-
initialMessages: convState.activeConversation?.messages,
|
|
512
|
-
composerPlaceholder: assistant.ui?.composer?.placeholder,
|
|
513
|
-
suggestions: assistant.ui?.suggestions
|
|
514
|
-
} : null;
|
|
515
|
-
const { threads, setThreadRunning } = useLiveThreads(activeDescriptor);
|
|
516
478
|
const modelPicker = /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(
|
|
517
479
|
Select,
|
|
518
480
|
{
|
|
@@ -551,17 +513,6 @@ function CollapsibleChat({ status, assistant }) {
|
|
|
551
513
|
])
|
|
552
514
|
}
|
|
553
515
|
) });
|
|
554
|
-
if (baseUrl.loading) {
|
|
555
|
-
return /* @__PURE__ */ jsx(Progress, {});
|
|
556
|
-
}
|
|
557
|
-
if (baseUrl.error || !baseUrl.value) {
|
|
558
|
-
return /* @__PURE__ */ jsx(
|
|
559
|
-
ResponseErrorPanel,
|
|
560
|
-
{
|
|
561
|
-
error: baseUrl.error ?? new Error("Failed to resolve backend URL")
|
|
562
|
-
}
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
516
|
return /* @__PURE__ */ jsx(FullHeightRegion, { children: /* @__PURE__ */ jsx(Content, { noPadding: true, className: classes.content, children: /* @__PURE__ */ jsxs("div", { className: classes.shell, children: [
|
|
566
517
|
sidePaneCollapsed ? /* @__PURE__ */ jsxs(
|
|
567
518
|
"aside",
|
|
@@ -579,32 +530,26 @@ function CollapsibleChat({ status, assistant }) {
|
|
|
579
530
|
children: /* @__PURE__ */ jsx(ChevronRightIcon, { fontSize: "small" })
|
|
580
531
|
}
|
|
581
532
|
) }) }),
|
|
582
|
-
/* @__PURE__ */ jsx(
|
|
583
|
-
|
|
533
|
+
/* @__PURE__ */ jsx("nav", { className: classes.sidePaneRailAssistants, "aria-label": "Assistants", children: status.assistants.map((a) => /* @__PURE__ */ jsx(Tooltip, { title: a.title, placement: "right", children: /* @__PURE__ */ jsx(
|
|
534
|
+
IconButton,
|
|
584
535
|
{
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
536
|
+
size: "small",
|
|
537
|
+
className: `${classes.sidePaneRailButton} ${a.id === assistant.id ? classes.sidePaneRailButtonActive : ""}`,
|
|
538
|
+
"aria-label": a.title,
|
|
539
|
+
onClick: () => handleSelectAssistant(a.id),
|
|
540
|
+
children: /* @__PURE__ */ jsx(
|
|
541
|
+
Badge,
|
|
589
542
|
{
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
"
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
{
|
|
597
|
-
color: "error",
|
|
598
|
-
variant: "dot",
|
|
599
|
-
overlap: "circular",
|
|
600
|
-
invisible: !hasUnread(a.id),
|
|
601
|
-
children: /* @__PURE__ */ jsx(AssistantAvatar, { color: a.color, size: 24 })
|
|
602
|
-
}
|
|
603
|
-
)
|
|
543
|
+
color: agentStatus(a.id) === "working" ? "primary" : "error",
|
|
544
|
+
variant: "dot",
|
|
545
|
+
overlap: "circular",
|
|
546
|
+
invisible: agentStatus(a.id) === "read",
|
|
547
|
+
classes: agentStatus(a.id) === "working" ? { dot: classes.pulseDot } : void 0,
|
|
548
|
+
children: /* @__PURE__ */ jsx(AssistantAvatar, { color: a.color, size: 24 })
|
|
604
549
|
}
|
|
605
|
-
)
|
|
550
|
+
)
|
|
606
551
|
}
|
|
607
|
-
),
|
|
552
|
+
) }, a.id)) }),
|
|
608
553
|
/* @__PURE__ */ jsx("div", { className: classes.sidePaneRailDivider }),
|
|
609
554
|
/* @__PURE__ */ jsx("div", { className: classes.sidePaneRailControls, children: /* @__PURE__ */ jsx(Tooltip, { title: "New Chat", placement: "right", children: /* @__PURE__ */ jsx(
|
|
610
555
|
IconButton,
|
|
@@ -621,7 +566,7 @@ function CollapsibleChat({ status, assistant }) {
|
|
|
621
566
|
{
|
|
622
567
|
className: classes.sidePaneRailChats,
|
|
623
568
|
"aria-label": "AI chat conversations",
|
|
624
|
-
children:
|
|
569
|
+
children: conversations.map((conversation) => /* @__PURE__ */ jsx(
|
|
625
570
|
Tooltip,
|
|
626
571
|
{
|
|
627
572
|
title: conversation.title,
|
|
@@ -630,16 +575,17 @@ function CollapsibleChat({ status, assistant }) {
|
|
|
630
575
|
IconButton,
|
|
631
576
|
{
|
|
632
577
|
size: "small",
|
|
633
|
-
className: `${classes.sidePaneRailButton} ${conversation.id ===
|
|
578
|
+
className: `${classes.sidePaneRailButton} ${conversation.id === activeId ? classes.sidePaneRailButtonActive : ""}`,
|
|
634
579
|
"aria-label": conversation.title,
|
|
635
|
-
onClick: () =>
|
|
580
|
+
onClick: () => handleSelect(conversation.id),
|
|
636
581
|
children: /* @__PURE__ */ jsx(
|
|
637
582
|
Badge,
|
|
638
583
|
{
|
|
639
|
-
color: "error",
|
|
584
|
+
color: conversation.generating ? "primary" : "error",
|
|
640
585
|
variant: "dot",
|
|
641
586
|
overlap: "circular",
|
|
642
|
-
invisible: conversation.
|
|
587
|
+
invisible: !conversation.generating && !conversation.unread,
|
|
588
|
+
classes: conversation.generating ? { dot: classes.pulseDot } : void 0,
|
|
643
589
|
children: /* @__PURE__ */ jsx(ChatBubbleOutlineIcon, { fontSize: "small" })
|
|
644
590
|
}
|
|
645
591
|
)
|
|
@@ -658,67 +604,48 @@ function CollapsibleChat({ status, assistant }) {
|
|
|
658
604
|
assistants: status.assistants,
|
|
659
605
|
activeAssistantId: assistant.id,
|
|
660
606
|
onSelectAssistant: handleSelectAssistant,
|
|
661
|
-
|
|
662
|
-
|
|
607
|
+
agentStatus,
|
|
608
|
+
conversations,
|
|
609
|
+
activeId,
|
|
663
610
|
onNew: handleNew,
|
|
664
|
-
onSelect:
|
|
665
|
-
onRename:
|
|
666
|
-
onPin:
|
|
667
|
-
onDelete:
|
|
611
|
+
onSelect: handleSelect,
|
|
612
|
+
onRename: handleRename,
|
|
613
|
+
onPin: handlePin,
|
|
614
|
+
onDelete: handleDelete,
|
|
668
615
|
onCollapse: () => setSidePaneCollapsed(true)
|
|
669
616
|
}
|
|
670
617
|
) }),
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
618
|
+
/* @__PURE__ */ jsxs("main", { className: classes.threadPane, "aria-label": "AI chat thread", children: [
|
|
619
|
+
/* @__PURE__ */ jsxs("div", { className: classes.threadHeader, children: [
|
|
620
|
+
/* @__PURE__ */ jsxs("div", { className: classes.threadIdentity, children: [
|
|
621
|
+
/* @__PURE__ */ jsx(AssistantAvatar, { color: assistant.color, size: 22 }),
|
|
622
|
+
/* @__PURE__ */ jsx(Typography, { variant: "subtitle2", className: classes.assistantName, children: assistant.title }),
|
|
623
|
+
activeTitle && /* @__PURE__ */ jsxs(
|
|
624
|
+
Typography,
|
|
625
|
+
{
|
|
626
|
+
variant: "body2",
|
|
627
|
+
className: classes.threadTitle,
|
|
628
|
+
title: activeTitle,
|
|
629
|
+
children: [
|
|
630
|
+
"\xB7 ",
|
|
631
|
+
activeTitle
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
)
|
|
635
|
+
] }),
|
|
636
|
+
modelPicker
|
|
637
|
+
] }),
|
|
638
|
+
/* @__PURE__ */ jsx("div", { className: classes.threadBody, children: /* @__PURE__ */ jsx(
|
|
639
|
+
ConversationSurface,
|
|
675
640
|
{
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
assistantName: isActive ? assistant.title : d.assistantTitle,
|
|
683
|
-
initialMessages: d.initialMessages,
|
|
684
|
-
onFinish: (messages) => handleThreadFinish(d, messages),
|
|
685
|
-
onRunningChange: (running) => setThreadRunning(d.convId, running),
|
|
686
|
-
composerPlaceholder: d.composerPlaceholder,
|
|
687
|
-
suggestions: d.suggestions,
|
|
688
|
-
assistantColor: isActive ? assistant.color : d.assistantColor,
|
|
689
|
-
headerRight: isActive ? modelPicker : void 0
|
|
690
|
-
},
|
|
691
|
-
d.convId
|
|
692
|
-
);
|
|
693
|
-
})
|
|
641
|
+
composerPlaceholder: assistant.ui?.composer?.placeholder,
|
|
642
|
+
suggestions: assistant.ui?.suggestions,
|
|
643
|
+
assistantColor: assistant.color
|
|
644
|
+
}
|
|
645
|
+
) })
|
|
646
|
+
] })
|
|
694
647
|
] }) }) });
|
|
695
648
|
}
|
|
696
|
-
function CollapsiblePage() {
|
|
697
|
-
const api = useApi(assistantsApiRef);
|
|
698
|
-
const [searchParams] = useSearchParams();
|
|
699
|
-
const requestedAssistant = searchParams.get("assistant");
|
|
700
|
-
const status = useAsync(() => api.getStatus(), [api]);
|
|
701
|
-
if (status.loading) {
|
|
702
|
-
return /* @__PURE__ */ jsx(Progress, {});
|
|
703
|
-
}
|
|
704
|
-
if (status.error) {
|
|
705
|
-
return /* @__PURE__ */ jsx(ResponseErrorPanel, { error: status.error });
|
|
706
|
-
}
|
|
707
|
-
if (!status.value) {
|
|
708
|
-
return null;
|
|
709
|
-
}
|
|
710
|
-
const assistants = status.value.assistants;
|
|
711
|
-
if (assistants.length === 0) {
|
|
712
|
-
return /* @__PURE__ */ jsx(
|
|
713
|
-
ResponseErrorPanel,
|
|
714
|
-
{
|
|
715
|
-
error: new Error("No assistants are available to you.")
|
|
716
|
-
}
|
|
717
|
-
);
|
|
718
|
-
}
|
|
719
|
-
const assistant = assistants.find((a) => a.id === requestedAssistant) ?? assistants[0];
|
|
720
|
-
return /* @__PURE__ */ jsx(CollapsibleChat, { status: status.value, assistant });
|
|
721
|
-
}
|
|
722
649
|
|
|
723
650
|
export { CollapsiblePage };
|
|
724
651
|
//# sourceMappingURL=CollapsiblePage.esm.js.map
|