@micha.bigler/ui-core-micha 4.1.0 → 4.2.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 +17 -0
- package/dist/chat/ChatComposer.js +2 -2
- package/dist/chat/ChatWorkspace.js +114 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.2.0 - UCM-CHAT-4
|
|
4
|
+
|
|
5
|
+
`ChatWorkspace` takes two optional props so a host can control which chat is shown. `activeChatId` shows that chat
|
|
6
|
+
(`null` shows none, `undefined`, the default, keeps the selection internal); an id that is not in the loaded lists
|
|
7
|
+
triggers one reload of the lists, and an id that still cannot be found leaves the normal no-chat state. It is applied
|
|
8
|
+
only when the prop value changes, so a host that echoes `onActiveChatChange` back causes no extra requests.
|
|
9
|
+
`onActiveChatChange(chatId)` is called when the id of the shown chat changes (selection, creation, deletion as `null`,
|
|
10
|
+
a change driven by `activeChatId`), not for the initial state with no chat and not when the shown chat is only
|
|
11
|
+
archived, renamed or updated.
|
|
12
|
+
`composerPrefill` (`{ id, text }`) puts the text into the composer of the shown chat and focuses it; each new `id` is
|
|
13
|
+
applied (the same text twice, if asked twice), and a request with no chat shown, for an archived chat, or while
|
|
14
|
+
`activeChatId` is `null` is ignored. A prefill sent together with an `activeChatId` switch waits for the requested chat.
|
|
15
|
+
`renderAnswerExtra` additionally receives `send(text)`, which sends the text as the next message exactly like a typed
|
|
16
|
+
send, and `canSend` (false while a turn runs, for an archived chat and while disconnected). `ChatComposer` takes an
|
|
17
|
+
optional `inputRef`. Switching chats now clears the previous chat's content before paint.
|
|
18
|
+
Without the new props nothing changes.
|
|
19
|
+
|
|
3
20
|
## 4.1.0 - UCM-CHAT-3
|
|
4
21
|
|
|
5
22
|
Extension points on the `/chat` surface; with none of the new props `ChatWorkspace` renders and behaves as in 4.0.x.
|
|
@@ -6,13 +6,13 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
* Message input. Send is disabled while a turn runs, for an archived chat and while the connection is down;
|
|
7
7
|
* Stop replaces it during a turn. Enter sends, Shift+Enter inserts a line break.
|
|
8
8
|
*/
|
|
9
|
-
export function ChatComposer({ value = '', onChange, onSend, onStop, running = false, archived = false, connected = true, sx }) {
|
|
9
|
+
export function ChatComposer({ value = '', onChange, onSend, onStop, running = false, archived = false, connected = true, inputRef, sx }) {
|
|
10
10
|
const { t } = useTranslation();
|
|
11
11
|
const blocked = archived || !connected;
|
|
12
12
|
const canSend = !running && !blocked && value.trim().length > 0;
|
|
13
13
|
const submit = () => { if (canSend)
|
|
14
14
|
onSend === null || onSend === void 0 ? void 0 : onSend(); };
|
|
15
|
-
return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", gap: 1, sx: Object.assign({ p: { xs: 1, md: 2 }, borderTop: 1, borderColor: 'divider' }, sx), children: [_jsx(TextField, { multiline: true, maxRows: 6, fullWidth: true, size: "small", value: value, disabled: blocked, placeholder: t('Chat.message'), slotProps: { htmlInput: { 'aria-label': t('Chat.message') } }, onChange: (event) => onChange === null || onChange === void 0 ? void 0 : onChange(event.target.value), onKeyDown: (event) => {
|
|
15
|
+
return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", gap: 1, sx: Object.assign({ p: { xs: 1, md: 2 }, borderTop: 1, borderColor: 'divider' }, sx), children: [_jsx(TextField, { multiline: true, maxRows: 6, fullWidth: true, size: "small", value: value, inputRef: inputRef, disabled: blocked, placeholder: t('Chat.message'), slotProps: { htmlInput: { 'aria-label': t('Chat.message') } }, onChange: (event) => onChange === null || onChange === void 0 ? void 0 : onChange(event.target.value), onKeyDown: (event) => {
|
|
16
16
|
var _a;
|
|
17
17
|
if (event.key === 'Enter' && !event.shiftKey && !((_a = event.nativeEvent) === null || _a === void 0 ? void 0 : _a.isComposing)) {
|
|
18
18
|
event.preventDefault();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { Alert, Box, Button, Chip, Drawer, Stack, Typography, useMediaQuery, useTheme } from '@mui/material';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { createChatApi } from './api';
|
|
@@ -42,7 +42,7 @@ const appendDelta = (turns, text) => {
|
|
|
42
42
|
* Props: `apiBase` and `wsUrl` (where the app mounted the chat routes), `api` (partial overrides of the REST
|
|
43
43
|
* calls; every function not given keeps its default), `scope` (passed when creating a chat), `sx`.
|
|
44
44
|
*/
|
|
45
|
-
export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, unavailableReasonText, sidePanel, sidePanelLabel, renderAnswerExtra, renderComposerExtra, onAppFrame, layout = 'viewport', compactBelow = 720 }) {
|
|
45
|
+
export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, unavailableReasonText, sidePanel, sidePanelLabel, renderAnswerExtra, renderComposerExtra, onAppFrame, activeChatId, onActiveChatChange, composerPrefill, layout = 'viewport', compactBelow = 720 }) {
|
|
46
46
|
const { t } = useTranslation();
|
|
47
47
|
const theme = useTheme();
|
|
48
48
|
const viewportNarrow = useMediaQuery(theme.breakpoints.down('md'));
|
|
@@ -70,17 +70,29 @@ export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, un
|
|
|
70
70
|
const [archived, setArchived] = useState([]);
|
|
71
71
|
const [nextChats, setNextChats] = useState(null);
|
|
72
72
|
const [chat, setChat] = useState(null);
|
|
73
|
+
const [listsLoaded, setListsLoaded] = useState(false);
|
|
73
74
|
const [turns, setTurns] = useState([]);
|
|
74
75
|
const [olderCursor, setOlderCursor] = useState(null);
|
|
75
76
|
const [olderBusy, setOlderBusy] = useState(false);
|
|
76
77
|
const [tool, setTool] = useState(null);
|
|
77
78
|
const [draft, setDraft] = useState('');
|
|
79
|
+
const composerInputRef = useRef(null);
|
|
80
|
+
const lastPrefillId = useRef(undefined);
|
|
81
|
+
const [focusRequest, setFocusRequest] = useState(0);
|
|
82
|
+
const focusPending = useRef(false);
|
|
78
83
|
const [problem, setProblem] = useState(null);
|
|
79
84
|
const [unavailableReason, setUnavailableReason] = useState(null);
|
|
80
85
|
const [trimmedCount, setTrimmedCount] = useState(null);
|
|
81
86
|
const [sidePanelOpen, setSidePanelOpen] = useState(false);
|
|
82
87
|
const turnsRef = useRef(turns);
|
|
83
88
|
useEffect(() => { turnsRef.current = turns; }, [turns]);
|
|
89
|
+
const activeChangeCallback = useRef(onActiveChatChange);
|
|
90
|
+
activeChangeCallback.current = onActiveChatChange;
|
|
91
|
+
const lastReportedId = useRef(null);
|
|
92
|
+
const latestWantedId = useRef(activeChatId);
|
|
93
|
+
latestWantedId.current = activeChatId;
|
|
94
|
+
const appliedId = useRef(undefined);
|
|
95
|
+
const unloadableIds = useRef(new Set()); // ids the host asked for that no list contains
|
|
84
96
|
const [listOpen, setListOpen] = useState(false);
|
|
85
97
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
86
98
|
const [openedOnce, setOpenedOnce] = useState(false); // distinguishes a reconnect from the first connect
|
|
@@ -90,7 +102,7 @@ export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, un
|
|
|
90
102
|
const activeEntry = catalogue.entries.find((entry) => entry.id === (selection === null || selection === void 0 ? void 0 : selection.entry));
|
|
91
103
|
// Bumped on every scope change so that a list response of the previous scope is dropped.
|
|
92
104
|
const scopeVersion = useRef(0);
|
|
93
|
-
useEffect(() => { scopeVersion.current += 1; setChat(null); }, [scope]);
|
|
105
|
+
useEffect(() => { scopeVersion.current += 1; setChat(null); setListsLoaded(false); appliedId.current = undefined; unloadableIds.current.clear(); }, [scope]);
|
|
94
106
|
const loadLists = useCallback(async () => {
|
|
95
107
|
const version = scopeVersion.current;
|
|
96
108
|
// Without a scope the api is called exactly as before the scope existed.
|
|
@@ -98,10 +110,11 @@ export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, un
|
|
|
98
110
|
? await Promise.all([api.listChats(), api.listChats({ archived: true })])
|
|
99
111
|
: await Promise.all([api.listChats({ scope }), api.listChats({ scope, archived: true })]);
|
|
100
112
|
if (version !== scopeVersion.current)
|
|
101
|
-
return;
|
|
113
|
+
return undefined;
|
|
102
114
|
setChats(active.results || []);
|
|
103
115
|
setNextChats(active.next || null);
|
|
104
116
|
setArchived(old.results || []);
|
|
117
|
+
return [...(active.results || []), ...(old.results || [])];
|
|
105
118
|
}, [api, scope]);
|
|
106
119
|
useEffect(() => {
|
|
107
120
|
let live = true;
|
|
@@ -119,6 +132,7 @@ export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, un
|
|
|
119
132
|
if (live) {
|
|
120
133
|
setStatus(state.status);
|
|
121
134
|
setCatalogue(entries);
|
|
135
|
+
setListsLoaded(true);
|
|
122
136
|
}
|
|
123
137
|
})
|
|
124
138
|
.catch(() => { if (live)
|
|
@@ -126,6 +140,52 @@ export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, un
|
|
|
126
140
|
return () => { live = false; };
|
|
127
141
|
}, [api, loadLists, scope]);
|
|
128
142
|
useEffect(() => {
|
|
143
|
+
var _a;
|
|
144
|
+
var _b;
|
|
145
|
+
const id = (_b = chat === null || chat === void 0 ? void 0 : chat.id) !== null && _b !== void 0 ? _b : null;
|
|
146
|
+
if (id === lastReportedId.current)
|
|
147
|
+
return;
|
|
148
|
+
lastReportedId.current = id;
|
|
149
|
+
(_a = activeChangeCallback.current) === null || _a === void 0 ? void 0 : _a.call(activeChangeCallback, id);
|
|
150
|
+
}, [chat === null || chat === void 0 ? void 0 : chat.id]);
|
|
151
|
+
// A layout effect: a host-driven switch to a known chat is applied before the browser paints.
|
|
152
|
+
useLayoutEffect(() => {
|
|
153
|
+
if (activeChatId === undefined || status !== 'available' || !listsLoaded)
|
|
154
|
+
return undefined;
|
|
155
|
+
const wantedKey = activeChatId === null ? null : String(activeChatId);
|
|
156
|
+
if (wantedKey === null) {
|
|
157
|
+
appliedId.current = null;
|
|
158
|
+
setChat(null);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (String(chat === null || chat === void 0 ? void 0 : chat.id) === wantedKey)
|
|
162
|
+
return;
|
|
163
|
+
const existing = [...chats, ...archived].find((item) => String(item.id) === wantedKey);
|
|
164
|
+
if (existing) {
|
|
165
|
+
setChat(existing);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (appliedId.current === wantedKey)
|
|
169
|
+
return;
|
|
170
|
+
appliedId.current = wantedKey;
|
|
171
|
+
let live = true;
|
|
172
|
+
loadLists().then((loaded) => {
|
|
173
|
+
if (!live || loaded === undefined || latestWantedId.current === undefined || String(latestWantedId.current) !== wantedKey)
|
|
174
|
+
return;
|
|
175
|
+
const found = loaded.find((item) => String(item.id) === wantedKey);
|
|
176
|
+
if (!found)
|
|
177
|
+
unloadableIds.current.add(wantedKey);
|
|
178
|
+
setChat(found || null);
|
|
179
|
+
}).catch(() => {
|
|
180
|
+
if (live && latestWantedId.current !== undefined && String(latestWantedId.current) === wantedKey) {
|
|
181
|
+
unloadableIds.current.add(wantedKey);
|
|
182
|
+
setChat(null);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
return () => { live = false; };
|
|
186
|
+
}, [activeChatId, status, listsLoaded]);
|
|
187
|
+
// Clear the previous chat's content before the browser paints, so a host-driven switch never flashes it under the new title.
|
|
188
|
+
useLayoutEffect(() => {
|
|
129
189
|
setTurns([]);
|
|
130
190
|
setOlderCursor(null);
|
|
131
191
|
setTool(null);
|
|
@@ -134,6 +194,8 @@ export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, un
|
|
|
134
194
|
setSidePanelOpen(false);
|
|
135
195
|
setDraft('');
|
|
136
196
|
setOpenedOnce(false);
|
|
197
|
+
}, [chat === null || chat === void 0 ? void 0 : chat.id]);
|
|
198
|
+
useEffect(() => {
|
|
137
199
|
if (!(chat === null || chat === void 0 ? void 0 : chat.id))
|
|
138
200
|
return undefined;
|
|
139
201
|
let live = true;
|
|
@@ -181,6 +243,37 @@ export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, un
|
|
|
181
243
|
const onClose = useCallback(({ reason, retrying }) => { if (!retrying)
|
|
182
244
|
setProblem(reason); }, []);
|
|
183
245
|
const socket = useChatSocket(chat === null || chat === void 0 ? void 0 : chat.id, { wsUrl, onFrame, onOpen, onClose });
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
if (composerPrefill == null || composerPrefill.id === lastPrefillId.current)
|
|
248
|
+
return;
|
|
249
|
+
// `null` means the host wants no chat shown: nothing to fill, and the chat being left must not receive the text.
|
|
250
|
+
if (activeChatId === null) {
|
|
251
|
+
lastPrefillId.current = composerPrefill.id;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// The host switches chats and prefills in one go: wait for the requested chat instead of filling the one being left.
|
|
255
|
+
const wanted = activeChatId == null ? null : String(activeChatId);
|
|
256
|
+
if (wanted !== null && String(chat === null || chat === void 0 ? void 0 : chat.id) !== wanted && !unloadableIds.current.has(wanted))
|
|
257
|
+
return;
|
|
258
|
+
lastPrefillId.current = composerPrefill.id;
|
|
259
|
+
if (!chat || isArchived)
|
|
260
|
+
return;
|
|
261
|
+
setDraft(composerPrefill.text);
|
|
262
|
+
focusPending.current = true;
|
|
263
|
+
setFocusRequest((count) => count + 1);
|
|
264
|
+
}, [composerPrefill, chat, isArchived, activeChatId]);
|
|
265
|
+
// Runs after the render that carries the prefilled draft; a counter, so the same text requested twice focuses twice.
|
|
266
|
+
// A disabled input (connection down) cannot take focus, so the request stays pending until it is enabled again.
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
var _a;
|
|
269
|
+
const input = composerInputRef.current;
|
|
270
|
+
if (!focusPending.current || !input || input.disabled)
|
|
271
|
+
return;
|
|
272
|
+
focusPending.current = false;
|
|
273
|
+
input.focus();
|
|
274
|
+
const end = input.value.length;
|
|
275
|
+
(_a = input.setSelectionRange) === null || _a === void 0 ? void 0 : _a.call(input, end, end);
|
|
276
|
+
}, [focusRequest, socket.connected]);
|
|
184
277
|
const guarded = (action) => async (...args) => {
|
|
185
278
|
try {
|
|
186
279
|
return await action(...args);
|
|
@@ -235,6 +328,22 @@ export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, un
|
|
|
235
328
|
}]);
|
|
236
329
|
return true;
|
|
237
330
|
};
|
|
331
|
+
const canSendFromSlot = Boolean(chat && !isArchived && socket.connected && !running);
|
|
332
|
+
// A slot may keep an old `send` around: read the latest availability, and close it right away after a send.
|
|
333
|
+
const canSendRef = useRef(false);
|
|
334
|
+
canSendRef.current = canSendFromSlot;
|
|
335
|
+
const sendFromSlot = (text) => {
|
|
336
|
+
const message = typeof text === 'string' ? text : '';
|
|
337
|
+
if (!canSendRef.current || !message.trim())
|
|
338
|
+
return false;
|
|
339
|
+
const started = startTurn(message);
|
|
340
|
+
if (started)
|
|
341
|
+
canSendRef.current = false;
|
|
342
|
+
return started;
|
|
343
|
+
};
|
|
344
|
+
const answerExtra = renderAnswerExtra
|
|
345
|
+
? (args) => renderAnswerExtra(Object.assign(Object.assign({}, args), { send: sendFromSlot, canSend: canSendFromSlot }))
|
|
346
|
+
: undefined;
|
|
238
347
|
const send = () => { if (startTurn(draft))
|
|
239
348
|
setDraft(''); };
|
|
240
349
|
const retry = (turn) => {
|
|
@@ -273,6 +382,6 @@ export function ChatWorkspace({ apiBase, wsUrl, api: apiOverrides, scope, sx, un
|
|
|
273
382
|
if (unavailable) {
|
|
274
383
|
return (_jsx(Stack, { alignItems: "center", justifyContent: "center", spacing: 1, sx: Object.assign({ minHeight: 320, p: 3, textAlign: 'center' }, sx), children: _jsx(Typography, { color: "text.secondary", children: unavailableText || t('Chat.notAvailableHere') }) }));
|
|
275
384
|
}
|
|
276
|
-
return (_jsxs(Box, { ref: rootRef, sx: Object.assign({ display: 'grid', gridTemplateColumns: narrow ? 'minmax(0, 1fr)' : `minmax(240px, 320px) minmax(0, 1fr)${chat && sidePanel ? ' minmax(240px, 320px)' : ''}`, height: '100%', minHeight: 480, border: 1, borderColor: 'divider', borderRadius: 1, overflow: 'hidden', bgcolor: 'background.paper' }, sx), children: [narrow ? null : _jsx(Box, { sx: { borderRight: 1, borderColor: 'divider', minHeight: 0 }, children: list }), _jsxs(Stack, { sx: { minWidth: 0, minHeight: 0 }, children: [_jsxs(Stack, { direction: "row", alignItems: "center", gap: 1, sx: { px: 1.5, py: 0.5, borderBottom: 1, borderColor: 'divider', minHeight: 56 }, children: [narrow ? _jsx(Button, { color: "inherit", onClick: () => setListOpen(true), children: t('Chat.chats') }) : null, _jsx(Typography, { noWrap: true, sx: { flex: 1, fontWeight: 600 }, children: chat ? (chat.title || t('Chat.untitled')) : '' }), socket.status === 'connecting' && openedOnce ? _jsx(Chip, { size: "small", color: "warning", variant: "outlined", label: t('Chat.reconnecting') }) : null, isArchived ? _jsx(Chip, { size: "small", label: t('Chat.archived') }) : null, isArchived ? _jsx(Button, { size: "small", onClick: () => updateChat(chat, { archived: false }), children: t('Chat.restore') }) : null, chat ? (_jsx(ModelPicker, { entries: catalogue.entries, selection: selection, disabled: isArchived || running, open: pickerOpen, onOpenChange: setPickerOpen, onChange: (next) => updateChat(chat, { selection: next }), sheet: narrow })) : null, chat && sidePanel && narrow ? _jsx(Button, { color: "inherit", onClick: () => setSidePanelOpen(true), children: sidePanelLabel || t('Chat.sidePanel') }) : null] }), chat ? (_jsxs(_Fragment, { children: [_jsx(ChatThread, { turns: turns, chat: chat, renderAnswerExtra:
|
|
385
|
+
return (_jsxs(Box, { ref: rootRef, sx: Object.assign({ display: 'grid', gridTemplateColumns: narrow ? 'minmax(0, 1fr)' : `minmax(240px, 320px) minmax(0, 1fr)${chat && sidePanel ? ' minmax(240px, 320px)' : ''}`, height: '100%', minHeight: 480, border: 1, borderColor: 'divider', borderRadius: 1, overflow: 'hidden', bgcolor: 'background.paper' }, sx), children: [narrow ? null : _jsx(Box, { sx: { borderRight: 1, borderColor: 'divider', minHeight: 0 }, children: list }), _jsxs(Stack, { sx: { minWidth: 0, minHeight: 0 }, children: [_jsxs(Stack, { direction: "row", alignItems: "center", gap: 1, sx: { px: 1.5, py: 0.5, borderBottom: 1, borderColor: 'divider', minHeight: 56 }, children: [narrow ? _jsx(Button, { color: "inherit", onClick: () => setListOpen(true), children: t('Chat.chats') }) : null, _jsx(Typography, { noWrap: true, sx: { flex: 1, fontWeight: 600 }, children: chat ? (chat.title || t('Chat.untitled')) : '' }), socket.status === 'connecting' && openedOnce ? _jsx(Chip, { size: "small", color: "warning", variant: "outlined", label: t('Chat.reconnecting') }) : null, isArchived ? _jsx(Chip, { size: "small", label: t('Chat.archived') }) : null, isArchived ? _jsx(Button, { size: "small", onClick: () => updateChat(chat, { archived: false }), children: t('Chat.restore') }) : null, chat ? (_jsx(ModelPicker, { entries: catalogue.entries, selection: selection, disabled: isArchived || running, open: pickerOpen, onOpenChange: setPickerOpen, onChange: (next) => updateChat(chat, { selection: next }), sheet: narrow })) : null, chat && sidePanel && narrow ? _jsx(Button, { color: "inherit", onClick: () => setSidePanelOpen(true), children: sidePanelLabel || t('Chat.sidePanel') }) : null] }), chat ? (_jsxs(_Fragment, { children: [_jsx(ChatThread, { turns: turns, chat: chat, renderAnswerExtra: answerExtra, trimmedCount: trimmedCount, tool: tool, notice: noticeFor(), hasOlder: Boolean(olderCursor), olderBusy: olderBusy, onLoadOlder: loadOlder, onRetry: retry, readOnly: isArchived }), isArchived ? _jsx(Typography, { variant: "caption", color: "text.secondary", sx: { px: 2, pb: 1 }, children: t('Chat.archivedReadOnly') }) : null, renderComposerExtra ? _jsx(Box, { children: renderComposerExtra({ chat }) }, chat.id) : null, _jsx(ChatComposer, { value: draft, onChange: setDraft, onSend: send, onStop: () => socket.send({ type: 'stop' }), running: running, archived: isArchived, connected: socket.connected, inputRef: composerInputRef })] })) : (_jsxs(Stack, { alignItems: "center", justifyContent: "center", spacing: 2, sx: { flex: 1, p: 3 }, children: [_jsx(Typography, { color: "text.secondary", align: "center", children: t('Chat.selectChat') }), _jsx(Button, { variant: "contained", onClick: createChat, children: t('Chat.newChat') })] }))] }), chat && sidePanel && !narrow ? _jsx(Box, { sx: { borderLeft: 1, borderColor: 'divider', minHeight: 0, overflow: 'auto' }, children: sidePanel(chat) }, chat.id) : null, narrow && chat && sidePanel ? _jsx(Drawer, { anchor: "right", open: sidePanelOpen, onClose: () => setSidePanelOpen(false), slotProps: { paper: { 'aria-label': sidePanelLabel || t('Chat.sidePanel') } }, children: _jsx(Box, { children: sidePanel(chat) }, chat.id) }) : null, narrow ? _jsx(Drawer, { open: listOpen, onClose: () => setListOpen(false), slotProps: { paper: { sx: { width: 'min(320px, 86vw)' } } }, children: list }) : null] }));
|
|
277
386
|
}
|
|
278
387
|
export default ChatWorkspace;
|