@micha.bigler/ui-core-micha 3.13.2 → 3.14.4
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/.github/workflows/ci.yml +35 -0
- package/.github/workflows/publish.yml +4 -4
- package/CHANGELOG.md +35 -0
- package/WORK_ORDERS.md +7 -1
- package/dist/auth/UserMenu.js +1 -1
- package/dist/auth/apiClient.js +7 -6
- package/dist/chat/ChatComposer.js +23 -0
- package/dist/chat/ChatList.js +30 -0
- package/dist/chat/ChatThread.js +70 -0
- package/dist/chat/ChatWorkspace.js +218 -0
- package/dist/chat/MarkdownReply.js +21 -0
- package/dist/chat/ModelPicker.js +35 -0
- package/dist/chat/api.js +17 -0
- package/dist/chat/useChatSocket.js +68 -0
- package/dist/chat/wire.js +7 -0
- package/dist/components/LoginForm.js +1 -1
- package/dist/components/SecurityComponent.js +1 -1
- package/dist/components/UserListComponent.js +2 -8
- package/dist/components/charts/ChartFrame.js +1 -1
- package/dist/components/charts/ScatterChart.js +2 -1
- package/dist/i18n/chatTranslations.js +55 -0
- package/dist/i18n/uiCoreTranslations.js +2 -1
- package/dist/index.js +10 -0
- package/dist/layout/SectionNav.js +1 -1
- package/dist/messaging/AttachmentList.js +2 -2
- package/dist/messaging/Composer.js +2 -1
- package/dist/messaging/ConversationLaunchers.js +1 -4
- package/dist/messaging/ConversationList.js +1 -1
- package/dist/messaging/DirectMessageLauncher.js +1 -1
- package/dist/messaging/MessagingProvider.js +0 -0
- package/dist/messaging/PollCard.js +7 -5
- package/dist/messaging/ReadTicks.js +2 -2
- package/dist/messaging/Thread.js +5 -3
- package/dist/notifications/NotificationBell.js +4 -7
- package/dist/notifications/NotificationSettings.js +1 -1
- package/dist/onboarding/OnboardingProvider.js +3 -6
- package/dist/pages/LoginPage.js +1 -1
- package/dist/pages/PasswordChangePage.js +1 -1
- package/dist/pages/PasswordInvitePage.js +1 -1
- package/dist/pages/PasswordResetRequestPage.js +1 -1
- package/dist/pages/SignUpPage.js +1 -1
- package/dist/pages/SignupConfirmPage.js +1 -1
- package/dist/theme/createAppTheme.js +5 -4
- package/dist/theme/kitSxRegistry.js +10 -0
- package/dist/theme/themeCompleteness.js +3 -12
- package/dist/utils/auth-errors.js +4 -3
- package/package.json +13 -11
- package/src/chat/ChatComposer.jsx +36 -0
- package/src/chat/ChatList.jsx +87 -0
- package/src/chat/ChatThread.jsx +128 -0
- package/src/chat/ChatWorkspace.jsx +244 -0
- package/src/chat/MarkdownReply.jsx +22 -0
- package/src/chat/ModelPicker.jsx +85 -0
- package/src/chat/api.js +18 -0
- package/src/chat/useChatSocket.js +32 -0
- package/src/chat/wire.js +7 -0
- package/src/i18n/chatTranslations.ts +55 -0
- package/src/i18n/uiCoreTranslations.js +2 -0
- package/src/index.js +11 -0
- package/src/theme/kitSxRegistry.js +10 -0
- package/tests/chatApi.test.js +48 -0
- package/tests/chatDriverNames.test.jsx +71 -0
- package/tests/chatExports.test.js +3 -0
- package/tests/chatList.test.jsx +84 -0
- package/tests/chatPicker.test.jsx +82 -0
- package/tests/chatSocket.test.jsx +26 -0
- package/tests/chatThread.test.jsx +116 -0
- package/tests/chatTranslations.test.js +47 -0
- package/tests/chatWorkspace.test.jsx +234 -0
- package/tests/i18nAggregate.test.js +2 -0
- package/tsconfig.build.json +1 -1
- package/work-orders/UCM-AUTH-13.md +2 -0
- package/work-orders/UCM-CHAT-1.md +190 -0
- package/work-orders/UCM-CHAT-2.md +75 -0
- package/work-orders/UCM-CI-1.md +65 -0
- package/work-orders/UCM-DEP-1.md +63 -0
- package/work-orders/assets/UCM-CHAT-1-ai-chat.html +447 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Box, Button, Chip, Drawer, List, ListItemButton, ListItemText, Popover, Stack, ToggleButton, ToggleButtonGroup, Typography, useMediaQuery, useTheme } from '@mui/material';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
export const EFFORT_LEVELS = ['low', 'medium', 'high', 'max'];
|
|
6
|
+
export const driverName = (t, id) => t(`Chat.driver.${id}`, { defaultValue: id });
|
|
7
|
+
const groupByDriver = (entries) => entries.reduce((groups, entry) => {
|
|
8
|
+
(groups[entry.driver] = groups[entry.driver] || []).push(entry);
|
|
9
|
+
return groups;
|
|
10
|
+
}, {});
|
|
11
|
+
/**
|
|
12
|
+
* Model and effort of one chat. A popover at wide widths, a bottom sheet at narrow widths. `onChange` receives
|
|
13
|
+
* `{ entry, effort }`; the caller persists it (the change applies from the next turn). Can be controlled with `open`.
|
|
14
|
+
*/
|
|
15
|
+
export function ModelPicker({ entries = [], selection, onChange, disabled = false, open, onOpenChange }) {
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
const theme = useTheme();
|
|
18
|
+
const narrow = useMediaQuery(theme.breakpoints.down('md'));
|
|
19
|
+
const anchor = useRef(null);
|
|
20
|
+
const [innerOpen, setInnerOpen] = useState(false);
|
|
21
|
+
const isOpen = open !== null && open !== void 0 ? open : innerOpen;
|
|
22
|
+
const setOpen = (value) => { setInnerOpen(value); onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(value); };
|
|
23
|
+
const groups = useMemo(() => groupByDriver(entries), [entries]);
|
|
24
|
+
const active = entries.find((entry) => entry.id === (selection === null || selection === void 0 ? void 0 : selection.entry));
|
|
25
|
+
const pickEntry = (entry) => {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
const effort = ((_a = entry.efforts) === null || _a === void 0 ? void 0 : _a.includes(selection === null || selection === void 0 ? void 0 : selection.effort)) ? selection.effort : (_b = entry.efforts) === null || _b === void 0 ? void 0 : _b[0];
|
|
28
|
+
onChange === null || onChange === void 0 ? void 0 : onChange({ entry: entry.id, effort });
|
|
29
|
+
};
|
|
30
|
+
const content = (_jsx(Box, { sx: { p: 2, minWidth: narrow ? 0 : 320, maxWidth: '100%' }, children: _jsxs(Stack, { spacing: 1.5, children: [Object.entries(groups).map(([driver, items]) => (_jsxs(Box, { role: "group", "aria-label": driverName(t, driver), children: [_jsx(Typography, { variant: "overline", color: "text.secondary", children: driverName(t, driver) }), _jsx(List, { dense: true, disablePadding: true, children: items.map((entry) => (_jsxs(ListItemButton, { selected: entry.id === (selection === null || selection === void 0 ? void 0 : selection.entry), onClick: () => pickEntry(entry), sx: { minHeight: 40, borderRadius: 1 }, children: [_jsx(ListItemText, { primary: entry.label || entry.model }), entry.billing === 'pay_per_use' ? _jsx(Chip, { size: "small", variant: "outlined", label: t('Chat.payPerUse') }) : null] }, entry.id))) })] }, driver))), _jsxs(Box, { children: [_jsx(Typography, { variant: "overline", color: "text.secondary", children: t('Chat.effortLabel') }), _jsx(ToggleButtonGroup, { exclusive: true, fullWidth: true, size: "small", "aria-label": t('Chat.effortLabel'), value: (selection === null || selection === void 0 ? void 0 : selection.effort) || null, onChange: (_event, effort) => { if (effort && active)
|
|
31
|
+
onChange === null || onChange === void 0 ? void 0 : onChange({ entry: active.id, effort }); }, children: EFFORT_LEVELS.map((effort) => { var _a; return (_jsx(ToggleButton, { value: effort, disabled: !((_a = active === null || active === void 0 ? void 0 : active.efforts) === null || _a === void 0 ? void 0 : _a.includes(effort)), sx: { minHeight: 40 }, children: t(`Chat.effort.${effort}`) }, effort)); }) })] })] }) }));
|
|
32
|
+
const label = active ? [active.label || active.model, (selection === null || selection === void 0 ? void 0 : selection.effort) && t(`Chat.effort.${selection.effort}`)].filter(Boolean).join(' · ') : t('Chat.chooseModel');
|
|
33
|
+
return (_jsxs(_Fragment, { children: [_jsx(Button, { ref: anchor, size: "small", color: "inherit", disabled: disabled || entries.length === 0, onClick: () => setOpen(true), "aria-haspopup": "dialog", children: label }), narrow ? (_jsx(Drawer, { anchor: "bottom", open: isOpen, onClose: () => setOpen(false), children: content })) : (_jsx(Popover, { open: isOpen, anchorEl: anchor.current, onClose: () => setOpen(false), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, children: content }))] }));
|
|
34
|
+
}
|
|
35
|
+
export default ModelPicker;
|
package/dist/chat/api.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import apiClient from '../auth/apiClient';
|
|
2
|
+
import { chatApiUrls } from './wire';
|
|
3
|
+
export function createChatApi(overrides = {}, { apiBase = '/api/assistant/' } = {}) {
|
|
4
|
+
const urls = chatApiUrls(apiBase);
|
|
5
|
+
const data = (request) => request.then((response) => response.data);
|
|
6
|
+
const defaults = {
|
|
7
|
+
getCatalogue: () => data(apiClient.get(urls.catalogue)),
|
|
8
|
+
listChats: ({ archived = false, cursor } = {}) => data(apiClient.get(urls.chats, { params: Object.assign({ archived }, (cursor ? { cursor } : {})) })),
|
|
9
|
+
createChat: (payload = {}) => data(apiClient.post(urls.chats, payload)),
|
|
10
|
+
updateChat: (id, patch) => data(apiClient.patch(urls.chat(id), patch)),
|
|
11
|
+
deleteChat: (id) => data(apiClient.delete(urls.chat(id))),
|
|
12
|
+
listTurns: (id, { cursor } = {}) => data(apiClient.get(urls.turns(id), { params: cursor ? { cursor } : {} })),
|
|
13
|
+
getStatus: () => data(apiClient.get(urls.status)),
|
|
14
|
+
};
|
|
15
|
+
return Object.assign(Object.assign({}, defaults), overrides);
|
|
16
|
+
}
|
|
17
|
+
export const defaultChatApi = createChatApi();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { CHAT_CLOSE_CODES, defaultChatWsUrl } from './wire';
|
|
3
|
+
const delays = [1000, 2000, 4000];
|
|
4
|
+
export function useChatSocket(chatId, { wsUrl = defaultChatWsUrl, onFrame, onOpen, onClose } = {}) {
|
|
5
|
+
const socket = useRef(null);
|
|
6
|
+
const callbacks = useRef({ onFrame, onOpen, onClose });
|
|
7
|
+
callbacks.current = { onFrame, onOpen, onClose };
|
|
8
|
+
// The URL builder is read at connect time only: an inline arrow from the caller must not reopen the socket on every render.
|
|
9
|
+
const urlBuilder = useRef(wsUrl);
|
|
10
|
+
urlBuilder.current = wsUrl;
|
|
11
|
+
const [status, setStatus] = useState('idle');
|
|
12
|
+
const [generation, setGeneration] = useState(0);
|
|
13
|
+
const reconnect = useCallback(() => setGeneration((n) => n + 1), []);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!chatId || typeof WebSocket === 'undefined') {
|
|
16
|
+
setStatus('idle');
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
let alive = true;
|
|
20
|
+
let tries = 0;
|
|
21
|
+
let timer;
|
|
22
|
+
let wasOpened = false;
|
|
23
|
+
const connect = () => {
|
|
24
|
+
setStatus('connecting');
|
|
25
|
+
const ws = new WebSocket(typeof urlBuilder.current === 'function' ? urlBuilder.current(chatId) : urlBuilder.current);
|
|
26
|
+
socket.current = ws;
|
|
27
|
+
ws.onopen = () => { var _a, _b; if (!alive)
|
|
28
|
+
return; tries = 0; setStatus('open'); (_b = (_a = callbacks.current).onOpen) === null || _b === void 0 ? void 0 : _b.call(_a, chatId, wasOpened); wasOpened = true; };
|
|
29
|
+
ws.onmessage = (event) => { var _a, _b; if (!alive)
|
|
30
|
+
return; try {
|
|
31
|
+
const frame = JSON.parse(event.data);
|
|
32
|
+
if (frame && typeof frame === 'object')
|
|
33
|
+
(_b = (_a = callbacks.current).onFrame) === null || _b === void 0 ? void 0 : _b.call(_a, frame, chatId);
|
|
34
|
+
}
|
|
35
|
+
catch (_c) { /* Ignore malformed frames. */ } };
|
|
36
|
+
ws.onclose = (event) => {
|
|
37
|
+
var _a, _b, _c, _d, _e, _f;
|
|
38
|
+
if (!alive || socket.current !== ws)
|
|
39
|
+
return;
|
|
40
|
+
socket.current = null;
|
|
41
|
+
const reason = CHAT_CLOSE_CODES[event.code];
|
|
42
|
+
if (reason) {
|
|
43
|
+
setStatus('closed');
|
|
44
|
+
(_b = (_a = callbacks.current).onClose) === null || _b === void 0 ? void 0 : _b.call(_a, { reason, chatId, retrying: false });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (tries < delays.length) {
|
|
48
|
+
setStatus('connecting');
|
|
49
|
+
(_d = (_c = callbacks.current).onClose) === null || _d === void 0 ? void 0 : _d.call(_c, { reason: 'connection_lost', chatId, retrying: true });
|
|
50
|
+
timer = setTimeout(connect, delays[tries++]);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
setStatus('closed');
|
|
54
|
+
(_f = (_e = callbacks.current).onClose) === null || _f === void 0 ? void 0 : _f.call(_e, { reason: 'connection_lost', chatId, retrying: false });
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
connect();
|
|
59
|
+
return () => { alive = false; clearTimeout(timer); const ws = socket.current; socket.current = null; if (ws) {
|
|
60
|
+
ws.onopen = ws.onmessage = ws.onclose = null;
|
|
61
|
+
ws.close();
|
|
62
|
+
} };
|
|
63
|
+
}, [chatId, generation]);
|
|
64
|
+
const send = useCallback((frame) => { const ws = socket.current; if (!ws || ws.readyState !== WebSocket.OPEN)
|
|
65
|
+
return false; ws.send(JSON.stringify(frame)); return true; }, []);
|
|
66
|
+
return { status, connected: status === 'open', send, reconnect };
|
|
67
|
+
}
|
|
68
|
+
export default useChatSocket;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const CHAT_CLOSE_CODES = { 4401: 'session_ended', 4403: 'access_lost', 4429: 'rate_limited' };
|
|
2
|
+
export const CHAT_ERROR_CODES = ['quota_exceeded', 'provider_error', 'timeout', 'prompt_too_long', 'invalid_output', 'security_violation', 'not_configured', 'rate_limited', 'invalid_selection', 'archived', 'turn_active', 'invalid_frame'];
|
|
3
|
+
export const chatApiUrls = (base = '/api/assistant/') => {
|
|
4
|
+
const root = base.replace(/\/$/, '') + '/';
|
|
5
|
+
return { catalogue: root + 'catalogue/', chats: root + 'chats/', chat: (id) => root + 'chats/' + encodeURIComponent(id) + '/', turns: (id) => root + 'chats/' + encodeURIComponent(id) + '/turns/', status: root + 'status/' };
|
|
6
|
+
};
|
|
7
|
+
export const defaultChatWsUrl = (chatId) => (window.location.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + window.location.host + '/ws/assistant/chats/' + encodeURIComponent(chatId) + '/';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useEffect } from 'react';
|
|
3
3
|
import { Box, TextField, Button, Typography, Divider, } from '@mui/material';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// src/auth/components/SecurityComponent.jsx
|
|
3
3
|
import React, { useContext, useMemo, useState } from 'react';
|
|
4
4
|
import { Box, Typography, Divider, Alert, } from '@mui/material';
|
|
@@ -265,10 +265,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
265
265
|
align: 'center',
|
|
266
266
|
headerAlign: 'center',
|
|
267
267
|
valueGetter: (row) => { var _a; return Boolean(((_a = row === null || row === void 0 ? void 0 : row.profile) === null || _a === void 0 ? void 0 : _a.is_new) || (row === null || row === void 0 ? void 0 : row.is_new)); },
|
|
268
|
-
renderCell: (row) => {
|
|
269
|
-
var _a;
|
|
270
|
-
return renderBooleanStatusIcon(Boolean(((_a = row === null || row === void 0 ? void 0 : row.profile) === null || _a === void 0 ? void 0 : _a.is_new) || (row === null || row === void 0 ? void 0 : row.is_new)), t('Common.YES', 'Yes'), t('Common.NO', 'No'));
|
|
271
|
-
},
|
|
268
|
+
renderCell: (row) => { var _a; return renderBooleanStatusIcon(Boolean(((_a = row === null || row === void 0 ? void 0 : row.profile) === null || _a === void 0 ? void 0 : _a.is_new) || (row === null || row === void 0 ? void 0 : row.is_new)), t('Common.YES', 'Yes'), t('Common.NO', 'No')); },
|
|
272
269
|
});
|
|
273
270
|
}
|
|
274
271
|
if (showSuccessfulLoginColumn) {
|
|
@@ -281,10 +278,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
281
278
|
align: 'center',
|
|
282
279
|
headerAlign: 'center',
|
|
283
280
|
valueGetter: (row) => { var _a; return Boolean((_a = row === null || row === void 0 ? void 0 : row.successful_login) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.last_login); },
|
|
284
|
-
renderCell: (row) => {
|
|
285
|
-
var _a;
|
|
286
|
-
return renderBooleanStatusIcon(Boolean((_a = row === null || row === void 0 ? void 0 : row.successful_login) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.last_login), t('Common.YES', 'Yes'), t('Common.NO', 'No'));
|
|
287
|
-
},
|
|
281
|
+
renderCell: (row) => { var _a; return renderBooleanStatusIcon(Boolean((_a = row === null || row === void 0 ? void 0 : row.successful_login) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.last_login), t('Common.YES', 'Yes'), t('Common.NO', 'No')); },
|
|
288
282
|
});
|
|
289
283
|
}
|
|
290
284
|
if (showRoleColumn) {
|
|
@@ -9,7 +9,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import {
|
|
12
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import React, { useId, useRef, useState } from 'react';
|
|
14
14
|
import { Alert, Box, Button, CircularProgress, Paper, Stack, Tooltip, Typography, } from '@mui/material';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
@@ -40,7 +40,8 @@ export function scaleBubbleRadius(value, maxValue, { minRadius = BUBBLE_MIN_RADI
|
|
|
40
40
|
// continuous-scale colour) can only be expressed by replacing the marker itself.
|
|
41
41
|
function makeMarkerSlot(seriesById, { sizeAccessor, chartMax, getPointStyle }) {
|
|
42
42
|
return function PresetScatterMarker(_a) {
|
|
43
|
-
var _b, _c
|
|
43
|
+
var _b, _c;
|
|
44
|
+
var _d, _e, _f, _g;
|
|
44
45
|
var { seriesId, dataIndex, color, x, y, isHighlighted, isFaded, size, onClick } = _a, rest = __rest(_a, ["seriesId", "dataIndex", "color", "x", "y", "isHighlighted", "isFaded", "size", "onClick"]);
|
|
45
46
|
const point = (_c = (_b = seriesById[seriesId]) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c[dataIndex];
|
|
46
47
|
const style = point && getPointStyle ? (getPointStyle(point, { seriesId, dataIndex }) || {}) : {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const chatTranslations = {
|
|
2
|
+
'Chat.newChat': { de: 'Neuer Chat', en: 'New chat', fr: 'Nouveau chat', sw: 'Mazungumzo mapya' },
|
|
3
|
+
'Chat.chats': { de: 'Chats', en: 'Chats', fr: 'Discussions', sw: 'Mazungumzo' },
|
|
4
|
+
'Chat.archived': { de: 'Archiviert', en: 'Archived', fr: 'Archivé', sw: 'Imewekwa kwenye kumbukumbu' },
|
|
5
|
+
'Chat.restore': { de: 'Wiederherstellen', en: 'Restore', fr: 'Restaurer', sw: 'Rejesha' },
|
|
6
|
+
'Chat.rename': { de: 'Umbenennen', en: 'Rename', fr: 'Renommer', sw: 'Badilisha jina' },
|
|
7
|
+
'Chat.archive': { de: 'Archivieren', en: 'Archive', fr: 'Archiver', sw: 'Weka kwenye kumbukumbu' },
|
|
8
|
+
'Chat.delete': { de: 'Löschen', en: 'Delete', fr: 'Supprimer', sw: 'Futa' },
|
|
9
|
+
'Chat.deleteConfirm': { de: 'Möchten Sie diesen Chat wirklich löschen?', en: 'Delete this chat?', fr: 'Supprimer cette discussion ?', sw: 'Futa mazungumzo haya?' },
|
|
10
|
+
'Chat.cancel': { de: 'Abbrechen', en: 'Cancel', fr: 'Annuler', sw: 'Ghairi' },
|
|
11
|
+
'Chat.save': { de: 'Speichern', en: 'Save', fr: 'Enregistrer', sw: 'Hifadhi' },
|
|
12
|
+
'Chat.actions': { de: 'Chat-Aktionen', en: 'Chat actions', fr: 'Actions de discussion', sw: 'Vitendo vya mazungumzo' },
|
|
13
|
+
'Chat.loadMore': { de: 'Mehr laden', en: 'Load more', fr: 'Charger plus', sw: 'Pakia zaidi' },
|
|
14
|
+
'Chat.loadOlder': { de: 'Ältere laden', en: 'Load older', fr: 'Charger les plus anciens', sw: 'Pakia za zamani' },
|
|
15
|
+
'Chat.send': { de: 'Senden', en: 'Send', fr: 'Envoyer', sw: 'Tuma' },
|
|
16
|
+
'Chat.stop': { de: 'Stoppen', en: 'Stop', fr: 'Arrêter', sw: 'Simamisha' },
|
|
17
|
+
'Chat.message': { de: 'Nachricht', en: 'Message', fr: 'Message', sw: 'Ujumbe' },
|
|
18
|
+
'Chat.typing': { de: 'Antwort wird erstellt …', en: 'Thinking…', fr: 'Réflexion…', sw: 'Inafikiria…' },
|
|
19
|
+
'Chat.retry': { de: 'Erneut versuchen', en: 'Retry', fr: 'Réessayer', sw: 'Jaribu tena' },
|
|
20
|
+
'Chat.interrupted': { de: 'Unterbrochen', en: 'Interrupted', fr: 'Interrompue', sw: 'Imekatizwa' },
|
|
21
|
+
'Chat.reconnecting': { de: 'Verbindung wird wiederhergestellt', en: 'Reconnecting', fr: 'Reconnexion', sw: 'Inaunganisha tena' },
|
|
22
|
+
'Chat.payPerUse': { de: 'Nutzungsabhängig', en: 'pay per use', fr: 'à l’usage', sw: 'lipa kwa matumizi' },
|
|
23
|
+
'Chat.chooseModel': { de: 'Modell wählen', en: 'Choose model', fr: 'Choisir un modèle', sw: 'Chagua modeli' },
|
|
24
|
+
'Chat.effort.low': { de: 'Niedrig', en: 'Low', fr: 'Faible', sw: 'Chini' },
|
|
25
|
+
'Chat.effort.medium': { de: 'Mittel', en: 'Medium', fr: 'Moyen', sw: 'Wastani' },
|
|
26
|
+
'Chat.effort.high': { de: 'Hoch', en: 'High', fr: 'Élevé', sw: 'Juu' },
|
|
27
|
+
'Chat.effort.max': { de: 'Maximum', en: 'Max', fr: 'Maximal', sw: 'Juu zaidi' },
|
|
28
|
+
'Chat.driver.claude_cli': { de: 'Claude Code (Abo)', en: 'Claude Code subscription', fr: 'Claude Code (abonnement)', sw: 'Claude Code (usajili)' },
|
|
29
|
+
'Chat.driver.anthropic_api': { de: 'Anthropic API', en: 'Anthropic API', fr: 'API Anthropic', sw: 'API ya Anthropic' },
|
|
30
|
+
'Chat.driver.openai_api': { de: 'OpenAI API', en: 'OpenAI API', fr: 'API OpenAI', sw: 'API ya OpenAI' },
|
|
31
|
+
'Chat.markdownTable': { de: 'Tabelleninhalt', en: 'Table content', fr: 'Contenu du tableau', sw: 'Yaliyomo kwenye jedwali' },
|
|
32
|
+
'Chat.markdownCode': { de: 'Codeblock', en: 'Code block', fr: 'Bloc de code', sw: 'Kizuizi cha msimbo' },
|
|
33
|
+
'Chat.changeModel': { de: 'Modell ändern', en: 'Change model', fr: 'Changer de modèle', sw: 'Badilisha modeli' },
|
|
34
|
+
'Chat.usageLimit': { de: 'Nutzungslimit für {{model}} erreicht. Wählen Sie ein anderes Modell oder versuchen Sie es später erneut.', en: 'Usage limit reached for {{model}}. Choose another model or try again later.', fr: 'Limite d’utilisation atteinte pour {{model}}. Choisissez un autre modèle ou réessayez plus tard.', sw: 'Kikomo cha matumizi kimefikiwa kwa {{model}}. Chagua modeli nyingine au jaribu tena baadaye.' },
|
|
35
|
+
'Chat.answerIncomplete': { de: 'Die Antwort konnte nicht abgeschlossen werden.', en: 'The answer could not be completed.', fr: 'La réponse n’a pas pu être terminée.', sw: 'Jibu halikuweza kukamilishwa.' },
|
|
36
|
+
'Chat.unavailable': { de: 'Chat ist nicht verfügbar', en: 'Chat is not available', fr: 'Le chat n’est pas disponible', sw: 'Mazungumzo hayapatikani' },
|
|
37
|
+
'Chat.noModel': { de: 'Auf diesem Server ist kein Sprachmodell konfiguriert.', en: 'No language model is configured on this server.', fr: 'Aucun modèle de langage n’est configuré sur ce serveur.', sw: 'Hakuna modeli ya lugha iliyosanidiwa kwenye seva hii.' },
|
|
38
|
+
'Chat.modelChanged': { de: 'Modell gewechselt: {{model}}', en: 'Model changed: {{model}}', fr: 'Modèle changé : {{model}}', sw: 'Modeli imebadilishwa: {{model}}' },
|
|
39
|
+
'Chat.connectionLost': { de: 'Verbindung verloren', en: 'Connection lost', fr: 'Connexion perdue', sw: 'Muunganisho umepotea' },
|
|
40
|
+
'Chat.reconnect': { de: 'Erneut verbinden', en: 'Reconnect', fr: 'Se reconnecter', sw: 'Unganisha tena' },
|
|
41
|
+
'Chat.sessionEnded': { de: 'Ihre Sitzung ist beendet. Bitte melden Sie sich erneut an.', en: 'Your session has ended. Please sign in again.', fr: 'Votre session a pris fin. Veuillez vous reconnecter.', sw: 'Kipindi chako kimeisha. Tafadhali ingia tena.' },
|
|
42
|
+
'Chat.accessLost': { de: 'Sie haben keinen Zugriff mehr auf diesen Chat.', en: 'You no longer have access to this chat.', fr: 'Vous n’avez plus accès à cette discussion.', sw: 'Huna tena ufikiaji wa mazungumzo haya.' },
|
|
43
|
+
'Chat.rateLimited': { de: 'Zu viele Anfragen. Bitte einen Moment warten.', en: 'Too many requests. Please wait a moment.', fr: 'Trop de requêtes. Veuillez patienter un instant.', sw: 'Maombi mengi mno. Tafadhali subiri kidogo.' },
|
|
44
|
+
'Chat.untitled': { de: 'Neuer Chat', en: 'New chat', fr: 'Nouvelle discussion', sw: 'Mazungumzo mapya' },
|
|
45
|
+
'Chat.emptyTitle': { de: 'Wie kann ich helfen?', en: 'How can I help?', fr: 'Comment puis-je aider ?', sw: 'Naweza kusaidiaje?' },
|
|
46
|
+
'Chat.selectChat': { de: 'Wählen Sie einen Chat oder starten Sie einen neuen.', en: 'Select a chat or start a new one.', fr: 'Sélectionnez une discussion ou commencez-en une nouvelle.', sw: 'Chagua mazungumzo au anza mapya.' },
|
|
47
|
+
'Chat.archivedReadOnly': { de: 'Archivierter Chat. Stellen Sie ihn wieder her, um weiterzuschreiben.', en: 'Archived chat. Restore it to continue.', fr: 'Discussion archivée. Restaurez-la pour continuer.', sw: 'Mazungumzo yaliyowekwa kwenye kumbukumbu. Yarejeshe ili kuendelea.' },
|
|
48
|
+
'Chat.loadError': { de: 'Chats konnten nicht geladen werden.', en: 'Chats could not be loaded.', fr: 'Impossible de charger les discussions.', sw: 'Mazungumzo hayakuweza kupakiwa.' },
|
|
49
|
+
'Chat.actionError': { de: 'Aktion fehlgeschlagen.', en: 'Action failed.', fr: 'Action impossible.', sw: 'Kitendo kimeshindwa.' },
|
|
50
|
+
'Chat.sources': { de: 'Quellen', en: 'Sources', fr: 'Sources', sw: 'Vyanzo' },
|
|
51
|
+
'Chat.model': { de: 'Modell', en: 'Model', fr: 'Modèle', sw: 'Modeli' },
|
|
52
|
+
'Chat.effortLabel': { de: 'Aufwand', en: 'Effort', fr: 'Effort', sw: 'Juhudi' },
|
|
53
|
+
'Chat.renameLabel': { de: 'Chat-Titel', en: 'Chat title', fr: 'Titre de la discussion', sw: 'Kichwa cha mazungumzo' },
|
|
54
|
+
'Chat.thread': { de: 'Unterhaltung', en: 'Conversation', fr: 'Conversation', sw: 'Mazungumzo' },
|
|
55
|
+
};
|
|
@@ -11,12 +11,13 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { authTranslations } from './authTranslations';
|
|
13
13
|
import { chartsTranslations } from './chartsTranslations';
|
|
14
|
+
import { chatTranslations } from './chatTranslations';
|
|
14
15
|
import { messagingTranslations } from './messagingTranslations';
|
|
15
16
|
import { notificationsTranslations } from './notificationsTranslations';
|
|
16
17
|
import { onboardingTranslations } from './onboardingTranslations';
|
|
17
18
|
import { sectionNavTranslations } from './sectionNavTranslations';
|
|
18
19
|
import { userMenuTranslations } from './userMenuTranslations';
|
|
19
|
-
export const uiCoreTranslations = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, authTranslations), chartsTranslations), messagingTranslations), notificationsTranslations), onboardingTranslations), sectionNavTranslations), userMenuTranslations);
|
|
20
|
+
export const uiCoreTranslations = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, authTranslations), chartsTranslations), chatTranslations), messagingTranslations), notificationsTranslations), onboardingTranslations), sectionNavTranslations), userMenuTranslations);
|
|
20
21
|
export function createUiCoreTranslations({ germanVariant = 'formal' } = {}) {
|
|
21
22
|
return Object.fromEntries(Object.entries(uiCoreTranslations).map(([key, value]) => {
|
|
22
23
|
const { de_informal } = value, rest = __rest(value, ["de_informal"]);
|
package/dist/index.js
CHANGED
|
@@ -78,6 +78,15 @@ export { AttachmentList } from './messaging/AttachmentList';
|
|
|
78
78
|
export { ReactionBar } from './messaging/ReactionBar';
|
|
79
79
|
export { PollCard } from './messaging/PollCard';
|
|
80
80
|
export { MessagingScopeConfig } from './messaging/MessagingScopeConfig';
|
|
81
|
+
// --- Shared AI chat ---
|
|
82
|
+
export { ChatWorkspace } from './chat/ChatWorkspace';
|
|
83
|
+
export { ChatList } from './chat/ChatList';
|
|
84
|
+
export { ChatThread } from './chat/ChatThread';
|
|
85
|
+
export { ChatComposer } from './chat/ChatComposer';
|
|
86
|
+
export { ModelPicker } from './chat/ModelPicker';
|
|
87
|
+
export { default as MarkdownReply } from './chat/MarkdownReply';
|
|
88
|
+
export { useChatSocket } from './chat/useChatSocket';
|
|
89
|
+
export { createChatApi, defaultChatApi } from './chat/api';
|
|
81
90
|
// --- 9. Onboarding ---
|
|
82
91
|
export * from './onboarding/api';
|
|
83
92
|
export { selectActiveSteps } from './onboarding/stepSelection';
|
|
@@ -92,4 +101,5 @@ export { notificationsTranslations } from './i18n/notificationsTranslations';
|
|
|
92
101
|
export { onboardingTranslations } from './i18n/onboardingTranslations';
|
|
93
102
|
export { chartsTranslations } from './i18n/chartsTranslations';
|
|
94
103
|
export { messagingTranslations } from './i18n/messagingTranslations';
|
|
104
|
+
export { chatTranslations } from './i18n/chatTranslations';
|
|
95
105
|
export { createUiCoreTranslations, uiCoreTranslations } from './i18n/uiCoreTranslations';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useId, useState } from 'react';
|
|
3
3
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
|
4
4
|
import { Box, ButtonBase, Drawer, List, ListItem, ListItemButton, ListItemText, Paper, Stack, Typography, } from '@mui/material';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import CloseIcon from '@mui/icons-material/Close';
|
|
3
3
|
import DownloadIcon from '@mui/icons-material/DownloadOutlined';
|
|
4
4
|
import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined';
|
|
@@ -49,7 +49,7 @@ export function AttachmentList({ attachments = [] }) {
|
|
|
49
49
|
setPreviews((current) => (Object.assign(Object.assign({}, current), { [attachment.id]: url })));
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
catch (
|
|
52
|
+
catch (_a) { /* The image remains available through the download action. */ }
|
|
53
53
|
});
|
|
54
54
|
return () => { live = false; urls.forEach((url) => URL.revokeObjectURL(url)); };
|
|
55
55
|
}, [attachments, getAttachmentThumbnail]);
|
|
@@ -110,7 +110,8 @@ export function Composer({ conversationId, conversation, replyTarget = null, onR
|
|
|
110
110
|
};
|
|
111
111
|
const removeFile = (index) => setFiles((current) => current.filter((_, fileIndex) => fileIndex !== index));
|
|
112
112
|
const insertEmoji = (emoji) => {
|
|
113
|
-
var _a, _b
|
|
113
|
+
var _a, _b;
|
|
114
|
+
var _c, _d;
|
|
114
115
|
const input = ((_b = (_a = messageInputRef.current) === null || _a === void 0 ? void 0 : _a.querySelector) === null || _b === void 0 ? void 0 : _b.call(_a, 'textarea')) || messageInputRef.current;
|
|
115
116
|
const start = (_c = input === null || input === void 0 ? void 0 : input.selectionStart) !== null && _c !== void 0 ? _c : body.length;
|
|
116
117
|
const end = (_d = input === null || input === void 0 ? void 0 : input.selectionEnd) !== null && _d !== void 0 ? _d : start;
|
|
@@ -32,8 +32,5 @@ export function ConversationLaunchers({ groupLaunchers = [], broadcastLauncher,
|
|
|
32
32
|
autoOpened.current = true;
|
|
33
33
|
open('broadcast', (_a = broadcastLauncher.payload) !== null && _a !== void 0 ? _a : broadcastLauncher);
|
|
34
34
|
}, [autoOpenBroadcast, broadcastLauncher]); // one intentional auto-open per mounted launcher
|
|
35
|
-
return (_jsxs(Stack, { spacing: 1, children: [broadcastLauncher && !autoOpenBroadcast && (_jsx(Button, { variant: "outlined", onClick: () => { var _a; return open('broadcast', (_a = broadcastLauncher.payload) !== null && _a !== void 0 ? _a : broadcastLauncher); }, children: broadcastLauncher.label || t('MessagingLaunchers.OPEN_BROADCAST') })), groupLaunchers.map((launcher) => {
|
|
36
|
-
var _a;
|
|
37
|
-
return (_jsx(Button, { variant: "outlined", onClick: () => { var _a; return open('group', (_a = launcher.payload) !== null && _a !== void 0 ? _a : launcher); }, children: launcher.label || t('MessagingLaunchers.OPEN_GROUP') }, (_a = launcher.id) !== null && _a !== void 0 ? _a : launcher.label));
|
|
38
|
-
}), error && _jsx(Alert, { severity: "error", children: error })] }));
|
|
35
|
+
return (_jsxs(Stack, { spacing: 1, children: [broadcastLauncher && !autoOpenBroadcast && (_jsx(Button, { variant: "outlined", onClick: () => { var _a; return open('broadcast', (_a = broadcastLauncher.payload) !== null && _a !== void 0 ? _a : broadcastLauncher); }, children: broadcastLauncher.label || t('MessagingLaunchers.OPEN_BROADCAST') })), groupLaunchers.map((launcher) => { var _a; return (_jsx(Button, { variant: "outlined", onClick: () => { var _a; return open('group', (_a = launcher.payload) !== null && _a !== void 0 ? _a : launcher); }, children: launcher.label || t('MessagingLaunchers.OPEN_GROUP') }, (_a = launcher.id) !== null && _a !== void 0 ? _a : launcher.label)); }), error && _jsx(Alert, { severity: "error", children: error })] }));
|
|
39
36
|
}
|
|
@@ -64,7 +64,7 @@ export function ConversationList({ onOpen, groupLaunchers, broadcastLauncher, au
|
|
|
64
64
|
const [loadingMore, setLoadingMore] = useState(false);
|
|
65
65
|
const [error, setError] = useState(null);
|
|
66
66
|
const conversations = useMemo(() => ordered(Object.values(cache.conversations).filter((item) => includeArchived || !item.archived_at && !item.archived)), [cache.conversations, includeArchived]);
|
|
67
|
-
const unreadFor = (conversation) => { var _a
|
|
67
|
+
const unreadFor = (conversation) => { var _a; var _b, _c; return (_c = (_b = conversation.unread_count) !== null && _b !== void 0 ? _b : (_a = cache.unread.by_conversation) === null || _a === void 0 ? void 0 : _a[conversation.id]) !== null && _c !== void 0 ? _c : 0; };
|
|
68
68
|
// Once opened, the broadcast conversation becomes a normal, recency-sorted
|
|
69
69
|
// row with real unread/preview state — hide the static launcher so it isn't
|
|
70
70
|
// shown twice (button + list row) with the button's stale, unread-less copy.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useId, useMemo, useState } from 'react';
|
|
3
3
|
import { Alert, Autocomplete, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, TextField, Typography, useMediaQuery, } from '@mui/material';
|
|
4
4
|
import { useTheme } from '@mui/material/styles';
|
|
Binary file
|
|
@@ -24,7 +24,8 @@ export const POLL_CARD_CHECKBOX_SX = { pt: 0.25 };
|
|
|
24
24
|
* rejected vote.
|
|
25
25
|
*/
|
|
26
26
|
export function PollCard({ message, canClose: canCloseProp }) {
|
|
27
|
-
var _a, _b
|
|
27
|
+
var _a, _b;
|
|
28
|
+
var _c, _d, _e, _f, _g, _h;
|
|
28
29
|
const { t } = useTranslation();
|
|
29
30
|
const { castPollVote, closeConversationPoll, currentUser } = useMessaging();
|
|
30
31
|
const poll = message === null || message === void 0 ? void 0 : message.poll;
|
|
@@ -47,8 +48,8 @@ export function PollCard({ message, canClose: canCloseProp }) {
|
|
|
47
48
|
if (!poll)
|
|
48
49
|
return null;
|
|
49
50
|
const closed = Boolean(poll.closed_at);
|
|
50
|
-
const creatorId = (
|
|
51
|
-
const canClose = (_g = (
|
|
51
|
+
const creatorId = (_d = (_c = poll.created_by_id) !== null && _c !== void 0 ? _c : (_a = poll.created_by) === null || _a === void 0 ? void 0 : _a.id) !== null && _d !== void 0 ? _d : poll.created_by;
|
|
52
|
+
const canClose = (_g = (_f = (_e = canCloseProp !== null && canCloseProp !== void 0 ? canCloseProp : poll.can_close) !== null && _e !== void 0 ? _e : message === null || message === void 0 ? void 0 : message.can_close) !== null && _f !== void 0 ? _f : (_b = message === null || message === void 0 ? void 0 : message.permissions) === null || _b === void 0 ? void 0 : _b.can_close) !== null && _g !== void 0 ? _g : (creatorId != null && creatorId === (currentUser === null || currentUser === void 0 ? void 0 : currentUser.id));
|
|
52
53
|
// Percentage denominator (MSG-6i): the number of distinct people who
|
|
53
54
|
// answered this poll, not sum(vote_count) across options. For a
|
|
54
55
|
// multi-select poll one person can vote for several options, so summing
|
|
@@ -109,11 +110,12 @@ export function PollCard({ message, canClose: canCloseProp }) {
|
|
|
109
110
|
}
|
|
110
111
|
};
|
|
111
112
|
const optionLabel = (option) => {
|
|
112
|
-
var _a
|
|
113
|
+
var _a;
|
|
114
|
+
var _b, _c;
|
|
113
115
|
// dcm's poll projection gives voter user ids only, never names — there
|
|
114
116
|
// is no user-directory endpoint (same structural limit already
|
|
115
117
|
// established for the DM candidate list).
|
|
116
|
-
const voteCount = (_c = (
|
|
118
|
+
const voteCount = (_c = (_b = option.vote_count) !== null && _b !== void 0 ? _b : (_a = option.voters) === null || _a === void 0 ? void 0 : _a.length) !== null && _c !== void 0 ? _c : 0;
|
|
117
119
|
const percent = respondentCount > 0 ? Math.round((voteCount / respondentCount) * 100) : 0;
|
|
118
120
|
const votedByMe = selected.includes(option.id);
|
|
119
121
|
const resultText = t('MessagingPoll.OPTION_RESULT', { count: voteCount, percent });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import DoneAllOutlinedIcon from '@mui/icons-material/DoneAllOutlined';
|
|
3
3
|
import DoneOutlinedIcon from '@mui/icons-material/DoneOutlined';
|
|
4
4
|
import { ButtonBase, List, ListItem, Popover, Tooltip, Typography } from '@mui/material';
|
|
@@ -41,7 +41,7 @@ export function ReadTicks({ messageId, conversation }) {
|
|
|
41
41
|
}, [getMessageReadStatus, messageId]);
|
|
42
42
|
if (!status)
|
|
43
43
|
return null;
|
|
44
|
-
const recipientDetail = ((_a = status.recipient_detail) === null || _a === void 0 ? void 0 : _a.map((recipient) => { var _a
|
|
44
|
+
const recipientDetail = ((_a = status.recipient_detail) === null || _a === void 0 ? void 0 : _a.map((recipient) => { var _a; var _b; return (Object.assign(Object.assign({}, recipient), { last_read_at: (_b = (_a = cache.receipts[`${conversation === null || conversation === void 0 ? void 0 : conversation.id}:${recipient.user_id}`]) === null || _a === void 0 ? void 0 : _a.last_read_at) !== null && _b !== void 0 ? _b : recipient.last_read_at })); })) || status.recipient_detail;
|
|
45
45
|
const directReceipt = (conversation === null || conversation === void 0 ? void 0 : conversation.kind) === 'direct' && (conversation === null || conversation === void 0 ? void 0 : conversation.other_user_id) != null
|
|
46
46
|
? cache.receipts[`${conversation.id}:${conversation.other_user_id}`]
|
|
47
47
|
: null;
|
package/dist/messaging/Thread.js
CHANGED
|
@@ -15,7 +15,8 @@ function replyToId(message) { var _a; return (_a = message.reply_to_id) !== null
|
|
|
15
15
|
// MessagingProvider.jsx's applyFrame merge-trap handling). A null receipt with
|
|
16
16
|
// at least one reply counts as unread; no replies means no marker at all.
|
|
17
17
|
function hasUnreadReplies(message, receipts, userId) {
|
|
18
|
-
var _a
|
|
18
|
+
var _a;
|
|
19
|
+
var _b;
|
|
19
20
|
if (!message.reply_count)
|
|
20
21
|
return false;
|
|
21
22
|
const lastReadAt = (_b = (_a = receipts[`thread:${message.id}:${userId}`]) === null || _a === void 0 ? void 0 : _a.last_read_at) !== null && _b !== void 0 ? _b : message.thread_last_read_at;
|
|
@@ -77,10 +78,11 @@ export function Thread({ conversationId, onReplyTargetChange, canModerateMessage
|
|
|
77
78
|
if (conversationId == null)
|
|
78
79
|
return undefined;
|
|
79
80
|
return subscribe(MESSAGING_ENVELOPE, (frame) => {
|
|
80
|
-
var _a
|
|
81
|
+
var _a;
|
|
82
|
+
var _b;
|
|
81
83
|
if (frame.type !== 'message')
|
|
82
84
|
return;
|
|
83
|
-
const frameConversationId = (
|
|
85
|
+
const frameConversationId = (_b = frame.conversation_id) !== null && _b !== void 0 ? _b : (_a = frame.message) === null || _a === void 0 ? void 0 : _a.conversation_id;
|
|
84
86
|
if (String(frameConversationId) !== String(conversationId))
|
|
85
87
|
return;
|
|
86
88
|
if (document.visibilityState === 'hidden')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import NotificationsIcon from '@mui/icons-material/Notifications';
|
|
4
4
|
import { Badge, Box, Button, IconButton, List, ListItemButton, ListItemText, Menu, Typography, } from '@mui/material';
|
|
@@ -20,10 +20,7 @@ export function NotificationBell({ resolveLink }) {
|
|
|
20
20
|
if (resolveLink && ((_a = notification.content) === null || _a === void 0 ? void 0 : _a.link))
|
|
21
21
|
resolveLink(notification.content.link);
|
|
22
22
|
};
|
|
23
|
-
return (_jsxs(_Fragment, { children: [_jsx(IconButton, { "aria-label": t('NotificationBell.TITLE'), color: "inherit", onClick: (event) => setAnchorEl(event.currentTarget), children: _jsx(Badge, { badgeContent: unreadCount, color: "error", max: 99, children: _jsx(NotificationsIcon, {}) }) }), _jsx(Menu, { anchorEl: anchorEl, anchorOrigin: { horizontal: 'right', vertical: 'bottom' }, onClose: () => setAnchorEl(null), open: open, transformOrigin: { horizontal: 'right', vertical: 'top' }, children: _jsxs(Box, { sx: { minWidth: 320 }, children: [_jsxs(Box, { sx: { alignItems: 'center', display: 'flex', justifyContent: 'space-between', px: 2, py: 1 }, children: [_jsx(Typography, { variant: "subtitle1", children: t('NotificationBell.TITLE') }), unreadIds.length > 0 && (_jsx(Button, { onClick: () => markSeen(unreadIds), size: "small", children: t('NotificationBell.MARK_ALL') }))] }), notifications.length === 0 ? (_jsx(Typography, { sx: { px: 2, py: 2 }, variant: "body2", children: t('NotificationBell.EMPTY') })) : (_jsx(List, { dense: true, disablePadding: true, children: notifications.map((notification) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
? t(notification.content.body_key, notification.content.params || {})
|
|
27
|
-
: undefined, slotProps: { primary: { fontWeight: notification.seen || notification.dismissed ? 'regular' : 'bold' } } }) }, (_d = notification.id) !== null && _d !== void 0 ? _d : notification.notification_id));
|
|
28
|
-
}) }))] }) })] }));
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsx(IconButton, { "aria-label": t('NotificationBell.TITLE'), color: "inherit", onClick: (event) => setAnchorEl(event.currentTarget), children: _jsx(Badge, { badgeContent: unreadCount, color: "error", max: 99, children: _jsx(NotificationsIcon, {}) }) }), _jsx(Menu, { anchorEl: anchorEl, anchorOrigin: { horizontal: 'right', vertical: 'bottom' }, onClose: () => setAnchorEl(null), open: open, transformOrigin: { horizontal: 'right', vertical: 'top' }, children: _jsxs(Box, { sx: { minWidth: 320 }, children: [_jsxs(Box, { sx: { alignItems: 'center', display: 'flex', justifyContent: 'space-between', px: 2, py: 1 }, children: [_jsx(Typography, { variant: "subtitle1", children: t('NotificationBell.TITLE') }), unreadIds.length > 0 && (_jsx(Button, { onClick: () => markSeen(unreadIds), size: "small", children: t('NotificationBell.MARK_ALL') }))] }), notifications.length === 0 ? (_jsx(Typography, { sx: { px: 2, py: 2 }, variant: "body2", children: t('NotificationBell.EMPTY') })) : (_jsx(List, { dense: true, disablePadding: true, children: notifications.map((notification) => { var _a, _b, _c; var _d; return (_jsx(ListItemButton, { onClick: () => handleItemClick(notification), children: _jsx(ListItemText, { primary: t((_a = notification.content) === null || _a === void 0 ? void 0 : _a.title_key, ((_b = notification.content) === null || _b === void 0 ? void 0 : _b.params) || {}), secondary: ((_c = notification.content) === null || _c === void 0 ? void 0 : _c.body_key)
|
|
24
|
+
? t(notification.content.body_key, notification.content.params || {})
|
|
25
|
+
: undefined, slotProps: { primary: { fontWeight: notification.seen || notification.dismissed ? 'regular' : 'bold' } } }) }, (_d = notification.id) !== null && _d !== void 0 ? _d : notification.notification_id)); }) }))] }) })] }));
|
|
29
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useEffect, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import Alert from '@mui/material/Alert';
|
|
@@ -57,12 +57,9 @@ export const UNIVERSAL_STEP_DESCRIPTORS = [
|
|
|
57
57
|
// verified their PWA manifest/icons are actually installable must not
|
|
58
58
|
// pass pwaInstallEnabled, or this would show an iOS "Add to Home Screen"
|
|
59
59
|
// hint for an app that isn't really installable.
|
|
60
|
-
condition: (ctx) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
&& !((_a = ctx.pwaInstall) === null || _a === void 0 ? void 0 : _a.isStandalone)
|
|
64
|
-
&& (Boolean((_b = ctx.pwaInstall) === null || _b === void 0 ? void 0 : _b.deferredPrompt) || Boolean((_c = ctx.pwaInstall) === null || _c === void 0 ? void 0 : _c.isIos)));
|
|
65
|
-
},
|
|
60
|
+
condition: (ctx) => { var _a, _b, _c; return Boolean(ctx.pwaInstallEnabled
|
|
61
|
+
&& !((_a = ctx.pwaInstall) === null || _a === void 0 ? void 0 : _a.isStandalone)
|
|
62
|
+
&& (Boolean((_b = ctx.pwaInstall) === null || _b === void 0 ? void 0 : _b.deferredPrompt) || Boolean((_c = ctx.pwaInstall) === null || _c === void 0 ? void 0 : _c.isIos))); },
|
|
66
63
|
blocking: false,
|
|
67
64
|
skipable: true,
|
|
68
65
|
persistDismissed: true,
|
package/dist/pages/LoginPage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useContext, useEffect } from 'react';
|
|
3
3
|
import { useNavigate, useLocation } from 'react-router-dom';
|
|
4
4
|
import { Helmet } from 'react-helmet';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// src/auth/pages/PasswordInvitePage.jsx
|
|
3
3
|
import React, { useState, useEffect } from 'react';
|
|
4
4
|
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
package/dist/pages/SignUpPage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { useNavigate, useLocation } from 'react-router-dom';
|
|
4
4
|
import { Alert, Box, Button, Stack, TextField, Typography, } from '@mui/material';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// src/pages/SignupConfirmPage.jsx
|
|
3
3
|
//
|
|
4
4
|
// S13: Completes a pending registration. Reads the signed pending-token from
|
|
@@ -266,7 +266,8 @@ function createPaletteAwareComponents(palette, isDark) {
|
|
|
266
266
|
* objects are created, so runtime app/site overrides cannot leave stale values.
|
|
267
267
|
*/
|
|
268
268
|
export function createAppTheme(appConfig = {}) {
|
|
269
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q
|
|
269
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
270
|
+
var _r, _s;
|
|
270
271
|
if (!((_a = appConfig === null || appConfig === void 0 ? void 0 : appConfig.palette) === null || _a === void 0 ? void 0 : _a.primary)) {
|
|
271
272
|
throw new TypeError('createAppTheme: appConfig.palette.primary is required.');
|
|
272
273
|
}
|
|
@@ -327,16 +328,16 @@ export function createAppTheme(appConfig = {}) {
|
|
|
327
328
|
? finalPalette.dataSeries.categorical.map((colour) => (deriveLightenedPageColour(colour, [finalPalette.background.paper], 3)))
|
|
328
329
|
: finalPalette.dataSeries.categorical;
|
|
329
330
|
const computedPalette = Object.assign(Object.assign({}, derivedStatusEntries), { controlBorder: Object.assign(Object.assign(Object.assign({}, finalPalette.controlBorder), derivedControlBorderEntries), { focus: isDark
|
|
330
|
-
? ((
|
|
331
|
+
? ((_r = (_m = (_l = appConfig.palette) === null || _l === void 0 ? void 0 : _l.controlBorder) === null || _m === void 0 ? void 0 : _m.focus) !== null && _r !== void 0 ? _r : deriveLightenedColour(primaryMain, contrastSurfaces))
|
|
331
332
|
: deriveFocusColour(finalPalette.primary.main, contrastSurfaces) }), primary: Object.assign(Object.assign(Object.assign({}, finalPalette.primary), (isDark && primaryMain !== finalPalette.primary.main
|
|
332
333
|
? withMainShades(primaryMain, primaryContrastText)
|
|
333
334
|
: { main: primaryMain })), { contrastText: primaryContrastText }), dataSeries: { categorical: derivedSeries } });
|
|
334
335
|
const resolvedPalette = Object.assign(Object.assign({}, finalPalette), computedPalette);
|
|
335
|
-
const fontFamily = ((
|
|
336
|
+
const fontFamily = ((_o = appConfig.typography) === null || _o === void 0 ? void 0 : _o.fontFamily) || BASELINE_STATIC.typography.fontFamily;
|
|
336
337
|
// The h4 step below `sm` follows the app's own `sm` if it overrides the breakpoint,
|
|
337
338
|
// so the step always ends where theme.breakpoints.down('sm') ends.
|
|
338
339
|
const _t = BASELINE_STATIC.typography.h4, _u = breakpointDownQuery(BREAKPOINT_SM), h4Mobile = _t[_u], h4Base = __rest(_t, [typeof _u === "symbol" ? _u : _u + ""]);
|
|
339
|
-
const smValue = (_s = (
|
|
340
|
+
const smValue = (_s = (_q = (_p = appConfig.breakpoints) === null || _p === void 0 ? void 0 : _p.values) === null || _q === void 0 ? void 0 : _q.sm) !== null && _s !== void 0 ? _s : BREAKPOINT_SM;
|
|
340
341
|
return createTheme(Object.assign(Object.assign({}, BASELINE_STATIC), { typography: Object.assign(Object.assign({}, BASELINE_STATIC.typography), { fontFamily, h4: Object.assign(Object.assign({}, h4Base), { [breakpointDownQuery(smValue)]: h4Mobile }) }), fontLoading: Object.assign(Object.assign({}, BASELINE_STATIC.fontLoading), { weights: [...BASELINE_STATIC.fontLoading.weights] }), shadow: isDark ? Object.assign(Object.assign({}, BASELINE_STATIC.shadow), { overlay: DARK_OVERLAY_SHADOW }) : BASELINE_STATIC.shadow, palette: resolvedPalette, themeCompleteness: {
|
|
341
342
|
baseline: true,
|
|
342
343
|
exemptions: (isDark
|