@burdenoff/fe-libs 2026.1029.7 → 2026.1029.9
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/appControls/grammar.js +21 -21
- package/dist/shared/assistant/appControls/registry.js +1 -1
- package/dist/shared/assistant/commandCompletions.js +87 -86
- 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/localUiCommands.js +106 -112
- package/dist/shared/assistant/localeCommands/languageNames.js +13 -13
- 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/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/useAssistantKeyboardShortcuts.d.ts +16 -2
- package/dist/shared/assistant/useAssistantKeyboardShortcuts.js +28 -16
- package/dist/shared-assistant.js +51 -42
- package/package.json +1 -1
|
@@ -18,6 +18,17 @@ export interface AssistantOverlayContextValue {
|
|
|
18
18
|
*/
|
|
19
19
|
panelInsetPx: number;
|
|
20
20
|
keys: AssistantOverlayKeys;
|
|
21
|
+
/**
|
|
22
|
+
* BOFF-10177 (N1): let the open panel ask before it is closed. The guard runs
|
|
23
|
+
* for every request to CLOSE from outside the panel (the launcher, the
|
|
24
|
+
* shortcut, the toggle event, Escape with focus outside it, `close()`), and
|
|
25
|
+
* returns true when it took the request, which it then answers itself, by
|
|
26
|
+
* asking the person and calling `close()` again once they have said yes.
|
|
27
|
+
* Returns an unregister function. Only the compact panel registers one, so
|
|
28
|
+
* with none, every path below closes exactly as before. Optional so a host
|
|
29
|
+
* that builds this value itself keeps compiling.
|
|
30
|
+
*/
|
|
31
|
+
registerCloseGuard?: (guard: () => boolean) => () => void;
|
|
21
32
|
}
|
|
22
33
|
/** The overlay state in force, or null when no provider is mounted. */
|
|
23
34
|
export declare function useAssistantOverlayOrNull(): AssistantOverlayContextValue | null;
|
|
@@ -4,134 +4,147 @@ import { assistantPanelOwnsEscape as n } from "./panelOverlays.js";
|
|
|
4
4
|
import { AssistantPreviewBridgeHost as r } from "../preview/bridgeHost.js";
|
|
5
5
|
import { ASSISTANT_DOCK_WIDTH_VAR as i, ASSISTANT_OVERLAY_DESKTOP_QUERY as a, ASSISTANT_OVERLAY_TOP_VAR as o, ASSISTANT_PANEL_INSET_VAR as s, ASSISTANT_SHEET_BOTTOM_VAR as c, assistantLauncherBottom as l, assistantLauncherRight as u, assistantOverlayClassName as d, assistantOverlayEscapeAction as f, assistantOverlayKeys as p, assistantQuotaKindOf as m, closestAround as h, createAssistantNavigate as g, isAssistantToggleShortcut as _, nextAssistantOpenState as v, publishedAssistantDockWidth as y, publishedAssistantPanelInset as b, readAssistantOverlayFlag as x, requestedAssistantOpenState as S, writeAssistantOverlayFlag as C } from "./assistantOverlayState.js";
|
|
6
6
|
import { useAssistantServerHistory as w } from "../history/useAssistantServerHistory.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
7
|
+
import { AssistantCloseGuardContext as T } from "./closeGuard.js";
|
|
8
|
+
import { AIAssistantPanel as E } from "./AIAssistantPanel.js";
|
|
9
|
+
import { createContext as D, useCallback as O, useContext as k, useEffect as A, useMemo as j, useRef as M, useState as N, useSyncExternalStore as P } from "react";
|
|
10
|
+
import { Sparkles as F } from "lucide-react";
|
|
11
|
+
import { jsx as I, jsxs as L } from "react/jsx-runtime";
|
|
11
12
|
//#region src/shared/assistant/ui/AssistantOverlay.tsx
|
|
12
|
-
var
|
|
13
|
-
function R() {
|
|
14
|
-
return O(L);
|
|
15
|
-
}
|
|
13
|
+
var R = D(null);
|
|
16
14
|
function z() {
|
|
17
|
-
return
|
|
15
|
+
return k(R);
|
|
18
16
|
}
|
|
19
17
|
function B() {
|
|
20
|
-
return
|
|
18
|
+
return k(R)?.dockWidthPx ?? 0;
|
|
19
|
+
}
|
|
20
|
+
function V() {
|
|
21
|
+
return k(R)?.panelInsetPx ?? 0;
|
|
21
22
|
}
|
|
22
|
-
var
|
|
23
|
-
function
|
|
23
|
+
var H = () => typeof window > "u" ? void 0 : window.sessionStorage, U = () => typeof window > "u" ? void 0 : window.localStorage;
|
|
24
|
+
function W(e) {
|
|
24
25
|
if (typeof window > "u" || typeof window.matchMedia != "function") return () => {};
|
|
25
26
|
let t = window.matchMedia(a);
|
|
26
27
|
return t.addEventListener("change", e), () => t.removeEventListener("change", e);
|
|
27
28
|
}
|
|
28
|
-
function
|
|
29
|
+
function G() {
|
|
29
30
|
return typeof window < "u" && typeof window.matchMedia == "function" && window.matchMedia("(min-width: 640px)").matches;
|
|
30
31
|
}
|
|
31
|
-
function
|
|
32
|
-
return
|
|
32
|
+
function K() {
|
|
33
|
+
return P(W, G, () => !1);
|
|
33
34
|
}
|
|
34
|
-
function
|
|
35
|
-
let r =
|
|
36
|
-
|
|
35
|
+
function q(t, n) {
|
|
36
|
+
let r = M(n);
|
|
37
|
+
A(() => {
|
|
37
38
|
r.current = n;
|
|
38
|
-
}, [n]),
|
|
39
|
+
}, [n]), A(() => {
|
|
39
40
|
if (!t || typeof window > "u") return;
|
|
40
41
|
let n = e(t), i = (e) => r.current(m(e), e);
|
|
41
42
|
return window.addEventListener(n, i), () => window.removeEventListener(n, i);
|
|
42
43
|
}, [t]);
|
|
43
44
|
}
|
|
44
|
-
var
|
|
45
|
-
function
|
|
46
|
-
let r =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}, [
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
var J = () => {};
|
|
46
|
+
function Y({ keyPrefix: e, quota: t, children: n }) {
|
|
47
|
+
let r = j(() => p(e), [e]), [a, o] = N(() => x(H, r.openStorageKey)), [c, l] = N(() => x(U, r.dockStorageKey)), u = M(null), d = M(a);
|
|
48
|
+
A(() => {
|
|
49
|
+
d.current = a;
|
|
50
|
+
}, [a]);
|
|
51
|
+
let f = O(() => u.current?.() === !0, []), m = O((e) => (u.current = e, () => {
|
|
52
|
+
u.current === e && (u.current = null);
|
|
53
|
+
}), []), h = O(() => o(!0), []), g = O(() => {
|
|
54
|
+
f() || o(!1);
|
|
55
|
+
}, [f]), w = O(() => {
|
|
56
|
+
d.current && f() || o((e) => !e);
|
|
57
|
+
}, [f]), T = O(() => l((e) => !e), []);
|
|
58
|
+
A(() => {
|
|
59
|
+
C(H, r.openStorageKey, a);
|
|
60
|
+
}, [r.openStorageKey, a]), A(() => {
|
|
61
|
+
C(U, r.dockStorageKey, c);
|
|
62
|
+
}, [r.dockStorageKey, c]), A(() => {
|
|
52
63
|
if (typeof window > "u") return;
|
|
53
64
|
let e = (e) => {
|
|
54
65
|
let t = S(e);
|
|
55
|
-
o((e) => v(e, t));
|
|
66
|
+
d.current && !v(!0, t) && u.current?.() === !0 || o((e) => v(e, t));
|
|
56
67
|
};
|
|
57
68
|
return window.addEventListener(r.toggleEvent, e), () => window.removeEventListener(r.toggleEvent, e);
|
|
58
|
-
}, [r.toggleEvent]),
|
|
69
|
+
}, [r.toggleEvent]), A(() => {
|
|
59
70
|
if (typeof document > "u") return;
|
|
60
71
|
let e = (e) => {
|
|
61
|
-
_(e) && (e.preventDefault(), o((e) => !e));
|
|
72
|
+
_(e) && (e.preventDefault(), !(d.current && u.current?.() === !0) && o((e) => !e));
|
|
62
73
|
};
|
|
63
74
|
return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
|
|
64
|
-
}, []),
|
|
65
|
-
let
|
|
75
|
+
}, []), q(t?.product ?? null, t?.onExhausted ?? J);
|
|
76
|
+
let E = K(), D = y({
|
|
66
77
|
isOpen: a,
|
|
67
78
|
docked: c,
|
|
68
|
-
isDesktop:
|
|
69
|
-
}),
|
|
79
|
+
isDesktop: E
|
|
80
|
+
}), k = b({
|
|
70
81
|
isOpen: a,
|
|
71
82
|
docked: c,
|
|
72
|
-
isDesktop:
|
|
83
|
+
isDesktop: E
|
|
73
84
|
});
|
|
74
|
-
|
|
85
|
+
A(() => {
|
|
75
86
|
if (typeof document > "u") return;
|
|
76
87
|
let e = document.documentElement.style;
|
|
77
|
-
return e.setProperty(i, `${
|
|
88
|
+
return e.setProperty(i, `${D}px`), () => {
|
|
78
89
|
e.removeProperty(i);
|
|
79
90
|
};
|
|
80
|
-
}, [
|
|
91
|
+
}, [D]), A(() => {
|
|
81
92
|
if (typeof document > "u") return;
|
|
82
93
|
let e = document.documentElement.style;
|
|
83
|
-
return e.setProperty(s, `${
|
|
94
|
+
return e.setProperty(s, `${k}px`), () => {
|
|
84
95
|
e.removeProperty(s);
|
|
85
96
|
};
|
|
86
|
-
}, [
|
|
87
|
-
let
|
|
97
|
+
}, [k]);
|
|
98
|
+
let P = j(() => ({
|
|
88
99
|
isOpen: a,
|
|
89
|
-
open:
|
|
90
|
-
close:
|
|
91
|
-
toggle:
|
|
100
|
+
open: h,
|
|
101
|
+
close: g,
|
|
102
|
+
toggle: w,
|
|
92
103
|
docked: c,
|
|
93
|
-
toggleDock:
|
|
94
|
-
dockWidthPx:
|
|
95
|
-
panelInsetPx:
|
|
96
|
-
keys: r
|
|
104
|
+
toggleDock: T,
|
|
105
|
+
dockWidthPx: D,
|
|
106
|
+
panelInsetPx: k,
|
|
107
|
+
keys: r,
|
|
108
|
+
registerCloseGuard: m
|
|
97
109
|
}), [
|
|
98
110
|
a,
|
|
99
|
-
|
|
100
|
-
d,
|
|
101
|
-
f,
|
|
102
|
-
c,
|
|
103
|
-
m,
|
|
111
|
+
h,
|
|
104
112
|
g,
|
|
105
113
|
w,
|
|
106
|
-
|
|
114
|
+
c,
|
|
115
|
+
T,
|
|
116
|
+
D,
|
|
117
|
+
k,
|
|
118
|
+
r,
|
|
119
|
+
m
|
|
107
120
|
]);
|
|
108
|
-
return /* @__PURE__ */
|
|
109
|
-
value:
|
|
121
|
+
return /* @__PURE__ */ I(R.Provider, {
|
|
122
|
+
value: P,
|
|
110
123
|
children: n
|
|
111
124
|
});
|
|
112
125
|
}
|
|
113
|
-
var
|
|
126
|
+
var X = (e, t) => /* @__PURE__ */ I(E, {
|
|
114
127
|
...e,
|
|
115
128
|
...t
|
|
116
129
|
});
|
|
117
|
-
function
|
|
118
|
-
let t =
|
|
119
|
-
return t ? /* @__PURE__ */
|
|
130
|
+
function Z(e) {
|
|
131
|
+
let t = z();
|
|
132
|
+
return t ? /* @__PURE__ */ I(Q, {
|
|
120
133
|
state: t,
|
|
121
134
|
...e
|
|
122
135
|
}) : null;
|
|
123
136
|
}
|
|
124
|
-
function
|
|
125
|
-
let { isOpen: S, close: C, toggle:
|
|
137
|
+
function Q({ state: e, panelId: i = "ai-assistant", panelProps: a, renderPanel: s, navigate: p, isShellLessTarget: m, topOffsetPx: _ = 56, launcherIcon: v, launcherBottomInset: y = "1.5rem", showLauncher: b = !0, closeOnEscape: x = !0 }) {
|
|
138
|
+
let { isOpen: S, close: C, toggle: E, docked: D, toggleDock: O, dockWidthPx: k } = e, M = t();
|
|
126
139
|
w(a?.serverHistory === !0);
|
|
127
|
-
let [
|
|
140
|
+
let [P, R] = N(!1), z = j(() => p ? g({
|
|
128
141
|
navigate: p,
|
|
129
142
|
isShellLessTarget: m,
|
|
130
|
-
isDesktop:
|
|
131
|
-
collapseSheet: () =>
|
|
143
|
+
isDesktop: G,
|
|
144
|
+
collapseSheet: () => R(!1),
|
|
132
145
|
openInNewTab: (e) => window.open(e, "_blank", "noopener,noreferrer")
|
|
133
146
|
}) : void 0, [p, m]);
|
|
134
|
-
|
|
147
|
+
A(() => {
|
|
135
148
|
if (!S || !x) return;
|
|
136
149
|
let e = (e) => {
|
|
137
150
|
f({
|
|
@@ -149,53 +162,56 @@ function Z({ state: e, panelId: i = "ai-assistant", panelProps: a, renderPanel:
|
|
|
149
162
|
C,
|
|
150
163
|
x
|
|
151
164
|
]);
|
|
152
|
-
let
|
|
165
|
+
let B = {
|
|
153
166
|
panelId: i,
|
|
154
167
|
visible: !0,
|
|
155
168
|
onClose: C,
|
|
156
|
-
docked:
|
|
157
|
-
onToggleDock:
|
|
158
|
-
onNavigate:
|
|
169
|
+
docked: D,
|
|
170
|
+
onToggleDock: O,
|
|
171
|
+
onNavigate: z,
|
|
159
172
|
isShellLessTarget: m
|
|
160
|
-
},
|
|
173
|
+
}, V = M("overlay.launcher", "AI Assistant"), H = {
|
|
161
174
|
[o]: `${_}px`,
|
|
162
175
|
[c]: "var(--mobile-tabbar-height, 0px)"
|
|
163
176
|
};
|
|
164
|
-
return /* @__PURE__ */
|
|
177
|
+
return /* @__PURE__ */ L(r, {
|
|
165
178
|
preview: a?.preview,
|
|
166
|
-
children: [S && /* @__PURE__ */
|
|
179
|
+
children: [S && /* @__PURE__ */ L("div", {
|
|
167
180
|
className: d({
|
|
168
|
-
expanded:
|
|
169
|
-
docked:
|
|
181
|
+
expanded: P,
|
|
182
|
+
docked: D
|
|
170
183
|
}),
|
|
171
|
-
style:
|
|
184
|
+
style: H,
|
|
172
185
|
"data-testid": "assistant-overlay",
|
|
173
|
-
children: [/* @__PURE__ */
|
|
186
|
+
children: [/* @__PURE__ */ I("button", {
|
|
174
187
|
type: "button",
|
|
175
|
-
onClick: () =>
|
|
188
|
+
onClick: () => R((e) => !e),
|
|
176
189
|
className: "flex w-full shrink-0 justify-center pt-2 pb-1 sm:hidden",
|
|
177
|
-
"aria-label":
|
|
178
|
-
"aria-expanded":
|
|
190
|
+
"aria-label": P ? M("sheet.collapse", "Shrink the assistant") : M("sheet.expand", "Expand the assistant"),
|
|
191
|
+
"aria-expanded": P,
|
|
179
192
|
"data-testid": "assistant-sheet-handle",
|
|
180
|
-
children: /* @__PURE__ */
|
|
181
|
-
}), /* @__PURE__ */
|
|
193
|
+
children: /* @__PURE__ */ I("span", { className: "h-1.5 w-10 rounded-full bg-border-strong" })
|
|
194
|
+
}), /* @__PURE__ */ I("div", {
|
|
182
195
|
className: "flex min-h-0 flex-1 flex-col",
|
|
183
|
-
children:
|
|
196
|
+
children: /* @__PURE__ */ I(T.Provider, {
|
|
197
|
+
value: e.registerCloseGuard ?? null,
|
|
198
|
+
children: s ? s(B) : X(a, B)
|
|
199
|
+
})
|
|
184
200
|
})]
|
|
185
|
-
}), b && /* @__PURE__ */
|
|
201
|
+
}), b && /* @__PURE__ */ I("button", {
|
|
186
202
|
type: "button",
|
|
187
|
-
onClick:
|
|
188
|
-
title:
|
|
189
|
-
"aria-label":
|
|
203
|
+
onClick: E,
|
|
204
|
+
title: M("overlay.launcherTitle", "AI Assistant ({{shortcut}})", { shortcut: "Ctrl+Shift+A" }),
|
|
205
|
+
"aria-label": V,
|
|
190
206
|
"aria-haspopup": "dialog",
|
|
191
207
|
"aria-expanded": S,
|
|
192
208
|
"data-testid": "assistant-fab",
|
|
193
209
|
style: {
|
|
194
210
|
bottom: l(y),
|
|
195
|
-
right: u(
|
|
211
|
+
right: u(k)
|
|
196
212
|
},
|
|
197
213
|
className: "fixed z-30 size-14 items-center justify-center rounded-full border border-border-strong bg-[var(--color-accent-soft)] text-action-primary-bg shadow-float transition-colors duration-200 hover:border-action-primary-bg focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] cursor-pointer " + (S ? "hidden sm:inline-flex" : "inline-flex"),
|
|
198
|
-
children: v ?? /* @__PURE__ */ F
|
|
214
|
+
children: v ?? /* @__PURE__ */ I(F, {
|
|
199
215
|
className: "size-6",
|
|
200
216
|
"aria-hidden": "true"
|
|
201
217
|
})
|
|
@@ -203,4 +219,4 @@ function Z({ state: e, panelId: i = "ai-assistant", panelProps: a, renderPanel:
|
|
|
203
219
|
});
|
|
204
220
|
}
|
|
205
221
|
//#endregion
|
|
206
|
-
export {
|
|
222
|
+
export { Z as AssistantOverlay, Y as AssistantOverlayProvider, B as useAssistantDockWidth, z as useAssistantOverlayOrNull, V as useAssistantPanelInset, q as useAssistantQuotaExhausted };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A request to close the assistant, answered before it happens (BOFF-10177, N1).
|
|
3
|
+
* Returns true when the guard took the request: it asks the person itself, and
|
|
4
|
+
* closes through the same path again once they have said yes. Returns false
|
|
5
|
+
* when nothing stands in the way.
|
|
6
|
+
*/
|
|
7
|
+
export type AssistantCloseGuard = () => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* How the open panel hands its close guard to whoever closes it. The overlay
|
|
10
|
+
* provides it (`AssistantOverlayProvider.registerCloseGuard`), so a launcher
|
|
11
|
+
* press, the shortcut, the toggle event and Escape with focus outside the panel
|
|
12
|
+
* are asked about too, not only the panel's own Close.
|
|
13
|
+
*
|
|
14
|
+
* ★ A context rather than a panel prop: a product that renders the panel
|
|
15
|
+
* through its own `renderPanel` wrapper gets the guard without having to spread
|
|
16
|
+
* one more prop. Null where no overlay provides one; the panel then guards its
|
|
17
|
+
* own Close and Escape only.
|
|
18
|
+
*/
|
|
19
|
+
export declare const AssistantCloseGuardContext: import('react').Context<((guard: AssistantCloseGuard) => () => void) | null>;
|
|
20
|
+
/** The registration function, or null when no overlay provides one. */
|
|
21
|
+
export declare function useAssistantCloseGuardRegistration(): ((guard: AssistantCloseGuard) => () => void) | null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createContext as e, useContext as t } from "react";
|
|
2
|
+
//#region src/shared/assistant/ui/closeGuard.ts
|
|
3
|
+
var n = e(null);
|
|
4
|
+
function r() {
|
|
5
|
+
return t(n);
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { n as AssistantCloseGuardContext, r as useAssistantCloseGuardRegistration };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { FC, ReactNode, Ref } from 'react';
|
|
2
|
+
export interface AssistantCompactHeaderProps {
|
|
3
|
+
/** Where the user is, or `null` when the product cannot say (the whole segment is dropped). */
|
|
4
|
+
contextLabel: string | null;
|
|
5
|
+
/** The ☰ view is open. */
|
|
6
|
+
menuOpen: boolean;
|
|
7
|
+
/** The id of the ☰ view, named by `aria-controls` while it is open. */
|
|
8
|
+
menuViewId?: string;
|
|
9
|
+
onOpenMenu: () => void;
|
|
10
|
+
menuButtonRef?: Ref<HTMLButtonElement>;
|
|
11
|
+
/** The ⋯ conversation menu, which owns its own trigger. */
|
|
12
|
+
conversationMenu: ReactNode;
|
|
13
|
+
/** Expand/Restore is offered at this viewport (N2). */
|
|
14
|
+
expandOffered: boolean;
|
|
15
|
+
expanded: boolean;
|
|
16
|
+
onToggleExpanded: () => void;
|
|
17
|
+
onTogglePreview?: () => void;
|
|
18
|
+
previewOpen?: boolean;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The compact presentation's header (BOFF-10177, Slice 2):
|
|
23
|
+
* `[☰] [✦] title / context ………… [Preview] [⋯] [Expand] [×]`.
|
|
24
|
+
*
|
|
25
|
+
* ★ What it does NOT carry, on purpose: no "Ready" line (D4), no New
|
|
26
|
+
* conversation or History button (both live in ☰), and no Dock/Float toggle
|
|
27
|
+
* (D13: Panel layout lives only in ☰ → Settings). Each is still reachable; see
|
|
28
|
+
* `compactFeatureMap.test.tsx`.
|
|
29
|
+
*
|
|
30
|
+
* ★ `aria-expanded` on ☰ and on ⋯ (Radix sets it on the latter), and
|
|
31
|
+
* `aria-pressed` on Expand. Expand keeps one NAME and says its state with
|
|
32
|
+
* `aria-pressed`: a label that also flipped would announce the state twice.
|
|
33
|
+
* It is offered only where the page keeps room beside a 760 px assistant (N2).
|
|
34
|
+
*
|
|
35
|
+
* ★ The preview toggle keeps its classic render rule: nothing unless the
|
|
36
|
+
* product supplied a preview surface.
|
|
37
|
+
*/
|
|
38
|
+
export declare const AssistantCompactHeader: FC<AssistantCompactHeaderProps>;
|
|
@@ -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 };
|