@drewswiredin/backstage-plugin-assistants 0.6.1 → 0.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/dist/AssistantsNavIcon.esm.js +2 -22
- package/dist/AssistantsNavIcon.esm.js.map +1 -1
- package/dist/collapsible/AssistantsList.esm.js +2 -12
- package/dist/collapsible/AssistantsList.esm.js.map +1 -1
- package/dist/collapsible/CollapsiblePage.esm.js +144 -71
- package/dist/collapsible/CollapsiblePage.esm.js.map +1 -1
- package/dist/collapsible/ConversationsPanel.esm.js +4 -12
- package/dist/collapsible/ConversationsPanel.esm.js.map +1 -1
- package/dist/collapsible/StatusDot.esm.js +39 -0
- package/dist/collapsible/StatusDot.esm.js.map +1 -0
- package/dist/collapsible/surface/parts.esm.js +5 -1
- package/dist/collapsible/surface/parts.esm.js.map +1 -1
- package/dist/collapsible/threadListAdapter.esm.js +9 -7
- package/dist/collapsible/threadListAdapter.esm.js.map +1 -1
- package/dist/collapsible/useAssistantRuntime.esm.js +59 -22
- package/dist/collapsible/useAssistantRuntime.esm.js.map +1 -1
- package/dist/collapsible/useThreadStatus.esm.js +61 -78
- package/dist/collapsible/useThreadStatus.esm.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,34 +1,14 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { Badge } from '@material-ui/core';
|
|
3
|
-
import { makeStyles } from '@material-ui/core/styles';
|
|
4
2
|
import { RiRobot2Line } from '@remixicon/react';
|
|
5
3
|
import { useApi } from '@backstage/core-plugin-api';
|
|
6
4
|
import { assistantsApiRef } from './api/AssistantsApi.esm.js';
|
|
7
5
|
import { useThreadStatus } from './collapsible/useThreadStatus.esm.js';
|
|
6
|
+
import { StatusDot } from './collapsible/StatusDot.esm.js';
|
|
8
7
|
|
|
9
|
-
const useStyles = makeStyles({
|
|
10
|
-
"@keyframes auiNavPulse": {
|
|
11
|
-
"0%": { transform: "scale(1)", opacity: 1 },
|
|
12
|
-
"50%": { transform: "scale(1.5)", opacity: 0.45 },
|
|
13
|
-
"100%": { transform: "scale(1)", opacity: 1 }
|
|
14
|
-
},
|
|
15
|
-
pulseDot: { animation: "$auiNavPulse 1.2s ease-in-out infinite" }
|
|
16
|
-
});
|
|
17
8
|
function AssistantsNavIcon() {
|
|
18
|
-
const classes = useStyles();
|
|
19
9
|
const api = useApi(assistantsApiRef);
|
|
20
10
|
const { overallStatus } = useThreadStatus(api, void 0);
|
|
21
|
-
return /* @__PURE__ */ jsx(
|
|
22
|
-
Badge,
|
|
23
|
-
{
|
|
24
|
-
color: overallStatus === "working" ? "primary" : "error",
|
|
25
|
-
variant: "dot",
|
|
26
|
-
overlap: "circular",
|
|
27
|
-
invisible: overallStatus === "read",
|
|
28
|
-
classes: overallStatus === "working" ? { dot: classes.pulseDot } : void 0,
|
|
29
|
-
children: /* @__PURE__ */ jsx(RiRobot2Line, {})
|
|
30
|
-
}
|
|
31
|
-
);
|
|
11
|
+
return /* @__PURE__ */ jsx(StatusDot, { status: overallStatus, children: /* @__PURE__ */ jsx(RiRobot2Line, {}) });
|
|
32
12
|
}
|
|
33
13
|
|
|
34
14
|
export { AssistantsNavIcon };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantsNavIcon.esm.js","sources":["../src/AssistantsNavIcon.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"AssistantsNavIcon.esm.js","sources":["../src/AssistantsNavIcon.tsx"],"sourcesContent":["import { RiRobot2Line } from '@remixicon/react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { assistantsApiRef } from './api';\nimport { useThreadStatus } from './collapsible/useThreadStatus';\nimport { StatusDot } from './collapsible/StatusDot';\n\n/**\n * The Assistants nav-rail icon with a live status dot: amber pulsing if any\n * conversation (across all assistants) is working, else solid red if any is\n * unread, else nothing — working wins. Shown on every Backstage page (it's a\n * sidebar item), and derived from the same server status snapshot as the in-page\n * indicators, so they never disagree.\n *\n * @public\n */\nexport function AssistantsNavIcon() {\n const api = useApi(assistantsApiRef);\n // No focused conversation in the nav context, so pass undefined.\n const { overallStatus } = useThreadStatus(api, undefined);\n\n return (\n <StatusDot status={overallStatus}>\n <RiRobot2Line />\n </StatusDot>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAeO,SAAS,iBAAA,GAAoB;AAClC,EAAA,MAAM,GAAA,GAAM,OAAO,gBAAgB,CAAA;AAEnC,EAAA,MAAM,EAAE,aAAA,EAAc,GAAI,eAAA,CAAgB,KAAK,MAAS,CAAA;AAExD,EAAA,2BACG,SAAA,EAAA,EAAU,MAAA,EAAQ,aAAA,EACjB,QAAA,kBAAA,GAAA,CAAC,gBAAa,CAAA,EAChB,CAAA;AAEJ;;;;"}
|
|
@@ -7,12 +7,12 @@ import ListItemIcon from '@material-ui/core/ListItemIcon';
|
|
|
7
7
|
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
|
|
8
8
|
import ListItemText from '@material-ui/core/ListItemText';
|
|
9
9
|
import IconButton from '@material-ui/core/IconButton';
|
|
10
|
-
import Badge from '@material-ui/core/Badge';
|
|
11
10
|
import Tooltip from '@material-ui/core/Tooltip';
|
|
12
11
|
import Typography from '@material-ui/core/Typography';
|
|
13
12
|
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
|
|
14
13
|
import { AssistantAvatar } from './surface/AssistantAvatar.esm.js';
|
|
15
14
|
import { AssistantDetailDialog } from './AssistantDetailDialog.esm.js';
|
|
15
|
+
import { StatusDot } from './StatusDot.esm.js';
|
|
16
16
|
|
|
17
17
|
const useStyles = makeStyles((theme) => ({
|
|
18
18
|
root: {
|
|
@@ -79,17 +79,7 @@ const AssistantsList = ({ assistants, activeId, onSelect, agentStatus }) => {
|
|
|
79
79
|
ContainerProps: { className: classes.container },
|
|
80
80
|
onClick: () => onSelect(a.id),
|
|
81
81
|
children: [
|
|
82
|
-
/* @__PURE__ */ jsx(ListItemIcon, { className: classes.icon, children: /* @__PURE__ */ jsx(
|
|
83
|
-
Badge,
|
|
84
|
-
{
|
|
85
|
-
color: agentStatus?.(a.id) === "working" ? "primary" : "error",
|
|
86
|
-
variant: "dot",
|
|
87
|
-
overlap: "circular",
|
|
88
|
-
invisible: (agentStatus?.(a.id) ?? "read") === "read",
|
|
89
|
-
classes: agentStatus?.(a.id) === "working" ? { dot: classes.pulseDot } : void 0,
|
|
90
|
-
children: /* @__PURE__ */ jsx(AssistantAvatar, { color: a.color, size: 22 })
|
|
91
|
-
}
|
|
92
|
-
) }),
|
|
82
|
+
/* @__PURE__ */ jsx(ListItemIcon, { className: classes.icon, children: /* @__PURE__ */ jsx(StatusDot, { status: agentStatus?.(a.id) ?? "read", children: /* @__PURE__ */ jsx(AssistantAvatar, { color: a.color, size: 22 }) }) }),
|
|
93
83
|
/* @__PURE__ */ jsx(
|
|
94
84
|
ListItemText,
|
|
95
85
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantsList.esm.js","sources":["../../src/collapsible/AssistantsList.tsx"],"sourcesContent":["import { FC, MouseEvent, useState } from 'react';\nimport { makeStyles } from '@material-ui/core/styles';\nimport List from '@material-ui/core/List';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemIcon from '@material-ui/core/ListItemIcon';\nimport ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport IconButton from '@material-ui/core/IconButton';\nimport
|
|
1
|
+
{"version":3,"file":"AssistantsList.esm.js","sources":["../../src/collapsible/AssistantsList.tsx"],"sourcesContent":["import { FC, MouseEvent, useState } from 'react';\nimport { makeStyles } from '@material-ui/core/styles';\nimport List from '@material-ui/core/List';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemIcon from '@material-ui/core/ListItemIcon';\nimport ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport IconButton from '@material-ui/core/IconButton';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport Typography from '@material-ui/core/Typography';\nimport InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';\nimport { AssistantSummary } from '@drewswiredin/backstage-plugin-assistants-common';\nimport { AssistantAvatar } from './surface/AssistantAvatar';\nimport { AssistantDetailDialog } from './AssistantDetailDialog';\nimport { StatusDot } from './StatusDot';\nimport type { ConvStatus } from './useThreadStatus';\n\nconst useStyles = makeStyles(theme => ({\n root: {\n display: 'flex',\n flexDirection: 'column',\n padding: theme.spacing(1, 1.5, 0),\n },\n label: {\n color: theme.palette.text.secondary,\n fontWeight: 600,\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n padding: theme.spacing(0, 0.5, 0.5),\n },\n item: {\n borderRadius: theme.shape.borderRadius,\n },\n // The hover rule lives on the container <li> (ListItem's ContainerProps), which\n // wraps BOTH the row and the secondary action. The action is a *sibling* of the\n // ListItem root, so a :hover rule on the row itself can't reach it.\n container: {\n '&:hover $infoAction': {\n opacity: 1,\n pointerEvents: 'auto',\n },\n },\n icon: {\n minWidth: 32,\n display: 'inline-flex',\n alignItems: 'center',\n },\n // Hidden until row hover (opacity + pointer-events so it's not clickable while\n // hidden); no reserved gutter so the title uses full width and truncates later.\n infoAction: {\n opacity: 0,\n pointerEvents: 'none',\n color: theme.palette.text.secondary,\n transition: theme.transitions.create('opacity'),\n },\n '@keyframes auiPulse': {\n '0%': { transform: 'scale(1)', opacity: 1 },\n '50%': { transform: 'scale(1.5)', opacity: 0.45 },\n '100%': { transform: 'scale(1)', opacity: 1 },\n },\n pulseDot: {\n animation: '$auiPulse 1.2s ease-in-out infinite',\n },\n}));\n\n/**\n * The in-page assistant switcher (top tier of the left sidebar). Lists every\n * accessible assistant; selecting one drives `?assistant=<id>` via `onSelect`,\n * which remounts the chat onto that assistant's (siloed) conversation set. The\n * active assistant is highlighted. A hover-revealed info button opens the\n * {@link AssistantDetailDialog} (description, tools, models).\n *\n * @public\n */\nexport const AssistantsList: FC<{\n assistants: AssistantSummary[];\n activeId: string;\n onSelect: (id: string) => void;\n /** Per-assistant rollup status (working/unread/read) for the rail dots. */\n agentStatus?: (assistantId: string) => ConvStatus;\n}> = ({ assistants, activeId, onSelect, agentStatus }) => {\n const classes = useStyles();\n const [detailId, setDetailId] = useState<string | null>(null);\n\n const openDetail = (e: MouseEvent<HTMLElement>, id: string) => {\n e.stopPropagation();\n setDetailId(id);\n };\n\n return (\n <div className={classes.root}>\n <Typography variant=\"caption\" className={classes.label}>\n Assistants\n </Typography>\n <List dense disablePadding>\n {assistants.map(a => (\n <ListItem\n key={a.id}\n button\n selected={a.id === activeId}\n className={classes.item}\n ContainerProps={{ className: classes.container }}\n onClick={() => onSelect(a.id)}\n >\n <ListItemIcon className={classes.icon}>\n <StatusDot status={agentStatus?.(a.id) ?? 'read'}>\n <AssistantAvatar color={a.color} size={22} />\n </StatusDot>\n </ListItemIcon>\n <ListItemText\n primary={a.title}\n primaryTypographyProps={{ variant: 'body2', noWrap: true }}\n />\n <ListItemSecondaryAction className={classes.infoAction}>\n <Tooltip title=\"Details\">\n <IconButton\n edge=\"end\"\n size=\"small\"\n aria-label={`${a.title} details`}\n onClick={e => openDetail(e, a.id)}\n >\n <InfoOutlinedIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </ListItemSecondaryAction>\n </ListItem>\n ))}\n </List>\n\n <AssistantDetailDialog\n assistant={assistants.find(a => a.id === detailId)}\n open={detailId !== null}\n onClose={() => setDetailId(null)}\n />\n </div>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
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 { useRef, useMemo, useState,
|
|
4
|
+
import { useRef, useMemo, useState, useCallback, useEffect } 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';
|
|
8
8
|
import { Progress, ResponseErrorPanel, Content } from '@backstage/core-components';
|
|
9
9
|
import { makeStyles } from '@material-ui/core/styles';
|
|
10
|
-
import { FormControl, Select, ListSubheader, MenuItem, ListItemIcon, Tooltip, IconButton,
|
|
10
|
+
import { FormControl, Select, ListSubheader, MenuItem, ListItemIcon, Tooltip, IconButton, Typography, Button } from '@material-ui/core';
|
|
11
11
|
import AddIcon from '@material-ui/icons/Add';
|
|
12
12
|
import ChatBubbleOutlineIcon from '@material-ui/icons/ChatBubbleOutline';
|
|
13
13
|
import CheckIcon from '@material-ui/icons/Check';
|
|
@@ -20,8 +20,10 @@ import { AssistantAvatar } from './surface/AssistantAvatar.esm.js';
|
|
|
20
20
|
import { SidePane } from './SidePane.esm.js';
|
|
21
21
|
import { FullHeightRegion } from './FullHeightRegion.esm.js';
|
|
22
22
|
import { createThreadListAdapter, patchThread } from './threadListAdapter.esm.js';
|
|
23
|
+
import { signalApiRef } from '@backstage/plugin-signals-react';
|
|
23
24
|
import { makeRuntimeHook } from './useAssistantRuntime.esm.js';
|
|
24
|
-
import {
|
|
25
|
+
import { StatusDot } from './StatusDot.esm.js';
|
|
26
|
+
import { useThreadStatus, toConvStatus } from './useThreadStatus.esm.js';
|
|
25
27
|
|
|
26
28
|
const SIDEPANE_COLLAPSED_KEY = "ai-chat-sidepane-collapsed";
|
|
27
29
|
function loadSidePaneCollapsed() {
|
|
@@ -214,14 +216,24 @@ const useStyles = makeStyles((theme) => ({
|
|
|
214
216
|
display: "flex",
|
|
215
217
|
flexDirection: "column"
|
|
216
218
|
},
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
// Bare-agent empty state (no conversation selected; new chats are "+"-only).
|
|
220
|
+
emptyState: {
|
|
221
|
+
display: "flex",
|
|
222
|
+
flexDirection: "column",
|
|
223
|
+
alignItems: "center",
|
|
224
|
+
justifyContent: "center",
|
|
225
|
+
gap: theme.spacing(1),
|
|
226
|
+
height: "100%",
|
|
227
|
+
textAlign: "center",
|
|
228
|
+
color: theme.palette.text.secondary,
|
|
229
|
+
padding: theme.spacing(3)
|
|
230
|
+
},
|
|
231
|
+
emptyTitle: {
|
|
232
|
+
fontWeight: 600,
|
|
233
|
+
color: theme.palette.text.primary
|
|
222
234
|
},
|
|
223
|
-
|
|
224
|
-
|
|
235
|
+
emptyButton: {
|
|
236
|
+
marginTop: theme.spacing(1)
|
|
225
237
|
}
|
|
226
238
|
}));
|
|
227
239
|
function splitModel(id, pool) {
|
|
@@ -259,12 +271,24 @@ function CollapsiblePage() {
|
|
|
259
271
|
}
|
|
260
272
|
);
|
|
261
273
|
}
|
|
262
|
-
const
|
|
263
|
-
return /* @__PURE__ */ jsx(
|
|
274
|
+
const initialAssistantId = assistants.find((a) => a.id === requestedAssistant)?.id ?? assistants[0].id;
|
|
275
|
+
return /* @__PURE__ */ jsx(
|
|
276
|
+
CollapsibleChat,
|
|
277
|
+
{
|
|
278
|
+
status: status.value,
|
|
279
|
+
initialAssistantId
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
function mostRecentThreadFor(assistantId, threadList) {
|
|
284
|
+
const updatedAt = (id) => threadList.threadItems[id]?.custom?.updatedAt ?? "";
|
|
285
|
+
return [...threadList.threadIds].filter(
|
|
286
|
+
(id) => threadList.threadItems[id]?.custom?.assistantId === assistantId
|
|
287
|
+
).sort((a, b) => updatedAt(a) < updatedAt(b) ? 1 : -1)[0];
|
|
264
288
|
}
|
|
265
289
|
function CollapsibleChat({
|
|
266
290
|
status,
|
|
267
|
-
|
|
291
|
+
initialAssistantId
|
|
268
292
|
}) {
|
|
269
293
|
const api = useApi(assistantsApiRef);
|
|
270
294
|
const baseUrl = useAsync(() => api.getBaseUrl(), [api]);
|
|
@@ -283,50 +307,71 @@ function CollapsibleChat({
|
|
|
283
307
|
ChatRuntime,
|
|
284
308
|
{
|
|
285
309
|
status,
|
|
286
|
-
assistant,
|
|
287
310
|
api,
|
|
288
|
-
baseUrl: baseUrl.value
|
|
311
|
+
baseUrl: baseUrl.value,
|
|
312
|
+
initialAssistantId
|
|
289
313
|
}
|
|
290
314
|
);
|
|
291
315
|
}
|
|
292
316
|
function ChatRuntime({
|
|
293
317
|
status,
|
|
294
|
-
assistant,
|
|
295
318
|
api,
|
|
296
|
-
baseUrl
|
|
319
|
+
baseUrl,
|
|
320
|
+
initialAssistantId
|
|
297
321
|
}) {
|
|
298
|
-
const
|
|
299
|
-
const modelIdRef = useRef(defaultModel);
|
|
322
|
+
const activeAssistantIdRef = useRef(initialAssistantId);
|
|
323
|
+
const modelIdRef = useRef(status.defaultModel);
|
|
300
324
|
const adapter = useMemo(
|
|
301
|
-
() => createThreadListAdapter(api,
|
|
302
|
-
[api
|
|
325
|
+
() => createThreadListAdapter(api, () => activeAssistantIdRef.current),
|
|
326
|
+
[api]
|
|
303
327
|
);
|
|
304
328
|
const runtimeHook = useMemo(
|
|
305
|
-
() => makeRuntimeHook({
|
|
306
|
-
|
|
329
|
+
() => makeRuntimeHook({
|
|
330
|
+
api,
|
|
331
|
+
baseUrl,
|
|
332
|
+
getActiveAssistantId: () => activeAssistantIdRef.current,
|
|
333
|
+
modelIdRef
|
|
334
|
+
}),
|
|
335
|
+
[api, baseUrl]
|
|
307
336
|
);
|
|
308
337
|
const runtime = useRemoteThreadListRuntime({ adapter, runtimeHook });
|
|
309
338
|
return /* @__PURE__ */ jsx(AssistantRuntimeProvider, { runtime, children: /* @__PURE__ */ jsx(
|
|
310
339
|
ChatChrome,
|
|
311
340
|
{
|
|
312
341
|
status,
|
|
313
|
-
assistant,
|
|
314
342
|
api,
|
|
315
343
|
modelIdRef,
|
|
316
|
-
|
|
344
|
+
activeAssistantIdRef,
|
|
345
|
+
initialAssistantId
|
|
317
346
|
}
|
|
318
347
|
) });
|
|
319
348
|
}
|
|
320
349
|
function ChatChrome({
|
|
321
350
|
status,
|
|
322
|
-
assistant,
|
|
323
351
|
api,
|
|
324
352
|
modelIdRef,
|
|
325
|
-
|
|
353
|
+
activeAssistantIdRef,
|
|
354
|
+
initialAssistantId
|
|
326
355
|
}) {
|
|
327
356
|
const classes = useStyles();
|
|
328
357
|
const runtime = useAssistantRuntime();
|
|
358
|
+
const signals = useApi(signalApiRef);
|
|
329
359
|
const [, setSearchParams] = useSearchParams();
|
|
360
|
+
const [activeAssistantId, setActiveAssistantIdState] = useState(
|
|
361
|
+
initialAssistantId
|
|
362
|
+
);
|
|
363
|
+
const setActiveAssistantId = useCallback(
|
|
364
|
+
(id) => {
|
|
365
|
+
activeAssistantIdRef.current = id;
|
|
366
|
+
setActiveAssistantIdState(id);
|
|
367
|
+
},
|
|
368
|
+
[activeAssistantIdRef]
|
|
369
|
+
);
|
|
370
|
+
const assistant = useMemo(
|
|
371
|
+
() => status.assistants.find((a) => a.id === activeAssistantId) ?? status.assistants[0],
|
|
372
|
+
[status.assistants, activeAssistantId]
|
|
373
|
+
);
|
|
374
|
+
const defaultModel = assistant.defaultModel ?? status.defaultModel;
|
|
330
375
|
const [threadList, setThreadList] = useState(
|
|
331
376
|
() => runtime.threads.getState()
|
|
332
377
|
);
|
|
@@ -340,44 +385,52 @@ function ChatChrome({
|
|
|
340
385
|
const activeItem = threadList.threadItems[activeId];
|
|
341
386
|
const activeRemoteId = activeItem?.remoteId;
|
|
342
387
|
const activeTitle = activeItem?.title ?? "";
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
activeRemoteId
|
|
346
|
-
);
|
|
388
|
+
const isBlankDraft = activeId === threadList.newThreadId && !activeRemoteId;
|
|
389
|
+
const { statusOf, agentStatus } = useThreadStatus(api, activeRemoteId);
|
|
347
390
|
const didSelectInitial = useRef(false);
|
|
348
391
|
useEffect(() => {
|
|
349
392
|
if (didSelectInitial.current || threadList.isLoading) return;
|
|
350
393
|
didSelectInitial.current = true;
|
|
351
394
|
if (activeId !== threadList.newThreadId) return;
|
|
352
|
-
const mostRecent =
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (mostRecent)
|
|
357
|
-
void runtime.threads.switchToThread(mostRecent);
|
|
358
|
-
}
|
|
395
|
+
const mostRecent = mostRecentThreadFor(
|
|
396
|
+
activeAssistantIdRef.current,
|
|
397
|
+
threadList
|
|
398
|
+
);
|
|
399
|
+
if (mostRecent) void runtime.threads.switchToThread(mostRecent);
|
|
359
400
|
}, [threadList.isLoading]);
|
|
360
401
|
const conversations = useMemo(
|
|
361
|
-
() => threadList.threadIds.
|
|
402
|
+
() => threadList.threadIds.filter((id) => {
|
|
403
|
+
if (id === activeId) return true;
|
|
404
|
+
if (id === threadList.newThreadId) return false;
|
|
405
|
+
const aid = threadList.threadItems[id]?.custom?.assistantId;
|
|
406
|
+
return aid === activeAssistantId;
|
|
407
|
+
}).map((id) => {
|
|
362
408
|
const item = threadList.threadItems[id];
|
|
363
409
|
const custom = item?.custom;
|
|
364
|
-
const st =
|
|
410
|
+
const st = statusOf(item?.remoteId);
|
|
365
411
|
return {
|
|
366
412
|
id,
|
|
367
413
|
remoteId: item?.remoteId,
|
|
368
414
|
title: item?.title ?? "New Chat",
|
|
369
415
|
pinned: custom?.pinned ?? false,
|
|
370
416
|
unread: st === "unread",
|
|
371
|
-
|
|
417
|
+
working: st === "working"
|
|
372
418
|
};
|
|
373
419
|
}),
|
|
374
|
-
[threadList,
|
|
420
|
+
[threadList, statusOf, activeAssistantId, activeId]
|
|
375
421
|
);
|
|
376
422
|
useEffect(() => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
423
|
+
let timer;
|
|
424
|
+
const sub = signals.subscribe("assistants:threads", (msg) => {
|
|
425
|
+
if (msg.type !== "updated") return;
|
|
426
|
+
if (timer) clearTimeout(timer);
|
|
427
|
+
timer = setTimeout(() => void runtime.threads.reload(), 150);
|
|
428
|
+
});
|
|
429
|
+
return () => {
|
|
430
|
+
if (timer) clearTimeout(timer);
|
|
431
|
+
sub.unsubscribe();
|
|
432
|
+
};
|
|
433
|
+
}, [signals, runtime]);
|
|
381
434
|
const allowedModels = useMemo(
|
|
382
435
|
() => assistant.models ?? status.models.map((m) => m.id),
|
|
383
436
|
[assistant.models, status.models]
|
|
@@ -405,9 +458,6 @@ function ChatChrome({
|
|
|
405
458
|
setModelId(next);
|
|
406
459
|
modelIdRef.current = next;
|
|
407
460
|
}, [activeId]);
|
|
408
|
-
useEffect(() => {
|
|
409
|
-
if (activeRemoteId) markRead(activeRemoteId);
|
|
410
|
-
}, [activeRemoteId]);
|
|
411
461
|
const [sidePaneCollapsed, setSidePaneCollapsed] = useState(loadSidePaneCollapsed);
|
|
412
462
|
useEffect(() => {
|
|
413
463
|
try {
|
|
@@ -417,9 +467,14 @@ function ChatChrome({
|
|
|
417
467
|
}, [sidePaneCollapsed]);
|
|
418
468
|
const handleSelectAssistant = useCallback(
|
|
419
469
|
(id) => {
|
|
420
|
-
if (id
|
|
470
|
+
if (id === activeAssistantId) return;
|
|
471
|
+
setActiveAssistantId(id);
|
|
472
|
+
setSearchParams({ assistant: id });
|
|
473
|
+
const mostRecent = mostRecentThreadFor(id, threadList);
|
|
474
|
+
if (mostRecent) void runtime.threads.switchToThread(mostRecent);
|
|
475
|
+
else void runtime.threads.switchToNewThread();
|
|
421
476
|
},
|
|
422
|
-
[
|
|
477
|
+
[activeAssistantId, setActiveAssistantId, setSearchParams, threadList, runtime]
|
|
423
478
|
);
|
|
424
479
|
const handleNew = useCallback(() => {
|
|
425
480
|
void (async () => {
|
|
@@ -448,9 +503,20 @@ function ChatChrome({
|
|
|
448
503
|
);
|
|
449
504
|
const handleDelete = useCallback(
|
|
450
505
|
(id) => {
|
|
451
|
-
void
|
|
506
|
+
void (async () => {
|
|
507
|
+
const wasActive = id === activeId;
|
|
508
|
+
await runtime.threads.getItemById(id).delete();
|
|
509
|
+
await runtime.threads.reload();
|
|
510
|
+
if (!wasActive) return;
|
|
511
|
+
const remaining = mostRecentThreadFor(
|
|
512
|
+
activeAssistantIdRef.current,
|
|
513
|
+
runtime.threads.getState()
|
|
514
|
+
);
|
|
515
|
+
if (remaining) await runtime.threads.switchToThread(remaining);
|
|
516
|
+
else await runtime.threads.switchToNewThread();
|
|
517
|
+
})();
|
|
452
518
|
},
|
|
453
|
-
[runtime]
|
|
519
|
+
[runtime, activeId, activeAssistantIdRef]
|
|
454
520
|
);
|
|
455
521
|
const handlePin = useCallback(
|
|
456
522
|
async (id) => {
|
|
@@ -537,17 +603,7 @@ function ChatChrome({
|
|
|
537
603
|
className: `${classes.sidePaneRailButton} ${a.id === assistant.id ? classes.sidePaneRailButtonActive : ""}`,
|
|
538
604
|
"aria-label": a.title,
|
|
539
605
|
onClick: () => handleSelectAssistant(a.id),
|
|
540
|
-
children: /* @__PURE__ */ jsx(
|
|
541
|
-
Badge,
|
|
542
|
-
{
|
|
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 })
|
|
549
|
-
}
|
|
550
|
-
)
|
|
606
|
+
children: /* @__PURE__ */ jsx(StatusDot, { status: agentStatus(a.id), children: /* @__PURE__ */ jsx(AssistantAvatar, { color: a.color, size: 24 }) })
|
|
551
607
|
}
|
|
552
608
|
) }, a.id)) }),
|
|
553
609
|
/* @__PURE__ */ jsx("div", { className: classes.sidePaneRailDivider }),
|
|
@@ -579,13 +635,12 @@ function ChatChrome({
|
|
|
579
635
|
"aria-label": conversation.title,
|
|
580
636
|
onClick: () => handleSelect(conversation.id),
|
|
581
637
|
children: /* @__PURE__ */ jsx(
|
|
582
|
-
|
|
638
|
+
StatusDot,
|
|
583
639
|
{
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
classes: conversation.generating ? { dot: classes.pulseDot } : void 0,
|
|
640
|
+
status: toConvStatus(
|
|
641
|
+
conversation.working,
|
|
642
|
+
conversation.unread
|
|
643
|
+
),
|
|
589
644
|
children: /* @__PURE__ */ jsx(ChatBubbleOutlineIcon, { fontSize: "small" })
|
|
590
645
|
}
|
|
591
646
|
)
|
|
@@ -635,7 +690,25 @@ function ChatChrome({
|
|
|
635
690
|
] }),
|
|
636
691
|
modelPicker
|
|
637
692
|
] }),
|
|
638
|
-
/* @__PURE__ */ jsx("div", { className: classes.threadBody, children: /* @__PURE__ */
|
|
693
|
+
/* @__PURE__ */ jsx("div", { className: classes.threadBody, children: isBlankDraft ? /* @__PURE__ */ jsxs("div", { className: classes.emptyState, children: [
|
|
694
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body1", className: classes.emptyTitle, children: "No conversation yet" }),
|
|
695
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "textSecondary", children: [
|
|
696
|
+
"Start a new chat with ",
|
|
697
|
+
assistant.title,
|
|
698
|
+
"."
|
|
699
|
+
] }),
|
|
700
|
+
/* @__PURE__ */ jsx(
|
|
701
|
+
Button,
|
|
702
|
+
{
|
|
703
|
+
variant: "outlined",
|
|
704
|
+
size: "small",
|
|
705
|
+
startIcon: /* @__PURE__ */ jsx(AddIcon, {}),
|
|
706
|
+
onClick: handleNew,
|
|
707
|
+
className: classes.emptyButton,
|
|
708
|
+
children: "New chat"
|
|
709
|
+
}
|
|
710
|
+
)
|
|
711
|
+
] }) : /* @__PURE__ */ jsx(
|
|
639
712
|
ConversationSurface,
|
|
640
713
|
{
|
|
641
714
|
composerPlaceholder: assistant.ui?.composer?.placeholder,
|