@burdenoff/fe-libs 2026.1029.8 → 2026.1029.10
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/shared/assistant/i18nManifest.d.ts +27 -0
- package/dist/shared/assistant/i18nManifest.js +27 -0
- package/dist/shared/assistant/index.d.ts +9 -0
- package/dist/shared/assistant/speech/speechFeed.d.ts +4 -3
- package/dist/shared/assistant/speech/speechFeed.js +3 -2
- package/dist/shared/assistant/ui/AIAssistantPanel.d.ts +11 -0
- package/dist/shared/assistant/ui/AIAssistantPanel.js +233 -127
- package/dist/shared/assistant/ui/AssistantChatArea.d.ts +13 -1
- package/dist/shared/assistant/ui/AssistantChatArea.js +1632 -1605
- package/dist/shared/assistant/ui/AssistantConversationList.d.ts +17 -1
- package/dist/shared/assistant/ui/AssistantConversationList.js +304 -274
- package/dist/shared/assistant/ui/AssistantOverlay.d.ts +11 -0
- package/dist/shared/assistant/ui/AssistantOverlay.js +106 -90
- package/dist/shared/assistant/ui/AssistantSpeechProvider.js +140 -137
- package/dist/shared/assistant/ui/closeGuard.d.ts +21 -0
- package/dist/shared/assistant/ui/closeGuard.js +8 -0
- package/dist/shared/assistant/ui/compact/AssistantCompactHeader.d.ts +38 -0
- package/dist/shared/assistant/ui/compact/AssistantCompactHeader.js +89 -0
- package/dist/shared/assistant/ui/compact/AssistantConversationMenu.d.ts +29 -0
- package/dist/shared/assistant/ui/compact/AssistantConversationMenu.js +87 -0
- package/dist/shared/assistant/ui/compact/AssistantDiscardDraftDialog.d.ts +19 -0
- package/dist/shared/assistant/ui/compact/AssistantDiscardDraftDialog.js +38 -0
- package/dist/shared/assistant/ui/compact/AssistantMenuView.d.ts +42 -0
- package/dist/shared/assistant/ui/compact/AssistantMenuView.js +285 -0
- package/dist/shared/assistant/ui/compact/AssistantVoiceLanguageSettings.d.ts +24 -0
- package/dist/shared/assistant/ui/compact/AssistantVoiceLanguageSettings.js +68 -0
- package/dist/shared/assistant/ui/compact/AssistantWaitingChangeRow.d.ts +21 -0
- package/dist/shared/assistant/ui/compact/AssistantWaitingChangeRow.js +34 -0
- package/dist/shared/assistant/ui/compact/compactChrome.d.ts +75 -0
- package/dist/shared/assistant/ui/compact/compactChrome.js +39 -0
- package/dist/shared/assistant/ui/compact/waitingChange.d.ts +49 -0
- package/dist/shared/assistant/ui/compact/waitingChange.js +12 -0
- package/dist/shared/assistant/ui/composer/AssistantComposer.d.ts +19 -18
- package/dist/shared/assistant/ui/composer/AssistantComposer.js +261 -224
- package/dist/shared/assistant/ui/useVoiceSession.js +13 -8
- package/dist/shared/assistant/useAssistantKeyboardShortcuts.d.ts +16 -2
- package/dist/shared/assistant/useAssistantKeyboardShortcuts.js +28 -16
- package/dist/shared-assistant.js +42 -33
- package/package.json +1 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useAssistantTr as e } from "../../i18n.js";
|
|
2
|
+
import { assistantPageLabelKey as t, translateProductCopy as n } from "../../productCopy.js";
|
|
3
|
+
import { useAssistantPreview as r } from "../../preview/context.js";
|
|
4
|
+
import { hasAssistantPreviewSurface as i } from "../../preview/types.js";
|
|
5
|
+
import { Eye as a, Maximize2 as o, Menu as s, Minimize2 as c, Sparkles as l, X as u } from "lucide-react";
|
|
6
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
7
|
+
//#region src/shared/assistant/ui/compact/AssistantCompactHeader.tsx
|
|
8
|
+
var p = "flex size-10 shrink-0 items-center justify-center rounded-button transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", m = "text-text-secondary hover:bg-bg-sunken hover:text-text-primary", h = "bg-action-primary-bg/15 text-action-primary-bg", g = ({ contextLabel: g, menuOpen: _, menuViewId: v, onOpenMenu: y, menuButtonRef: b, conversationMenu: x, expandOffered: S, expanded: C, onToggleExpanded: w, onTogglePreview: T, previewOpen: E, onClose: D }) => {
|
|
9
|
+
let O = e(), k = r();
|
|
10
|
+
return /* @__PURE__ */ f("div", {
|
|
11
|
+
className: "flex min-h-14 items-center gap-0.5 border-b border-border-default bg-bg-elevated px-2 py-2",
|
|
12
|
+
"data-testid": "assistant-header",
|
|
13
|
+
"data-presentation": "compact",
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ d("button", {
|
|
16
|
+
ref: b,
|
|
17
|
+
type: "button",
|
|
18
|
+
onClick: y,
|
|
19
|
+
className: `${p} ${m}`,
|
|
20
|
+
"aria-label": O("header.menu", "Assistant menu"),
|
|
21
|
+
"aria-expanded": _,
|
|
22
|
+
"aria-controls": _ ? v : void 0,
|
|
23
|
+
"data-testid": "assistant-menu",
|
|
24
|
+
children: /* @__PURE__ */ d(s, {
|
|
25
|
+
className: "size-5",
|
|
26
|
+
"aria-hidden": "true"
|
|
27
|
+
})
|
|
28
|
+
}),
|
|
29
|
+
/* @__PURE__ */ f("div", {
|
|
30
|
+
className: "flex min-w-0 flex-1 items-center gap-3 pl-1",
|
|
31
|
+
children: [/* @__PURE__ */ d("span", {
|
|
32
|
+
className: "flex size-7 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-action-primary-bg/25 to-action-primary-bg/5 text-action-primary-bg",
|
|
33
|
+
children: /* @__PURE__ */ d(l, {
|
|
34
|
+
className: "size-4",
|
|
35
|
+
"aria-hidden": "true"
|
|
36
|
+
})
|
|
37
|
+
}), /* @__PURE__ */ f("span", {
|
|
38
|
+
className: "min-w-0 flex-1",
|
|
39
|
+
children: [/* @__PURE__ */ d("span", {
|
|
40
|
+
className: "block truncate text-base font-semibold leading-tight text-text-primary",
|
|
41
|
+
children: O("header.title", "AI Assistant")
|
|
42
|
+
}), g ? /* @__PURE__ */ d("span", {
|
|
43
|
+
className: "mt-0.5 block truncate text-xs leading-tight text-text-muted",
|
|
44
|
+
children: O("header.context", "Context: {{page}}", { page: n(O, t(g), g) })
|
|
45
|
+
}) : null]
|
|
46
|
+
})]
|
|
47
|
+
}),
|
|
48
|
+
i(k) && T ? /* @__PURE__ */ d("button", {
|
|
49
|
+
type: "button",
|
|
50
|
+
onClick: T,
|
|
51
|
+
className: `${p} ${E ? h : m}`,
|
|
52
|
+
"aria-label": k.label ?? O("header.preview", "Preview"),
|
|
53
|
+
"aria-pressed": !!E,
|
|
54
|
+
"data-testid": "assistant-toggle-preview",
|
|
55
|
+
children: /* @__PURE__ */ d(a, {
|
|
56
|
+
className: "size-5",
|
|
57
|
+
"aria-hidden": "true"
|
|
58
|
+
})
|
|
59
|
+
}) : null,
|
|
60
|
+
x,
|
|
61
|
+
S ? /* @__PURE__ */ d("button", {
|
|
62
|
+
type: "button",
|
|
63
|
+
onClick: w,
|
|
64
|
+
className: `${p} ${C ? h : m}`,
|
|
65
|
+
"aria-label": O("header.expand", "Expand assistant"),
|
|
66
|
+
"aria-pressed": C,
|
|
67
|
+
title: C ? O("header.restoreSize", "Restore size") : O("header.expandTip", "Expand"),
|
|
68
|
+
"data-testid": "assistant-expand",
|
|
69
|
+
children: d(C ? c : o, {
|
|
70
|
+
className: "size-5",
|
|
71
|
+
"aria-hidden": "true"
|
|
72
|
+
})
|
|
73
|
+
}) : null,
|
|
74
|
+
/* @__PURE__ */ d("button", {
|
|
75
|
+
type: "button",
|
|
76
|
+
onClick: D,
|
|
77
|
+
className: `${p} ${m}`,
|
|
78
|
+
"aria-label": O("header.close", "Close assistant"),
|
|
79
|
+
"data-testid": "assistant-close",
|
|
80
|
+
children: /* @__PURE__ */ d(u, {
|
|
81
|
+
className: "size-5",
|
|
82
|
+
"aria-hidden": "true"
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
//#endregion
|
|
89
|
+
export { g as AssistantCompactHeader };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FC, Ref } from 'react';
|
|
2
|
+
export interface AssistantConversationMenuProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
triggerRef?: Ref<HTMLButtonElement>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The ⋯ conversation menu of the compact header (F3): what can be done with
|
|
9
|
+
* THIS conversation, as a real menu. Export as Markdown, Export as JSON (once
|
|
10
|
+
* the conversation has messages) and Voice transcript (once something was
|
|
11
|
+
* spoken in it).
|
|
12
|
+
*
|
|
13
|
+
* ★ A Radix menu, so arrow keys move, Enter and Space choose, Escape closes it
|
|
14
|
+
* and returns focus to ⋯, and it takes the key before anything below it. The
|
|
15
|
+
* classic ⋯ was a `role="menu"` that held selects and a checkbox (F-05): those
|
|
16
|
+
* settings live in ☰ → Voice & language now, and nothing is in two menus.
|
|
17
|
+
*
|
|
18
|
+
* ★ Controlled (`open`), because Ctrl/Cmd+Shift+E opens it from the keyboard
|
|
19
|
+
* and a programmatic click does not open a Radix trigger. It keeps
|
|
20
|
+
* `data-testid="assistant-more"`, which that shortcut and the doctor suites
|
|
21
|
+
* look for.
|
|
22
|
+
*
|
|
23
|
+
* ★ The actions are the classic ones: `exportConversation` + `downloadFile`
|
|
24
|
+
* (the same projection of what the thread shows, spoken turns labelled), and
|
|
25
|
+
* the transcript opens through the `assistant.transcript` control the chat
|
|
26
|
+
* area registers, the one a typed or spoken "show the transcript" runs, which
|
|
27
|
+
* stays unregistered while a voice session or the Avatar tab is on.
|
|
28
|
+
*/
|
|
29
|
+
export declare const AssistantConversationMenu: FC<AssistantConversationMenuProps>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DropdownMenu as e, DropdownMenuContent as t, DropdownMenuItem as n, DropdownMenuLabel as r, DropdownMenuTrigger as i } from "../../../../ui/dropdown-menu.js";
|
|
2
|
+
import { focusWasDropped as a } from "../../../hooks/useReturnFocus.js";
|
|
3
|
+
import { getAssistantControl as o, hasAssistantControl as s, subscribeAssistantControls as c } from "../../appControls/registry.js";
|
|
4
|
+
import { useAssistantTr as l } from "../../i18n.js";
|
|
5
|
+
import { useAssistantStore as u } from "../../store.js";
|
|
6
|
+
import { voiceTurnsOnly as d } from "../../voice/turnVisibility.js";
|
|
7
|
+
import { downloadFile as f, exportConversation as p } from "../../exportConversation.js";
|
|
8
|
+
import { useCallback as m, useSyncExternalStore as h } from "react";
|
|
9
|
+
import { FileJson as g, FileText as _, Mic as v, MoreHorizontal as y } from "lucide-react";
|
|
10
|
+
import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
11
|
+
//#region src/shared/assistant/ui/compact/AssistantConversationMenu.tsx
|
|
12
|
+
var C = "min-h-11 gap-2.5 px-3", w = ({ open: w, onOpenChange: T, triggerRef: E }) => {
|
|
13
|
+
let D = l(), { conversations: O, activeConversationId: k, processingByConversationId: A } = u(), j = O.find((e) => e.id === k), M = !!(j && j.messages.length > 0), N = !!(h(c, () => s("assistant.transcript"), () => !1) && j && d(j.messages).length > 0), P = (e) => {
|
|
14
|
+
if (!j) return;
|
|
15
|
+
let { content: t, filename: n, mimeType: r } = p(j, e, {
|
|
16
|
+
processing: A[j.id],
|
|
17
|
+
tr: D
|
|
18
|
+
});
|
|
19
|
+
f(t, n, r);
|
|
20
|
+
}, F = () => {
|
|
21
|
+
o("assistant.transcript")?.run();
|
|
22
|
+
}, I = m((e) => {
|
|
23
|
+
a(document) || e.preventDefault();
|
|
24
|
+
}, []), L = D("header.conversationOptions", "Conversation options");
|
|
25
|
+
return /* @__PURE__ */ S(e, {
|
|
26
|
+
open: w,
|
|
27
|
+
onOpenChange: T,
|
|
28
|
+
modal: !1,
|
|
29
|
+
children: [/* @__PURE__ */ x(i, {
|
|
30
|
+
asChild: !0,
|
|
31
|
+
children: /* @__PURE__ */ x("button", {
|
|
32
|
+
ref: E,
|
|
33
|
+
type: "button",
|
|
34
|
+
"aria-label": L,
|
|
35
|
+
title: L,
|
|
36
|
+
className: "flex size-10 shrink-0 items-center justify-center rounded-button text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
37
|
+
"data-testid": "assistant-more",
|
|
38
|
+
children: /* @__PURE__ */ x(y, {
|
|
39
|
+
className: "size-5",
|
|
40
|
+
"aria-hidden": "true"
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
}), /* @__PURE__ */ S(t, {
|
|
44
|
+
align: "end",
|
|
45
|
+
sideOffset: 4,
|
|
46
|
+
collisionPadding: 8,
|
|
47
|
+
onCloseAutoFocus: I,
|
|
48
|
+
className: "min-w-56 max-w-[calc(100vw-2rem)] max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto",
|
|
49
|
+
"data-testid": "assistant-more-menu",
|
|
50
|
+
children: [
|
|
51
|
+
M ? /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ S(n, {
|
|
52
|
+
className: C,
|
|
53
|
+
onSelect: () => P("markdown"),
|
|
54
|
+
"data-testid": "export-markdown",
|
|
55
|
+
children: [/* @__PURE__ */ x(_, {
|
|
56
|
+
className: "size-4 shrink-0 text-text-secondary",
|
|
57
|
+
"aria-hidden": "true"
|
|
58
|
+
}), D("export.markdown", "Export as Markdown")]
|
|
59
|
+
}), /* @__PURE__ */ S(n, {
|
|
60
|
+
className: C,
|
|
61
|
+
onSelect: () => P("json"),
|
|
62
|
+
"data-testid": "export-json",
|
|
63
|
+
children: [/* @__PURE__ */ x(g, {
|
|
64
|
+
className: "size-4 shrink-0 text-text-secondary",
|
|
65
|
+
"aria-hidden": "true"
|
|
66
|
+
}), D("export.json", "Export as JSON")]
|
|
67
|
+
})] }) : null,
|
|
68
|
+
N ? /* @__PURE__ */ S(n, {
|
|
69
|
+
className: C,
|
|
70
|
+
onSelect: F,
|
|
71
|
+
"data-testid": "assistant-more-voice-transcript",
|
|
72
|
+
children: [/* @__PURE__ */ x(v, {
|
|
73
|
+
className: "size-4 shrink-0 text-text-secondary",
|
|
74
|
+
"aria-hidden": "true"
|
|
75
|
+
}), D("conversationMenu.voiceTranscript", "Voice transcript")]
|
|
76
|
+
}) : null,
|
|
77
|
+
!M && !N ? /* @__PURE__ */ x(r, {
|
|
78
|
+
className: "px-3 py-2 text-sm font-normal text-text-secondary",
|
|
79
|
+
"data-testid": "assistant-more-empty",
|
|
80
|
+
children: D("conversationMenu.empty", "Nothing to export yet")
|
|
81
|
+
}) : null
|
|
82
|
+
]
|
|
83
|
+
})]
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
export { w as AssistantConversationMenu };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
export interface AssistantDiscardDraftDialogProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
/** Keep writing: nothing is closed, nothing is lost. Escape and the scrim mean this too. */
|
|
5
|
+
onKeep: () => void;
|
|
6
|
+
/** Discard what was typed or attached, and close the assistant. */
|
|
7
|
+
onDiscard: () => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* "Discard your unsent message?" (N1, 2026-10-29): what Close asks, in the
|
|
11
|
+
* compact presentation only, while something typed or attached has not been
|
|
12
|
+
* sent. Closing unmounts the composer, so without this the words and the files
|
|
13
|
+
* were simply gone.
|
|
14
|
+
*
|
|
15
|
+
* An alert dialog: it interrupts to ask for a decision. Focus starts on Keep
|
|
16
|
+
* writing (the safe answer is the one Enter gives), and Escape, the close
|
|
17
|
+
* button and the scrim all mean Keep writing, which changes nothing.
|
|
18
|
+
*/
|
|
19
|
+
export declare const AssistantDiscardDraftDialog: FC<AssistantDiscardDraftDialogProps>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Button as e } from "../../../../ui/button.js";
|
|
2
|
+
import { ModalShell as t } from "../../../../ui/modal-shell.js";
|
|
3
|
+
import { useAssistantTr as n } from "../../i18n.js";
|
|
4
|
+
import { useRef as r } from "react";
|
|
5
|
+
import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
|
+
//#region src/shared/assistant/ui/compact/AssistantDiscardDraftDialog.tsx
|
|
7
|
+
var s = ({ open: s, onKeep: c, onDiscard: l }) => {
|
|
8
|
+
let u = n(), d = r(null);
|
|
9
|
+
return /* @__PURE__ */ a(t, {
|
|
10
|
+
open: s,
|
|
11
|
+
onClose: c,
|
|
12
|
+
size: "sm",
|
|
13
|
+
role: "alertdialog",
|
|
14
|
+
testId: "assistant-discard-draft-dialog",
|
|
15
|
+
onOpenAutoFocus: (e) => {
|
|
16
|
+
e.preventDefault(), d.current?.focus();
|
|
17
|
+
},
|
|
18
|
+
title: u("closeConfirm.title", "Discard your unsent message?"),
|
|
19
|
+
footer: /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ a(e, {
|
|
20
|
+
ref: d,
|
|
21
|
+
variant: "outline",
|
|
22
|
+
onClick: c,
|
|
23
|
+
"data-testid": "assistant-discard-draft-keep",
|
|
24
|
+
children: u("closeConfirm.keep", "Keep writing")
|
|
25
|
+
}), /* @__PURE__ */ a(e, {
|
|
26
|
+
variant: "default",
|
|
27
|
+
onClick: l,
|
|
28
|
+
"data-testid": "assistant-discard-draft-confirm",
|
|
29
|
+
children: u("closeConfirm.discard", "Discard and close")
|
|
30
|
+
})] }),
|
|
31
|
+
children: /* @__PURE__ */ a("p", {
|
|
32
|
+
className: "text-sm text-text-secondary",
|
|
33
|
+
children: u("closeConfirm.body", "What you typed or attached has not been sent. If you close the assistant now, it is discarded.")
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { s as AssistantDiscardDraftDialog };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
export interface AssistantMenuViewProps {
|
|
3
|
+
/** The element id `aria-controls` on ☰ points at. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Back to the conversation (Back, or Escape from the list). */
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
/** The × of the view: closes the assistant, through the panel's close request (N1). */
|
|
8
|
+
onRequestClosePanel: () => void;
|
|
9
|
+
/** New conversation: the panel creates it, closes this view and focuses the message box. */
|
|
10
|
+
onNewConversation: () => void;
|
|
11
|
+
/** Absent: the live-avatar tab is not offered here, and the Avatar row is not shown. */
|
|
12
|
+
onOpenAvatar?: () => void;
|
|
13
|
+
docked?: boolean;
|
|
14
|
+
/** Absent: the product shows no dock control, and Panel layout is not shown. */
|
|
15
|
+
onToggleDock?: () => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The ☰ view (BOFF-10177, F2): what the compact header's ☰ opens, over the
|
|
19
|
+
* panel, in place of the classic header's New and History buttons and the ⋯
|
|
20
|
+
* menu's settings.
|
|
21
|
+
*
|
|
22
|
+
* - Back to conversation, and × (which closes the assistant);
|
|
23
|
+
* - New conversation (Ctrl/Cmd+Shift+N);
|
|
24
|
+
* - Recent: the newest conversations, and View all history, which opens the
|
|
25
|
+
* EXISTING history list (search, All/Chat/Voice, Pinned, Archived, groups,
|
|
26
|
+
* row actions, delete confirmation) as a sub-view;
|
|
27
|
+
* - Settings: Voice & language (a sub-view holding the six controls), Avatar
|
|
28
|
+
* (a row that opens the Avatar tab; nothing of the avatar is duplicated
|
|
29
|
+
* here) and Panel layout (Dock/Float, D13: its only home).
|
|
30
|
+
*
|
|
31
|
+
* ★ Every screen has an `h2` and a Back; focus lands on Back on open, on the
|
|
32
|
+
* sub-view's Back on entering one, and on the row that opened it on the way
|
|
33
|
+
* back. Escape closes ONE layer: a sub-view returns to this list, and this list
|
|
34
|
+
* returns to the conversation (`menuViewEscape`). The view is ONE `EscapeLayer`
|
|
35
|
+
* that decides, so it can neither double-close nor skip a screen, and a menu or
|
|
36
|
+
* dialog opened over it (mounted later) takes the key first.
|
|
37
|
+
*
|
|
38
|
+
* ★ The conversation under it stays mounted and `inert` (the panel sets that,
|
|
39
|
+
* as for the History drawer), so the draft, a running reply and a waiting
|
|
40
|
+
* change are exactly as they were.
|
|
41
|
+
*/
|
|
42
|
+
export declare const AssistantMenuView: FC<AssistantMenuViewProps>;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { useI18n as e } from "../../../providers/shell/I18nProvider.js";
|
|
2
|
+
import { EscapeLayer as t } from "../../../components/EscapeLayer.js";
|
|
3
|
+
import { useAssistantProduct as n } from "../../product.js";
|
|
4
|
+
import { useAssistantTr as r } from "../../i18n.js";
|
|
5
|
+
import { useAssistantStore as i } from "../../store.js";
|
|
6
|
+
import { resolveLanguagePair as a } from "../../voice/languagePair.js";
|
|
7
|
+
import { useOverlayFocus as o } from "../useOverlayFocus.js";
|
|
8
|
+
import { assistantModeOptionName as s } from "../composer/AssistantModeMenu.js";
|
|
9
|
+
import { useAssistantHistoryStatus as c } from "../../history/historyStatus.js";
|
|
10
|
+
import { assistantHistory as l } from "../../history/historyEngine.js";
|
|
11
|
+
import { AssistantConversationList as u } from "../AssistantConversationList.js";
|
|
12
|
+
import { fieldOwnsEscape as ee, menuViewEscape as te, recentConversations as ne } from "./compactChrome.js";
|
|
13
|
+
import { AssistantVoiceLanguageSettings as re } from "./AssistantVoiceLanguageSettings.js";
|
|
14
|
+
import { useEffect as d, useLayoutEffect as f, useMemo as p, useRef as m, useState as h } from "react";
|
|
15
|
+
import { ArrowLeft as g, ChevronRight as _, History as ie, Languages as ae, PanelRight as oe, Plus as se, UserRound as v, X as y } from "lucide-react";
|
|
16
|
+
import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
17
|
+
//#region src/shared/assistant/ui/compact/AssistantMenuView.tsx
|
|
18
|
+
var C = "focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", w = `flex size-10 shrink-0 items-center justify-center rounded-button text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary ${C}`, T = `flex min-h-11 w-full items-center gap-3 px-4 py-2 text-left text-sm text-text-primary transition-colors hover:bg-bg-sunken ${C} focus-visible:ring-inset`, ce = typeof document > "u" ? d : f, E = ({ icon: e, label: t, trailing: n, onClick: r, buttonRef: i, testId: a, describedBy: o }) => /* @__PURE__ */ S("button", {
|
|
19
|
+
ref: i,
|
|
20
|
+
type: "button",
|
|
21
|
+
onClick: r,
|
|
22
|
+
className: T,
|
|
23
|
+
"aria-describedby": o,
|
|
24
|
+
"data-testid": a,
|
|
25
|
+
children: [
|
|
26
|
+
/* @__PURE__ */ x("span", {
|
|
27
|
+
className: "flex size-5 shrink-0 items-center justify-center text-text-secondary",
|
|
28
|
+
children: e
|
|
29
|
+
}),
|
|
30
|
+
/* @__PURE__ */ x("span", {
|
|
31
|
+
className: "min-w-0 flex-1 truncate",
|
|
32
|
+
children: t
|
|
33
|
+
}),
|
|
34
|
+
n
|
|
35
|
+
]
|
|
36
|
+
}), D = ({ id: e, children: t }) => /* @__PURE__ */ x("h3", {
|
|
37
|
+
id: e,
|
|
38
|
+
className: "px-4 pb-1 pt-4 text-xs font-semibold uppercase tracking-wide text-text-muted",
|
|
39
|
+
children: t
|
|
40
|
+
}), le = ({ title: e, tr: t, onBack: n, backRef: r }) => /* @__PURE__ */ S("div", {
|
|
41
|
+
className: "flex items-center gap-1 border-b border-border-default bg-bg-elevated px-2 py-2",
|
|
42
|
+
children: [/* @__PURE__ */ x("button", {
|
|
43
|
+
ref: r,
|
|
44
|
+
type: "button",
|
|
45
|
+
onClick: n,
|
|
46
|
+
className: w,
|
|
47
|
+
"aria-label": t("menuView.back", "Back"),
|
|
48
|
+
"data-testid": "assistant-menu-subview-back",
|
|
49
|
+
children: /* @__PURE__ */ x(g, {
|
|
50
|
+
className: "size-5",
|
|
51
|
+
"aria-hidden": "true"
|
|
52
|
+
})
|
|
53
|
+
}), /* @__PURE__ */ x("h2", {
|
|
54
|
+
className: "min-w-0 flex-1 truncate text-base font-semibold text-text-primary",
|
|
55
|
+
children: e
|
|
56
|
+
})]
|
|
57
|
+
}), O = ({ id: d, onClose: f, onRequestClosePanel: O, onNewConversation: ue, onOpenAvatar: k, docked: A, onToggleDock: j }) => {
|
|
58
|
+
let M = r(), { locale: N } = e(), { modes: P } = n(), { conversations: F, activeConversationId: I, assistantLanguage: L, replyLanguage: R, setActiveConversation: z, setMode: B } = i(), V = c((e) => e.product !== null), [H, U] = h("root"), de = o("assistant-menu"), W = m(null), G = m(null), K = m(null), q = m(null), J = m("back"), Y = (e) => {
|
|
59
|
+
J.current = e, U(e);
|
|
60
|
+
}, X = () => U("root");
|
|
61
|
+
ce(() => {
|
|
62
|
+
if (H !== "history") {
|
|
63
|
+
if (H === "voice-language") {
|
|
64
|
+
G.current?.focus({ preventScroll: !0 });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
((J.current === "voice-language" ? K.current : J.current === "history" ? q.current : null) ?? W.current)?.focus({ preventScroll: !0 }), J.current = "back";
|
|
68
|
+
}
|
|
69
|
+
}, [H]);
|
|
70
|
+
let Z = p(() => ne(F), [F]), fe = M("history.untitled", "Untitled conversation"), pe = (e) => {
|
|
71
|
+
if (V) l.openConversation(e, { modes: P });
|
|
72
|
+
else {
|
|
73
|
+
let t = F.find((t) => t.id === e);
|
|
74
|
+
z(e), t && B(t.mode);
|
|
75
|
+
}
|
|
76
|
+
f();
|
|
77
|
+
}, me = a(L, R, N).input.label, Q = `${d}-settings`, $ = `${d}-recent`;
|
|
78
|
+
return /* @__PURE__ */ x(t, {
|
|
79
|
+
onDismiss: () => {
|
|
80
|
+
H === "history" && ee(document.activeElement) || (te(H) === "back" ? X() : f());
|
|
81
|
+
},
|
|
82
|
+
children: /* @__PURE__ */ x("div", {
|
|
83
|
+
ref: de,
|
|
84
|
+
id: d,
|
|
85
|
+
tabIndex: -1,
|
|
86
|
+
role: "region",
|
|
87
|
+
"aria-label": M("menuView.title", "Assistant menu"),
|
|
88
|
+
className: "absolute inset-0 z-10 flex flex-col bg-bg-surface outline-none animate-in slide-in-from-left-2 fade-in duration-200 motion-reduce:animate-none",
|
|
89
|
+
"data-testid": "assistant-menu-view",
|
|
90
|
+
"data-menu-view": H,
|
|
91
|
+
children: H === "root" ? /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ S("div", {
|
|
92
|
+
className: "flex items-center gap-1 border-b border-border-default bg-bg-elevated px-2 py-2",
|
|
93
|
+
children: [
|
|
94
|
+
/* @__PURE__ */ S("button", {
|
|
95
|
+
ref: W,
|
|
96
|
+
type: "button",
|
|
97
|
+
onClick: f,
|
|
98
|
+
className: `flex min-h-10 items-center gap-1.5 rounded-button px-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken ${C}`,
|
|
99
|
+
"data-testid": "assistant-menu-back",
|
|
100
|
+
children: [/* @__PURE__ */ x(g, {
|
|
101
|
+
className: "size-5",
|
|
102
|
+
"aria-hidden": "true"
|
|
103
|
+
}), M("menuView.backToConversation", "Back to conversation")]
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ x("h2", {
|
|
106
|
+
className: "sr-only",
|
|
107
|
+
children: M("menuView.title", "Assistant menu")
|
|
108
|
+
}),
|
|
109
|
+
/* @__PURE__ */ x("span", { className: "flex-1" }),
|
|
110
|
+
/* @__PURE__ */ x("button", {
|
|
111
|
+
type: "button",
|
|
112
|
+
onClick: O,
|
|
113
|
+
className: w,
|
|
114
|
+
"aria-label": M("header.close", "Close assistant"),
|
|
115
|
+
"data-testid": "assistant-menu-close",
|
|
116
|
+
children: /* @__PURE__ */ x(y, {
|
|
117
|
+
className: "size-5",
|
|
118
|
+
"aria-hidden": "true"
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
]
|
|
122
|
+
}), /* @__PURE__ */ S("div", {
|
|
123
|
+
className: "scrollbar-slim min-h-0 flex-1 overflow-y-auto pb-2",
|
|
124
|
+
children: [
|
|
125
|
+
/* @__PURE__ */ S("button", {
|
|
126
|
+
type: "button",
|
|
127
|
+
onClick: ue,
|
|
128
|
+
className: `${T} mt-2`,
|
|
129
|
+
"aria-keyshortcuts": "Control+Shift+N Meta+Shift+N",
|
|
130
|
+
"data-testid": "assistant-new-conversation",
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ x("span", {
|
|
133
|
+
className: "flex size-5 shrink-0 items-center justify-center text-text-secondary",
|
|
134
|
+
children: /* @__PURE__ */ x(se, {
|
|
135
|
+
className: "size-5",
|
|
136
|
+
"aria-hidden": "true"
|
|
137
|
+
})
|
|
138
|
+
}),
|
|
139
|
+
/* @__PURE__ */ x("span", {
|
|
140
|
+
className: "min-w-0 flex-1 truncate",
|
|
141
|
+
children: M("header.newConversation", "New conversation")
|
|
142
|
+
}),
|
|
143
|
+
/* @__PURE__ */ x("kbd", {
|
|
144
|
+
className: "shrink-0 text-xs text-text-muted",
|
|
145
|
+
children: "Ctrl+Shift+N"
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
}),
|
|
149
|
+
/* @__PURE__ */ S("section", {
|
|
150
|
+
"aria-labelledby": $,
|
|
151
|
+
"data-testid": "assistant-menu-recent",
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ x(D, {
|
|
154
|
+
id: $,
|
|
155
|
+
children: M("menuView.recent", "Recent")
|
|
156
|
+
}),
|
|
157
|
+
Z.length === 0 ? /* @__PURE__ */ x("p", {
|
|
158
|
+
className: "px-4 py-2 text-sm text-text-muted",
|
|
159
|
+
children: M("conversations.empty", "No conversations yet")
|
|
160
|
+
}) : /* @__PURE__ */ x("ul", { children: Z.map((e) => {
|
|
161
|
+
let t = P.find((t) => t.id === e.mode);
|
|
162
|
+
return /* @__PURE__ */ x("li", { children: /* @__PURE__ */ x("button", {
|
|
163
|
+
type: "button",
|
|
164
|
+
onClick: () => pe(e.id),
|
|
165
|
+
className: `${T} ${e.id === I ? "bg-bg-elevated" : ""}`,
|
|
166
|
+
"aria-current": e.id === I ? "true" : void 0,
|
|
167
|
+
"data-testid": "assistant-menu-recent-row",
|
|
168
|
+
children: /* @__PURE__ */ S("span", {
|
|
169
|
+
className: "min-w-0 flex-1",
|
|
170
|
+
children: [/* @__PURE__ */ x("span", {
|
|
171
|
+
className: "block truncate",
|
|
172
|
+
children: e.title || fe
|
|
173
|
+
}), t ? /* @__PURE__ */ x("span", {
|
|
174
|
+
className: "block truncate text-xs text-text-muted",
|
|
175
|
+
children: s(M, t)
|
|
176
|
+
}) : null]
|
|
177
|
+
})
|
|
178
|
+
}) }, e.id);
|
|
179
|
+
}) }),
|
|
180
|
+
/* @__PURE__ */ x(E, {
|
|
181
|
+
buttonRef: q,
|
|
182
|
+
icon: /* @__PURE__ */ x(ie, {
|
|
183
|
+
className: "size-5",
|
|
184
|
+
"aria-hidden": "true"
|
|
185
|
+
}),
|
|
186
|
+
label: M("menuView.viewAllHistory", "View all history"),
|
|
187
|
+
trailing: /* @__PURE__ */ x(_, {
|
|
188
|
+
className: "size-4 text-text-muted",
|
|
189
|
+
"aria-hidden": "true"
|
|
190
|
+
}),
|
|
191
|
+
onClick: () => Y("history"),
|
|
192
|
+
testId: "assistant-open-history"
|
|
193
|
+
})
|
|
194
|
+
]
|
|
195
|
+
}),
|
|
196
|
+
/* @__PURE__ */ S("section", {
|
|
197
|
+
"aria-labelledby": Q,
|
|
198
|
+
"data-testid": "assistant-menu-settings",
|
|
199
|
+
children: [
|
|
200
|
+
/* @__PURE__ */ x(D, {
|
|
201
|
+
id: Q,
|
|
202
|
+
children: M("menuView.settings", "Settings")
|
|
203
|
+
}),
|
|
204
|
+
/* @__PURE__ */ x(E, {
|
|
205
|
+
buttonRef: K,
|
|
206
|
+
icon: /* @__PURE__ */ x(ae, {
|
|
207
|
+
className: "size-5",
|
|
208
|
+
"aria-hidden": "true"
|
|
209
|
+
}),
|
|
210
|
+
label: M("menuView.voiceLanguage", "Voice & language"),
|
|
211
|
+
trailing: /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ x("span", {
|
|
212
|
+
className: "max-w-[40%] truncate text-xs text-text-muted",
|
|
213
|
+
children: me
|
|
214
|
+
}), /* @__PURE__ */ x(_, {
|
|
215
|
+
className: "size-4 shrink-0 text-text-muted",
|
|
216
|
+
"aria-hidden": "true"
|
|
217
|
+
})] }),
|
|
218
|
+
onClick: () => Y("voice-language"),
|
|
219
|
+
testId: "assistant-menu-voice-language-row"
|
|
220
|
+
}),
|
|
221
|
+
k ? /* @__PURE__ */ x(E, {
|
|
222
|
+
icon: /* @__PURE__ */ x(v, {
|
|
223
|
+
className: "size-5",
|
|
224
|
+
"aria-hidden": "true"
|
|
225
|
+
}),
|
|
226
|
+
label: M("menuView.avatar", "Avatar"),
|
|
227
|
+
trailing: /* @__PURE__ */ x(_, {
|
|
228
|
+
className: "size-4 text-text-muted",
|
|
229
|
+
"aria-hidden": "true"
|
|
230
|
+
}),
|
|
231
|
+
onClick: k,
|
|
232
|
+
testId: "assistant-menu-avatar-row"
|
|
233
|
+
}) : null,
|
|
234
|
+
j ? /* @__PURE__ */ S("fieldset", {
|
|
235
|
+
className: "hidden min-w-0 border-0 px-4 py-2 sm:block",
|
|
236
|
+
"data-testid": "assistant-menu-panel-layout",
|
|
237
|
+
children: [/* @__PURE__ */ S("legend", {
|
|
238
|
+
className: "flex items-center gap-3 py-1 text-sm text-text-primary",
|
|
239
|
+
children: [/* @__PURE__ */ x(oe, {
|
|
240
|
+
className: "size-5 shrink-0 text-text-secondary",
|
|
241
|
+
"aria-hidden": "true"
|
|
242
|
+
}), M("menuView.panelLayout", "Panel layout")]
|
|
243
|
+
}), [[
|
|
244
|
+
!0,
|
|
245
|
+
M("menuView.layoutDocked", "Docked side panel"),
|
|
246
|
+
"docked"
|
|
247
|
+
], [
|
|
248
|
+
!1,
|
|
249
|
+
M("menuView.layoutFloating", "Floating window"),
|
|
250
|
+
"floating"
|
|
251
|
+
]].map(([e, t, n]) => /* @__PURE__ */ S("label", {
|
|
252
|
+
className: "flex min-h-11 cursor-pointer items-center gap-3 pl-8 text-sm text-text-primary focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)]",
|
|
253
|
+
children: [/* @__PURE__ */ x("input", {
|
|
254
|
+
type: "radio",
|
|
255
|
+
name: `${d}-layout`,
|
|
256
|
+
checked: !!A === e,
|
|
257
|
+
onChange: () => {
|
|
258
|
+
!!A !== e && j();
|
|
259
|
+
},
|
|
260
|
+
className: "size-4 accent-current",
|
|
261
|
+
"data-testid": `assistant-panel-layout-${n}`
|
|
262
|
+
}), t]
|
|
263
|
+
}, n))]
|
|
264
|
+
}) : null
|
|
265
|
+
]
|
|
266
|
+
})
|
|
267
|
+
]
|
|
268
|
+
})] }) : H === "voice-language" ? /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ x(le, {
|
|
269
|
+
title: M("menuView.voiceLanguage", "Voice & language"),
|
|
270
|
+
tr: M,
|
|
271
|
+
onBack: X,
|
|
272
|
+
backRef: G
|
|
273
|
+
}), /* @__PURE__ */ x("div", {
|
|
274
|
+
className: "scrollbar-slim min-h-0 flex-1 overflow-y-auto",
|
|
275
|
+
children: /* @__PURE__ */ x(re, {})
|
|
276
|
+
})] }) : /* @__PURE__ */ x(u, {
|
|
277
|
+
presentation: "compact",
|
|
278
|
+
onBack: X,
|
|
279
|
+
onDone: f
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
//#endregion
|
|
285
|
+
export { O as AssistantMenuView };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* ☰ → Settings → Voice & language: the six controls the classic ⋯ menu held
|
|
4
|
+
* (BOFF-10177, DESIGN-DELTA A4), the SAME components under the SAME render
|
|
5
|
+
* conditions, so a value read or written here is the one the ⋯ menu would have
|
|
6
|
+
* read or written: nothing is stored anew.
|
|
7
|
+
*
|
|
8
|
+
* 1 Language you speak `AssistantLanguageMenu`
|
|
9
|
+
* 2 Assistant replies in `AssistantLanguageMenu`
|
|
10
|
+
* 3 Read replies aloud below; needs speech output
|
|
11
|
+
* 4 Voice + Preview voice picker needs 2 or more voices; Preview a resolved voice
|
|
12
|
+
* 5 Wait after I stop talking needs speech recognition
|
|
13
|
+
* 6 Hands-free interrupt (beta) needs speech recognition or microphone capture
|
|
14
|
+
*
|
|
15
|
+
* ★ Read replies aloud is a real checkbox in its label, like the hands-free
|
|
16
|
+
* toggle beside it: the visible words are its name (WCAG 2.5.3). Blocked and
|
|
17
|
+
* off, it is `aria-disabled` (not `disabled`) and says why on screen, so a
|
|
18
|
+
* keyboard or screen-reader user finds it and hears the reason, as the classic
|
|
19
|
+
* row's tooltip told a mouse. Turning it OFF also silences what is speaking.
|
|
20
|
+
*
|
|
21
|
+
* ★ None of these touches the interface language: they set the assistant's
|
|
22
|
+
* own language and voice.
|
|
23
|
+
*/
|
|
24
|
+
export declare const AssistantVoiceLanguageSettings: FC;
|