@parallelworks/ui 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/adapter/types.d.ts +5 -0
- package/dist/ai/components/ChatSidebar.js +92 -176
- package/dist/ai/core/parts.js +20 -0
- package/dist/ai/index.d.ts +2 -1
- package/dist/ai/index.js +3 -2
- package/dist/ai/strings.d.ts +2 -0
- package/dist/ai/strings.js +2 -0
- package/dist/ai/ui/ConversationSidebar.d.ts +58 -0
- package/dist/ai/ui/ConversationSidebar.js +162 -0
- package/dist/ai/ui/Notice.d.ts +8 -0
- package/dist/ai/ui/Notice.js +27 -1
- package/dist/ai/ui/sidebar.d.ts +2 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -7,19 +7,20 @@ import { FaUsers } from "../../icons/fa/FaUsers.js";
|
|
|
7
7
|
import { IoSettingsOutline } from "../../icons/io5/IoSettingsOutline.js";
|
|
8
8
|
import { PiShareBold } from "../../icons/pi/PiShareBold.js";
|
|
9
9
|
import { TbEdit } from "../../icons/tb/TbEdit.js";
|
|
10
|
-
import { positionKeys } from "../../components/keys.js";
|
|
11
10
|
import { useRowMenu } from "../../list/RowContextMenu.js";
|
|
12
11
|
import { useChatConfig } from "../core/config.js";
|
|
13
12
|
import { useChat } from "../core/ChatProvider.js";
|
|
14
13
|
import { FOCUS_SIDEBAR_SEARCH_EVENT } from "../core/events.js";
|
|
15
14
|
import { conversationToMarkdown, downloadText, exportFilename } from "../core/exportConversation.js";
|
|
16
|
-
import {
|
|
15
|
+
import { SidebarRow, useRowDialogs } from "../ui/sidebar.js";
|
|
16
|
+
import { ConversationSidebar } from "../ui/ConversationSidebar.js";
|
|
17
17
|
import ShareDialog from "./ShareDialog.js";
|
|
18
18
|
import cx from "classnames";
|
|
19
19
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
20
20
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
21
21
|
import { DateTime } from "luxon";
|
|
22
22
|
//#region src/ai/components/ChatSidebar.tsx
|
|
23
|
+
var SIDEBAR_WIDTH_STORAGE_KEY = "aiChatSidebarWidth";
|
|
23
24
|
function groupByDate(conversations) {
|
|
24
25
|
const now = DateTime.now();
|
|
25
26
|
const groups = {
|
|
@@ -90,64 +91,6 @@ function ConversationRow({ conv, isCurrent, sharingAvailable, openMenu, onRename
|
|
|
90
91
|
})
|
|
91
92
|
});
|
|
92
93
|
}
|
|
93
|
-
var SIDEBAR_WIDTH_STORAGE_KEY = "aiChatSidebarWidth";
|
|
94
|
-
var SIDEBAR_MIN_WIDTH_PX = 200;
|
|
95
|
-
var SIDEBAR_MAX_WIDTH_PX = 480;
|
|
96
|
-
function clampSidebarWidth(width) {
|
|
97
|
-
return Math.min(SIDEBAR_MAX_WIDTH_PX, Math.max(SIDEBAR_MIN_WIDTH_PX, width));
|
|
98
|
-
}
|
|
99
|
-
function readStoredSidebarWidth() {
|
|
100
|
-
try {
|
|
101
|
-
const stored = Number(window.localStorage.getItem(SIDEBAR_WIDTH_STORAGE_KEY));
|
|
102
|
-
return Number.isFinite(stored) && stored > 0 ? clampSidebarWidth(stored) : 256;
|
|
103
|
-
} catch {
|
|
104
|
-
return 256;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function storeSidebarWidth(width) {
|
|
108
|
-
try {
|
|
109
|
-
window.localStorage.setItem(SIDEBAR_WIDTH_STORAGE_KEY, String(width));
|
|
110
|
-
} catch {}
|
|
111
|
-
}
|
|
112
|
-
function useResizableSidebarWidth() {
|
|
113
|
-
const [width, setWidth] = useState(readStoredSidebarWidth);
|
|
114
|
-
const [resizing, setResizing] = useState(false);
|
|
115
|
-
const widthRef = useRef(width);
|
|
116
|
-
widthRef.current = width;
|
|
117
|
-
const startResize = (e) => {
|
|
118
|
-
e.preventDefault();
|
|
119
|
-
const startX = e.clientX;
|
|
120
|
-
const startWidth = widthRef.current;
|
|
121
|
-
setResizing(true);
|
|
122
|
-
const onMove = (ev) => {
|
|
123
|
-
setWidth(clampSidebarWidth(startWidth + ev.clientX - startX));
|
|
124
|
-
};
|
|
125
|
-
const onUp = () => {
|
|
126
|
-
window.removeEventListener("pointermove", onMove);
|
|
127
|
-
window.removeEventListener("pointerup", onUp);
|
|
128
|
-
setResizing(false);
|
|
129
|
-
storeSidebarWidth(widthRef.current);
|
|
130
|
-
};
|
|
131
|
-
window.addEventListener("pointermove", onMove);
|
|
132
|
-
window.addEventListener("pointerup", onUp);
|
|
133
|
-
};
|
|
134
|
-
const reset = () => {
|
|
135
|
-
setWidth(256);
|
|
136
|
-
storeSidebarWidth(256);
|
|
137
|
-
};
|
|
138
|
-
const nudge = (delta) => {
|
|
139
|
-
const next = clampSidebarWidth(widthRef.current + delta);
|
|
140
|
-
setWidth(next);
|
|
141
|
-
storeSidebarWidth(next);
|
|
142
|
-
};
|
|
143
|
-
return {
|
|
144
|
-
width,
|
|
145
|
-
resizing,
|
|
146
|
-
startResize,
|
|
147
|
-
reset,
|
|
148
|
-
nudge
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
94
|
function ChatSidebar({ actions = true }) {
|
|
152
95
|
const { extraLinks, LinkComponent, strings } = useChatConfig();
|
|
153
96
|
const tSidebar = strings.sidebar;
|
|
@@ -171,7 +114,6 @@ function ChatSidebar({ actions = true }) {
|
|
|
171
114
|
onDelete: (conv) => deleteConversation(conv.id),
|
|
172
115
|
onRename: (conv, title) => updateConversationTitle(conv.id, title)
|
|
173
116
|
});
|
|
174
|
-
const { width: sidebarWidth, resizing, startResize, reset: resetSidebarWidth, nudge: nudgeSidebarWidth } = useResizableSidebarWidth();
|
|
175
117
|
const [search, setSearch] = useState("");
|
|
176
118
|
const searchInputRef = useRef(null);
|
|
177
119
|
const sidebarCollapsedRef = useRef(sidebarCollapsed);
|
|
@@ -197,7 +139,11 @@ function ChatSidebar({ actions = true }) {
|
|
|
197
139
|
search,
|
|
198
140
|
tSidebar.untitled
|
|
199
141
|
]);
|
|
200
|
-
const
|
|
142
|
+
const groups = useMemo(() => groupByDate(filteredConversations).map(([key, items]) => ({
|
|
143
|
+
key,
|
|
144
|
+
label: tSidebar[key],
|
|
145
|
+
items
|
|
146
|
+
})), [filteredConversations, tSidebar]);
|
|
201
147
|
const handleExport = async (id) => {
|
|
202
148
|
try {
|
|
203
149
|
const conversation = await adapter.conversations.get(id);
|
|
@@ -215,125 +161,95 @@ function ChatSidebar({ actions = true }) {
|
|
|
215
161
|
navigation.toNewChat();
|
|
216
162
|
};
|
|
217
163
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
218
|
-
/* @__PURE__ */
|
|
164
|
+
/* @__PURE__ */ jsx(ConversationSidebar, {
|
|
219
165
|
collapsed: sidebarCollapsed,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
166
|
+
onToggle: toggleSidebar,
|
|
167
|
+
toggleLabels: {
|
|
168
|
+
open: tSidebar.openSidebar,
|
|
169
|
+
close: tSidebar.closeSidebar
|
|
170
|
+
},
|
|
171
|
+
resize: {
|
|
172
|
+
storageKey: SIDEBAR_WIDTH_STORAGE_KEY,
|
|
173
|
+
label: tSidebar.resizeLabel,
|
|
174
|
+
hint: tSidebar.resizeHint
|
|
175
|
+
},
|
|
176
|
+
groups,
|
|
177
|
+
getKey: (conv) => conv.id,
|
|
178
|
+
renderRow: (conv) => /* @__PURE__ */ jsx(ConversationRow, {
|
|
179
|
+
conv,
|
|
180
|
+
isCurrent: activeConversationId === conv.id,
|
|
181
|
+
sharingAvailable,
|
|
182
|
+
openMenu,
|
|
183
|
+
onRename: requestRename,
|
|
184
|
+
onShare: handleShareClick,
|
|
185
|
+
onDelete: requestDelete,
|
|
186
|
+
onExport: handleExport
|
|
187
|
+
}),
|
|
188
|
+
loading: isLoading,
|
|
189
|
+
placeholder: conversations.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
190
|
+
className: "theme-muted-text text-sm px-2 whitespace-nowrap",
|
|
191
|
+
children: tSidebar.noConversations
|
|
192
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
193
|
+
className: "theme-muted-text text-sm px-2",
|
|
194
|
+
children: strings.chrome.noMatches
|
|
195
|
+
}),
|
|
196
|
+
cycle: {
|
|
197
|
+
current: filteredConversations.find((conv) => conv.id === activeConversationId),
|
|
198
|
+
onSelect: (conv) => navigation.toConversation(conv.id)
|
|
199
|
+
},
|
|
200
|
+
top: actions && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
201
|
+
className: "flex flex-col gap-0.5 px-2 pt-2 pb-1",
|
|
202
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
203
|
+
type: "button",
|
|
204
|
+
onClick: handleNewChat,
|
|
205
|
+
className: "flex items-center h-9 px-2 gap-3 rounded-lg font-medium transition-colors hover:chat-tint",
|
|
206
|
+
title: tSidebar.newChat,
|
|
207
|
+
children: [/* @__PURE__ */ jsx(TbEdit, { className: cx("flex-shrink-0", "w-4 h-4") }), /* @__PURE__ */ jsx("span", {
|
|
208
|
+
className: cx("text-sm transition-opacity duration-200", sidebarCollapsed ? "opacity-0 w-0 overflow-hidden" : "opacity-100"),
|
|
209
|
+
children: tSidebar.newChat
|
|
210
|
+
})]
|
|
211
|
+
}), attachmentsAvailable && /* @__PURE__ */ jsxs(LinkComponent, {
|
|
212
|
+
target: { kind: "attachments" },
|
|
213
|
+
className: "flex items-center h-9 px-2 gap-3 rounded-lg transition-colors hover:chat-tint",
|
|
214
|
+
title: tSidebar.attachments,
|
|
215
|
+
children: [/* @__PURE__ */ jsx(FaRegFileImage, { className: "flex-shrink-0 w-4 h-4" }), /* @__PURE__ */ jsx("span", {
|
|
216
|
+
className: cx("text-sm transition-opacity duration-200", sidebarCollapsed ? "opacity-0 w-0 overflow-hidden" : "opacity-100"),
|
|
217
|
+
children: tSidebar.attachments
|
|
259
218
|
})]
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
},
|
|
276
|
-
placeholder: strings.chrome.searchPlaceholder,
|
|
277
|
-
"aria-label": strings.chrome.searchLabel,
|
|
278
|
-
className: "w-full h-9 pl-8 pr-2 text-sm rounded-lg border border-transparent chat-tint theme-text placeholder:theme-muted-text transition-colors focus:outline-none focus:border-(--theme-border) focus:bg-(--theme-panel-bg)"
|
|
279
|
-
})]
|
|
280
|
-
})
|
|
281
|
-
}),
|
|
282
|
-
/* @__PURE__ */ jsx("div", {
|
|
283
|
-
className: cx("min-h-0 flex-1 overflow-y-auto px-2 pb-2 transition-opacity duration-200", sidebarCollapsed ? "opacity-0 pointer-events-none" : "opacity-100"),
|
|
284
|
-
children: isLoading ? /* @__PURE__ */ jsx("div", {
|
|
285
|
-
className: "space-y-3 px-2 pt-2",
|
|
286
|
-
children: positionKeys(5, "skeleton").map((key, i) => /* @__PURE__ */ jsx("div", {
|
|
287
|
-
className: "animate-pulse",
|
|
288
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
289
|
-
className: "h-4 rounded theme-muted-panel",
|
|
290
|
-
style: { width: `${70 - i * 10}%` }
|
|
291
|
-
})
|
|
292
|
-
}, key))
|
|
293
|
-
}) : conversations.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
294
|
-
className: "theme-muted-text text-sm px-2 whitespace-nowrap",
|
|
295
|
-
children: tSidebar.noConversations
|
|
296
|
-
}) : filteredConversations.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
297
|
-
className: "theme-muted-text text-sm px-2",
|
|
298
|
-
children: strings.chrome.noMatches
|
|
299
|
-
}) : groupedConversations.map(([label, convs]) => /* @__PURE__ */ jsxs("div", {
|
|
300
|
-
className: "mb-2",
|
|
301
|
-
children: [/* @__PURE__ */ jsx(SidebarGroupHeading, { children: tSidebar[label] }), /* @__PURE__ */ jsx("ul", {
|
|
302
|
-
className: "space-y-0.5",
|
|
303
|
-
children: convs.map((conv) => /* @__PURE__ */ jsx(ConversationRow, {
|
|
304
|
-
conv,
|
|
305
|
-
isCurrent: activeConversationId === conv.id,
|
|
306
|
-
sharingAvailable,
|
|
307
|
-
openMenu,
|
|
308
|
-
onRename: requestRename,
|
|
309
|
-
onShare: handleShareClick,
|
|
310
|
-
onDelete: requestDelete,
|
|
311
|
-
onExport: handleExport
|
|
312
|
-
}, conv.id))
|
|
313
|
-
})]
|
|
314
|
-
}, label))
|
|
315
|
-
}),
|
|
316
|
-
/* @__PURE__ */ jsxs("div", {
|
|
317
|
-
className: cx("flex shrink-0 items-center gap-1 p-1.5", sidebarCollapsed ? "justify-center" : extraLinks.manageProviders ? "justify-between" : "justify-end"),
|
|
318
|
-
children: [!sidebarCollapsed && extraLinks.manageProviders && /* @__PURE__ */ jsxs(LinkComponent, {
|
|
319
|
-
target: {
|
|
320
|
-
kind: "external",
|
|
321
|
-
href: extraLinks.manageProviders
|
|
219
|
+
})]
|
|
220
|
+
}), !sidebarCollapsed && conversations.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
221
|
+
className: "px-2 pb-2",
|
|
222
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
223
|
+
className: "relative",
|
|
224
|
+
children: [/* @__PURE__ */ jsx(FaSearch, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 theme-muted-text pointer-events-none" }), /* @__PURE__ */ jsx("input", {
|
|
225
|
+
ref: searchInputRef,
|
|
226
|
+
type: "text",
|
|
227
|
+
value: search,
|
|
228
|
+
onChange: (e) => setSearch(e.target.value),
|
|
229
|
+
onKeyDown: (e) => {
|
|
230
|
+
if (e.key === "Escape" && search) {
|
|
231
|
+
e.stopPropagation();
|
|
232
|
+
setSearch("");
|
|
233
|
+
}
|
|
322
234
|
},
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
className: "truncate",
|
|
327
|
-
children: tSidebar.manageProviders
|
|
328
|
-
})]
|
|
329
|
-
}), /* @__PURE__ */ jsx(SidebarToggle, {
|
|
330
|
-
collapsed: sidebarCollapsed,
|
|
331
|
-
onToggle: toggleSidebar,
|
|
332
|
-
openLabel: tSidebar.openSidebar,
|
|
333
|
-
closeLabel: tSidebar.closeSidebar
|
|
235
|
+
placeholder: strings.chrome.searchPlaceholder,
|
|
236
|
+
"aria-label": strings.chrome.searchLabel,
|
|
237
|
+
className: "w-full h-9 pl-8 pr-2 text-sm rounded-lg border border-transparent chat-tint theme-text placeholder:theme-muted-text transition-colors focus:outline-none focus:border-(--theme-border) focus:bg-(--theme-panel-bg)"
|
|
334
238
|
})]
|
|
335
239
|
})
|
|
336
|
-
]
|
|
240
|
+
})] }),
|
|
241
|
+
footer: extraLinks.manageProviders && /* @__PURE__ */ jsxs(LinkComponent, {
|
|
242
|
+
target: {
|
|
243
|
+
kind: "external",
|
|
244
|
+
href: extraLinks.manageProviders
|
|
245
|
+
},
|
|
246
|
+
className: "flex min-w-0 items-center gap-2 rounded-md px-2 py-1.5 text-xs theme-muted-text transition-colors hover:chat-tint hover:theme-text",
|
|
247
|
+
title: tSidebar.manageProviders,
|
|
248
|
+
children: [/* @__PURE__ */ jsx(IoSettingsOutline, { className: "h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
|
|
249
|
+
className: "truncate",
|
|
250
|
+
children: tSidebar.manageProviders
|
|
251
|
+
})]
|
|
252
|
+
})
|
|
337
253
|
}),
|
|
338
254
|
contextMenu,
|
|
339
255
|
dialogs,
|
package/dist/ai/core/parts.js
CHANGED
|
@@ -5,6 +5,23 @@
|
|
|
5
5
|
* rather than invented — an end without a start has nothing to render. */
|
|
6
6
|
function applyPartDelta(parts, delta) {
|
|
7
7
|
switch (delta.type) {
|
|
8
|
+
case "text":
|
|
9
|
+
case "reasoning": {
|
|
10
|
+
const last = parts.at(-1);
|
|
11
|
+
if (last?.kind === delta.type) return [...parts.slice(0, -1), {
|
|
12
|
+
...last,
|
|
13
|
+
text: last.text + delta.text
|
|
14
|
+
}];
|
|
15
|
+
return [...parts, {
|
|
16
|
+
kind: delta.type,
|
|
17
|
+
text: delta.text
|
|
18
|
+
}];
|
|
19
|
+
}
|
|
20
|
+
case "retract": {
|
|
21
|
+
let end = parts.length;
|
|
22
|
+
while (end > 0 && isStreamedText(parts[end - 1])) end -= 1;
|
|
23
|
+
return end === parts.length ? parts : parts.slice(0, end);
|
|
24
|
+
}
|
|
8
25
|
case "tool_start": return [...parts, {
|
|
9
26
|
kind: "tool_call",
|
|
10
27
|
id: delta.id,
|
|
@@ -70,6 +87,9 @@ function applyPartDelta(parts, delta) {
|
|
|
70
87
|
default: return parts;
|
|
71
88
|
}
|
|
72
89
|
}
|
|
90
|
+
function isStreamedText(part) {
|
|
91
|
+
return part?.kind === "text" || part?.kind === "reasoning";
|
|
92
|
+
}
|
|
73
93
|
/** A turn can end (completed or stopped) with tool calls still marked
|
|
74
94
|
* running — the transport died before their tool_end arrived. A stored
|
|
75
95
|
* message must not spin forever, so close them out as interrupted. */
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -38,7 +38,8 @@ export { default as useDragDrop } from './core/useDragDrop.js';
|
|
|
38
38
|
export { useKeyboardShortcuts } from './core/useKeyboardShortcuts.js';
|
|
39
39
|
export { type ChatStrings, defaultChatStrings } from './strings.js';
|
|
40
40
|
export * from './types.js';
|
|
41
|
+
export { ConversationSidebar, type ConversationSidebarGroup, useResizableSidebarWidth, } from './ui/ConversationSidebar.js';
|
|
41
42
|
export { type DropdownOption, default as Dropdown } from './ui/Dropdown.js';
|
|
42
|
-
export { NoticeBar, NoticeCard, type NoticeTone } from './ui/Notice.js';
|
|
43
|
+
export { NoticeBar, NoticeCard, type NoticeTone, OutputCard } from './ui/Notice.js';
|
|
43
44
|
export { RenameDialog, type RowDialogStrings, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs, } from './ui/sidebar.js';
|
|
44
45
|
export { formatFileSize, isMac, isOrgModel } from './utils.js';
|
package/dist/ai/index.js
CHANGED
|
@@ -36,6 +36,7 @@ import { FOCUS_SIDEBAR_SEARCH_EVENT } from "./core/events.js";
|
|
|
36
36
|
import { useKeyboardShortcuts } from "./core/useKeyboardShortcuts.js";
|
|
37
37
|
import { KeyboardShortcutsProvider } from "./core/KeyboardShortcutsProvider.js";
|
|
38
38
|
import { RenameDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs } from "./ui/sidebar.js";
|
|
39
|
+
import { ConversationSidebar, useResizableSidebarWidth } from "./ui/ConversationSidebar.js";
|
|
39
40
|
import ShareDialog, { ShareButton } from "./components/ShareDialog.js";
|
|
40
41
|
import ChatSidebar from "./components/ChatSidebar.js";
|
|
41
42
|
import ChatLayout from "./components/ChatLayout.js";
|
|
@@ -43,5 +44,5 @@ import ChatMessage_default from "./components/ChatMessage.js";
|
|
|
43
44
|
import ChatMessageList from "./components/ChatMessageList.js";
|
|
44
45
|
import ChatThread from "./components/ChatThread.js";
|
|
45
46
|
import ComposerFrame from "./components/ComposerFrame.js";
|
|
46
|
-
import { NoticeBar, NoticeCard } from "./ui/Notice.js";
|
|
47
|
-
export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatThread, ComposerContext, ComposerControls, ComposerFrame, ComposerSettings, ConnectToolsLink, DEFAULT_PERMISSION_MODE, DragOverlay, Dropdown, FOCUS_SIDEBAR_SEARCH_EVENT, INTERRUPTED_NOTICE, KeyboardShortcutsProvider, MAX_EDIT_DIFF_LINES, MAX_WRITE_DIFF_LINES, ModelSelector, NAMED_PERMISSION_MODES, NoticeBar, NoticeCard, PermissionPicker, ProviderIssueBanner, RenameDialog, ShareButton, ShareDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, compactCount, computeEditDiff, computeWriteRows, defaultChatStrings, durationToAbsHumanDuration, editSummary, finalizeParts, firstNonEmptyLine, formatElapsed, formatFileSize, fullToolArgs, getGreeting, getProviderKeyFromModelId, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, pasteInlineMaxBytes, permissionModesUpTo, planFromArgs, plural, providerIssueFor, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useDragDrop, useKeyboardShortcuts, useRowDialogs, wordEmphasisRanges };
|
|
47
|
+
import { NoticeBar, NoticeCard, OutputCard } from "./ui/Notice.js";
|
|
48
|
+
export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatThread, ComposerContext, ComposerControls, ComposerFrame, ComposerSettings, ConnectToolsLink, ConversationSidebar, DEFAULT_PERMISSION_MODE, DragOverlay, Dropdown, FOCUS_SIDEBAR_SEARCH_EVENT, INTERRUPTED_NOTICE, KeyboardShortcutsProvider, MAX_EDIT_DIFF_LINES, MAX_WRITE_DIFF_LINES, ModelSelector, NAMED_PERMISSION_MODES, NoticeBar, NoticeCard, OutputCard, PermissionPicker, ProviderIssueBanner, RenameDialog, ShareButton, ShareDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, compactCount, computeEditDiff, computeWriteRows, defaultChatStrings, durationToAbsHumanDuration, editSummary, finalizeParts, firstNonEmptyLine, formatElapsed, formatFileSize, fullToolArgs, getGreeting, getProviderKeyFromModelId, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, pasteInlineMaxBytes, permissionModesUpTo, planFromArgs, plural, providerIssueFor, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useDragDrop, useKeyboardShortcuts, useResizableSidebarWidth, useRowDialogs, wordEmphasisRanges };
|
package/dist/ai/strings.d.ts
CHANGED
package/dist/ai/strings.js
CHANGED
|
@@ -100,6 +100,8 @@ var defaultChatStrings = {
|
|
|
100
100
|
sharedWithYou: "Shared with you",
|
|
101
101
|
openSidebar: "Open sidebar",
|
|
102
102
|
closeSidebar: "Close sidebar",
|
|
103
|
+
resizeLabel: "Resize conversation list",
|
|
104
|
+
resizeHint: "Drag to resize; double-click to reset",
|
|
103
105
|
newChat: "New chat",
|
|
104
106
|
attachments: "Attachments",
|
|
105
107
|
connectTools: "Connect Tools",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export declare const SIDEBAR_MIN_WIDTH_PX = 200;
|
|
3
|
+
export declare const SIDEBAR_MAX_WIDTH_PX = 480;
|
|
4
|
+
/** A sidebar width the reader drags, nudges or resets, kept per list under
|
|
5
|
+
* its own storage key. */
|
|
6
|
+
export declare function useResizableSidebarWidth(storageKey: string): {
|
|
7
|
+
width: number;
|
|
8
|
+
resizing: boolean;
|
|
9
|
+
startResize: (e: React.PointerEvent<HTMLElement>) => void;
|
|
10
|
+
reset: () => void;
|
|
11
|
+
nudge: (delta: number) => void;
|
|
12
|
+
};
|
|
13
|
+
export interface ConversationSidebarGroup<T> {
|
|
14
|
+
key: string;
|
|
15
|
+
label: ReactNode;
|
|
16
|
+
tone?: 'default' | 'attention';
|
|
17
|
+
items: T[];
|
|
18
|
+
}
|
|
19
|
+
/** A list of conversations beside the thread: headed groups of rows that
|
|
20
|
+
* collapse to a rail, with an optional drag handle for the width. The host
|
|
21
|
+
* decides what is listed, how it is grouped and how each row reads. */
|
|
22
|
+
export declare function ConversationSidebar<T>({ label, collapsed, onToggle, toggleLabels, resize, groups, getKey, renderRow, loading, placeholder, top, rail, footer, cycle, groupTestId, children, }: {
|
|
23
|
+
label?: string;
|
|
24
|
+
collapsed: boolean;
|
|
25
|
+
onToggle: () => void;
|
|
26
|
+
toggleLabels: {
|
|
27
|
+
open: string;
|
|
28
|
+
close: string;
|
|
29
|
+
};
|
|
30
|
+
/** Omit for a fixed width. */
|
|
31
|
+
resize?: {
|
|
32
|
+
storageKey: string;
|
|
33
|
+
label: string;
|
|
34
|
+
hint: string;
|
|
35
|
+
};
|
|
36
|
+
groups: ConversationSidebarGroup<T>[];
|
|
37
|
+
getKey: (item: T) => string;
|
|
38
|
+
/** One `SidebarRow`. */
|
|
39
|
+
renderRow: (item: T) => ReactNode;
|
|
40
|
+
loading?: boolean;
|
|
41
|
+
/** Shown instead of the groups when there are none, such as an empty
|
|
42
|
+
* state, a search with no matches, or an error. */
|
|
43
|
+
placeholder?: ReactNode;
|
|
44
|
+
/** Above the list, collapsed or not: new-item buttons and search. */
|
|
45
|
+
top?: ReactNode;
|
|
46
|
+
/** What the collapsed rail holds, as `SidebarRailItem`s. */
|
|
47
|
+
rail?: ReactNode;
|
|
48
|
+
/** Beside the toggle at the bottom, while expanded. */
|
|
49
|
+
footer?: ReactNode;
|
|
50
|
+
/** ⌘⌥↑ and ⌘⌥↓ (Ctrl+Alt elsewhere) select the previous or next item. */
|
|
51
|
+
cycle?: {
|
|
52
|
+
current: T | undefined;
|
|
53
|
+
onSelect: (item: T) => void;
|
|
54
|
+
};
|
|
55
|
+
groupTestId?: string;
|
|
56
|
+
/** Dialogs and menus that belong to the list. */
|
|
57
|
+
children?: ReactNode;
|
|
58
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { positionKeys } from "../../components/keys.js";
|
|
2
|
+
import { SidebarGroupHeading, SidebarPanel, SidebarToggle } from "./sidebar.js";
|
|
3
|
+
import cx from "classnames";
|
|
4
|
+
import { Fragment, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function clampSidebarWidth(width) {
|
|
7
|
+
return Math.min(480, Math.max(200, width));
|
|
8
|
+
}
|
|
9
|
+
function readStoredSidebarWidth(storageKey) {
|
|
10
|
+
try {
|
|
11
|
+
const stored = Number(window.localStorage.getItem(storageKey));
|
|
12
|
+
return Number.isFinite(stored) && stored > 0 ? clampSidebarWidth(stored) : 256;
|
|
13
|
+
} catch {
|
|
14
|
+
return 256;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function storeSidebarWidth(storageKey, width) {
|
|
18
|
+
try {
|
|
19
|
+
window.localStorage.setItem(storageKey, String(width));
|
|
20
|
+
} catch {}
|
|
21
|
+
}
|
|
22
|
+
/** A sidebar width the reader drags, nudges or resets, kept per list under
|
|
23
|
+
* its own storage key. */
|
|
24
|
+
function useResizableSidebarWidth(storageKey) {
|
|
25
|
+
const [width, setWidth] = useState(() => readStoredSidebarWidth(storageKey));
|
|
26
|
+
const [resizing, setResizing] = useState(false);
|
|
27
|
+
const widthRef = useRef(width);
|
|
28
|
+
widthRef.current = width;
|
|
29
|
+
const startResize = (e) => {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
const startX = e.clientX;
|
|
32
|
+
const startWidth = widthRef.current;
|
|
33
|
+
setResizing(true);
|
|
34
|
+
const onMove = (ev) => {
|
|
35
|
+
setWidth(clampSidebarWidth(startWidth + ev.clientX - startX));
|
|
36
|
+
};
|
|
37
|
+
const onUp = () => {
|
|
38
|
+
window.removeEventListener("pointermove", onMove);
|
|
39
|
+
window.removeEventListener("pointerup", onUp);
|
|
40
|
+
setResizing(false);
|
|
41
|
+
storeSidebarWidth(storageKey, widthRef.current);
|
|
42
|
+
};
|
|
43
|
+
window.addEventListener("pointermove", onMove);
|
|
44
|
+
window.addEventListener("pointerup", onUp);
|
|
45
|
+
};
|
|
46
|
+
const reset = () => {
|
|
47
|
+
setWidth(256);
|
|
48
|
+
storeSidebarWidth(storageKey, 256);
|
|
49
|
+
};
|
|
50
|
+
const nudge = (delta) => {
|
|
51
|
+
const next = clampSidebarWidth(widthRef.current + delta);
|
|
52
|
+
setWidth(next);
|
|
53
|
+
storeSidebarWidth(storageKey, next);
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
width,
|
|
57
|
+
resizing,
|
|
58
|
+
startResize,
|
|
59
|
+
reset,
|
|
60
|
+
nudge
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Steps through the listed items in order and wraps, so moving between
|
|
64
|
+
* conversations never needs the pointer. */
|
|
65
|
+
function useItemCycling(ordered, cycle) {
|
|
66
|
+
const latest = useRef({
|
|
67
|
+
ordered,
|
|
68
|
+
cycle
|
|
69
|
+
});
|
|
70
|
+
latest.current = {
|
|
71
|
+
ordered,
|
|
72
|
+
cycle
|
|
73
|
+
};
|
|
74
|
+
const enabled = !!cycle;
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!enabled) return;
|
|
77
|
+
const onKeyDown = (e) => {
|
|
78
|
+
const step = e.key === "ArrowDown" ? 1 : e.key === "ArrowUp" ? -1 : 0;
|
|
79
|
+
const { ordered: items, cycle: c } = latest.current;
|
|
80
|
+
if (!step || !e.altKey || !(e.metaKey || e.ctrlKey) || !c || items.length === 0) return;
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
const at = c.current === void 0 ? -1 : items.indexOf(c.current);
|
|
83
|
+
const next = at < 0 ? items[step > 0 ? 0 : items.length - 1] : items[(at + step + items.length) % items.length];
|
|
84
|
+
if (next !== void 0) c.onSelect(next);
|
|
85
|
+
};
|
|
86
|
+
document.addEventListener("keydown", onKeyDown);
|
|
87
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
88
|
+
}, [enabled]);
|
|
89
|
+
}
|
|
90
|
+
/** A list of conversations beside the thread: headed groups of rows that
|
|
91
|
+
* collapse to a rail, with an optional drag handle for the width. The host
|
|
92
|
+
* decides what is listed, how it is grouped and how each row reads. */
|
|
93
|
+
function ConversationSidebar({ label, collapsed, onToggle, toggleLabels, resize, groups, getKey, renderRow, loading = false, placeholder, top, rail, footer, cycle, groupTestId, children }) {
|
|
94
|
+
const sized = useResizableSidebarWidth(resize?.storageKey ?? "");
|
|
95
|
+
useItemCycling(groups.flatMap((g) => g.items), cycle);
|
|
96
|
+
return /* @__PURE__ */ jsxs(SidebarPanel, {
|
|
97
|
+
collapsed,
|
|
98
|
+
label,
|
|
99
|
+
...resize ? {
|
|
100
|
+
width: sized.width,
|
|
101
|
+
resizing: sized.resizing
|
|
102
|
+
} : {},
|
|
103
|
+
children: [
|
|
104
|
+
resize && !collapsed && /* @__PURE__ */ jsx("div", {
|
|
105
|
+
role: "separator",
|
|
106
|
+
tabIndex: 0,
|
|
107
|
+
"aria-orientation": "vertical",
|
|
108
|
+
"aria-label": resize.label,
|
|
109
|
+
"aria-valuemin": 200,
|
|
110
|
+
"aria-valuemax": 480,
|
|
111
|
+
"aria-valuenow": sized.width,
|
|
112
|
+
title: resize.hint,
|
|
113
|
+
onPointerDown: sized.startResize,
|
|
114
|
+
onDoubleClick: sized.reset,
|
|
115
|
+
onKeyDown: (e) => {
|
|
116
|
+
if (e.key === "ArrowLeft") sized.nudge(-16);
|
|
117
|
+
else if (e.key === "ArrowRight") sized.nudge(16);
|
|
118
|
+
},
|
|
119
|
+
className: cx("absolute inset-y-0 right-0 z-10 w-1 cursor-col-resize touch-none", sized.resizing ? "bg-(--theme-element)" : "hover:bg-(--theme-border)")
|
|
120
|
+
}),
|
|
121
|
+
top,
|
|
122
|
+
collapsed && rail ? /* @__PURE__ */ jsx("ul", {
|
|
123
|
+
className: "flex min-h-0 flex-1 flex-col items-center gap-1 overflow-y-auto py-1",
|
|
124
|
+
children: rail
|
|
125
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
126
|
+
className: cx("min-h-0 flex-1 overflow-y-auto px-2 pb-2 transition-opacity duration-200", collapsed ? "pointer-events-none opacity-0" : "opacity-100"),
|
|
127
|
+
children: loading ? /* @__PURE__ */ jsx("div", {
|
|
128
|
+
className: "space-y-3 px-2 pt-2",
|
|
129
|
+
children: positionKeys(5, "skeleton").map((key, i) => /* @__PURE__ */ jsx("div", {
|
|
130
|
+
className: "animate-pulse",
|
|
131
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
132
|
+
className: "h-4 rounded theme-muted-panel",
|
|
133
|
+
style: { width: `${70 - i * 10}%` }
|
|
134
|
+
})
|
|
135
|
+
}, key))
|
|
136
|
+
}) : groups.length === 0 ? placeholder : groups.map((group) => /* @__PURE__ */ jsxs("div", {
|
|
137
|
+
className: "mb-2",
|
|
138
|
+
children: [/* @__PURE__ */ jsx(SidebarGroupHeading, {
|
|
139
|
+
tone: group.tone ?? "default",
|
|
140
|
+
testId: groupTestId,
|
|
141
|
+
children: group.label
|
|
142
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
143
|
+
className: "space-y-0.5",
|
|
144
|
+
children: group.items.map((item) => /* @__PURE__ */ jsx(Fragment, { children: renderRow(item) }, getKey(item)))
|
|
145
|
+
})]
|
|
146
|
+
}, group.key))
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ jsxs("div", {
|
|
149
|
+
className: cx("flex shrink-0 items-center gap-1 p-1.5", collapsed ? "justify-center" : footer ? "justify-between" : "justify-end"),
|
|
150
|
+
children: [!collapsed && footer, /* @__PURE__ */ jsx(SidebarToggle, {
|
|
151
|
+
collapsed,
|
|
152
|
+
onToggle,
|
|
153
|
+
openLabel: toggleLabels.open,
|
|
154
|
+
closeLabel: toggleLabels.close
|
|
155
|
+
})]
|
|
156
|
+
}),
|
|
157
|
+
children
|
|
158
|
+
]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { ConversationSidebar, useResizableSidebarWidth };
|
package/dist/ai/ui/Notice.d.ts
CHANGED
|
@@ -10,6 +10,14 @@ export declare function NoticeBar({ tone, title, children, action, onAction, tes
|
|
|
10
10
|
onAction?: () => void;
|
|
11
11
|
testId?: string;
|
|
12
12
|
}): import("react").JSX.Element;
|
|
13
|
+
/** What a command printed, kept verbatim under the command that printed it. */
|
|
14
|
+
export declare function OutputCard({ title, text, onDismiss, dismissLabel, testId, }: {
|
|
15
|
+
title: string;
|
|
16
|
+
text: string;
|
|
17
|
+
onDismiss?: () => void;
|
|
18
|
+
dismissLabel?: string;
|
|
19
|
+
testId?: string;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
13
21
|
/** An inline notice next to the composer. One with no dismiss states a fact
|
|
14
22
|
* that is still true; one with a dismiss is a message the reader can put away. */
|
|
15
23
|
export declare function NoticeCard({ tone, children, onDismiss, dismissLabel, testId, }: {
|