@parallelworks/ui 0.34.0 → 0.35.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.
Files changed (84) hide show
  1. package/README.md +201 -0
  2. package/dist/ai/components/ChatEmptyState.js +71 -86
  3. package/dist/ai/components/ChatInput.d.ts +11 -3
  4. package/dist/ai/components/ChatInput.js +19 -11
  5. package/dist/ai/components/ChatSidebar.js +12 -130
  6. package/dist/ai/components/ChatStage.d.ts +11 -0
  7. package/dist/ai/components/ChatStage.js +26 -0
  8. package/dist/ai/components/ComposerChrome.d.ts +3 -0
  9. package/dist/ai/components/ComposerChrome.js +21 -2
  10. package/dist/ai/components/conversationActions.d.ts +23 -0
  11. package/dist/ai/components/conversationActions.js +108 -0
  12. package/dist/ai/index.d.ts +4 -3
  13. package/dist/ai/index.js +6 -5
  14. package/dist/ai/strings.d.ts +1 -0
  15. package/dist/ai/strings.js +2 -1
  16. package/dist/ai/ui/ConversationSidebar.d.ts +3 -2
  17. package/dist/ai/ui/ConversationSidebar.js +2 -1
  18. package/dist/ai/ui/sidebar.d.ts +6 -0
  19. package/dist/ai/ui/sidebar.js +10 -1
  20. package/dist/components/Dropdown.js +3 -1
  21. package/dist/components/FilterPill.js +1 -0
  22. package/dist/components/Provider.d.ts +14 -0
  23. package/dist/components/Provider.js +14 -3
  24. package/dist/components/SegmentedControl.d.ts +24 -0
  25. package/dist/components/SegmentedControl.js +69 -0
  26. package/dist/editing.d.ts +32 -0
  27. package/dist/editor/FormCodePanel.js +1 -1
  28. package/dist/engine.d.ts +3 -0
  29. package/dist/form/FieldWrapper.js +1 -1
  30. package/dist/form/Fields/CheckboxGroupField.d.ts +3 -3
  31. package/dist/form/Fields/CheckboxGroupField.js +10 -4
  32. package/dist/form/Fields/RadioField.d.ts +5 -3
  33. package/dist/form/Fields/RadioField.js +25 -17
  34. package/dist/form/Fields/fieldOption.d.ts +12 -0
  35. package/dist/form/Fields/fieldOption.js +17 -0
  36. package/dist/form/Form.d.ts +4 -2
  37. package/dist/form/Form.js +170 -68
  38. package/dist/form/FormLayout.d.ts +7 -0
  39. package/dist/form/FormLayout.js +144 -0
  40. package/dist/form/SpecialTypes/FormikCustomDropdown.js +1 -1
  41. package/dist/form/SpecialTypes/FormikCustomInput.js +7 -0
  42. package/dist/form/Wizard/WizardContainer.d.ts +1 -1
  43. package/dist/form/Wizard/WizardContainer.js +244 -49
  44. package/dist/form/Wizard/WizardNavigation.js +4 -4
  45. package/dist/form/Wizard/WizardStepContent.d.ts +1 -1
  46. package/dist/form/Wizard/WizardStepContent.js +19 -10
  47. package/dist/form/Wizard/WizardStepIndicator.js +35 -7
  48. package/dist/form/Wizard/types.d.ts +19 -6
  49. package/dist/form/Wizard/useWizardState.d.ts +1 -0
  50. package/dist/form/Wizard/useWizardState.js +5 -1
  51. package/dist/form/Wizard/utils.d.ts +7 -0
  52. package/dist/form/Wizard/utils.js +18 -1
  53. package/dist/form/index.d.ts +2 -0
  54. package/dist/form/index.js +4 -2
  55. package/dist/form/layout.d.ts +50 -0
  56. package/dist/form/layout.js +125 -0
  57. package/dist/form/layoutCSS.d.ts +10 -0
  58. package/dist/form/layoutCSS.js +234 -0
  59. package/dist/form/lib.d.ts +2 -0
  60. package/dist/form/lib.js +17 -2
  61. package/dist/form/types/fieldTypes.d.ts +8 -23
  62. package/dist/graph/DependencyGraph.d.ts +20 -1
  63. package/dist/graph/DependencyGraph.js +437 -142
  64. package/dist/graph/MatrixGroup.d.ts +3 -1
  65. package/dist/graph/MatrixGroup.js +31 -28
  66. package/dist/graph/TreeView.js +6 -6
  67. package/dist/graph/gridSpacing.d.ts +7 -0
  68. package/dist/graph/gridSpacing.js +10 -0
  69. package/dist/graph/util.d.ts +2 -0
  70. package/dist/graph/util.js +2 -2
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.js +2 -1
  73. package/dist/list/ListViewControls.d.ts +15 -0
  74. package/dist/list/ListViewControls.js +9 -3
  75. package/dist/list/index.d.ts +1 -1
  76. package/dist/list/index.js +2 -2
  77. package/dist/styles/base.css +3 -1
  78. package/dist/styles/theme.css +5 -0
  79. package/dist/styles.css +1 -1
  80. package/dist/theme/index.d.ts +2 -0
  81. package/dist/theme/index.js +3 -2
  82. package/package.json +4 -2
  83. package/dist/ai/components/ComposerFrame.d.ts +0 -16
  84. package/dist/ai/components/ComposerFrame.js +0 -33
@@ -1,76 +1,21 @@
1
- import { BiSolidTrashAlt } from "../../icons/bi/BiSolidTrashAlt.js";
2
- import { FaCloudDownloadAlt } from "../../icons/fa/FaCloudDownloadAlt.js";
3
- import { FaEdit } from "../../icons/fa/FaEdit.js";
4
1
  import { FaRegFileImage } from "../../icons/fa/FaRegFileImage.js";
5
2
  import { FaSearch } from "../../icons/fa/FaSearch.js";
6
3
  import { FaUsers } from "../../icons/fa/FaUsers.js";
7
- import { IoSettingsOutline } from "../../icons/io5/IoSettingsOutline.js";
8
- import { PiShareBold } from "../../icons/pi/PiShareBold.js";
9
4
  import { TbEdit } from "../../icons/tb/TbEdit.js";
10
- import { useRowMenu } from "../../list/RowContextMenu.js";
11
5
  import { useChatConfig } from "../core/config.js";
12
6
  import { useChat } from "../core/ChatProvider.js";
7
+ import { ManageProvidersLink } from "./ComposerChrome.js";
13
8
  import { FOCUS_SIDEBAR_SEARCH_EVENT } from "../core/events.js";
14
- import { SidebarRow, useRowDialogs } from "../ui/sidebar.js";
15
- import { conversationToMarkdown, downloadText, exportFilename } from "../core/exportConversation.js";
9
+ import { SidebarPlaceholder, SidebarRow } from "../ui/sidebar.js";
16
10
  import { ConversationSidebar } from "../ui/ConversationSidebar.js";
17
- import ShareDialog from "./ShareDialog.js";
11
+ import { groupConversationsByDate, useConversationActions } from "./conversationActions.js";
18
12
  import cx from "classnames";
19
13
  import { useEffect, useMemo, useRef, useState } from "react";
20
14
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
21
- import { DateTime } from "luxon";
22
15
  //#region src/ai/components/ChatSidebar.tsx
23
- var SIDEBAR_WIDTH_STORAGE_KEY = "aiChatSidebarWidth";
24
- function groupByDate(conversations) {
25
- const now = DateTime.now();
26
- const groups = {
27
- groupToday: [],
28
- groupYesterday: [],
29
- groupThisWeek: [],
30
- groupThisMonth: [],
31
- groupOlder: []
32
- };
33
- for (const conv of conversations) {
34
- const dt = DateTime.fromISO(conv.createdAt);
35
- const diff = now.diff(dt, "days").days;
36
- if (diff < 1) groups.groupToday.push(conv);
37
- else if (diff < 2) groups.groupYesterday.push(conv);
38
- else if (diff < 7) groups.groupThisWeek.push(conv);
39
- else if (diff < 30) groups.groupThisMonth.push(conv);
40
- else groups.groupOlder.push(conv);
41
- }
42
- return Object.entries(groups).filter(([, items]) => items.length > 0);
43
- }
44
- function ConversationRow({ conv, isCurrent, sharingAvailable, openMenu, onRename, onShare, onDelete, onExport, onOpen }) {
16
+ function ConversationRow({ conv, isCurrent, menuItems, openMenu, onOpen }) {
45
17
  const { LinkComponent, strings } = useChatConfig();
46
18
  const t = strings.sidebar;
47
- const menuItems = [
48
- {
49
- kind: "action",
50
- label: t.rename,
51
- icon: /* @__PURE__ */ jsx(FaEdit, {}),
52
- onSelect: () => onRename(conv)
53
- },
54
- ...sharingAvailable ? [{
55
- kind: "action",
56
- label: t.share,
57
- icon: /* @__PURE__ */ jsx(PiShareBold, {}),
58
- onSelect: () => onShare(conv.id)
59
- }] : [],
60
- {
61
- kind: "action",
62
- label: strings.chrome.download,
63
- icon: /* @__PURE__ */ jsx(FaCloudDownloadAlt, {}),
64
- onSelect: () => onExport(conv.id)
65
- },
66
- {
67
- kind: "action",
68
- label: t.delete,
69
- icon: /* @__PURE__ */ jsx(BiSolidTrashAlt, {}),
70
- destructive: true,
71
- onSelect: () => onDelete(conv)
72
- }
73
- ];
74
19
  return /* @__PURE__ */ jsx(SidebarRow, {
75
20
  selected: isCurrent,
76
21
  menuItems,
@@ -93,28 +38,11 @@ function ConversationRow({ conv, isCurrent, sharingAvailable, openMenu, onRename
93
38
  });
94
39
  }
95
40
  function ChatSidebar({ actions = true, top }) {
96
- const { extraLinks, LinkComponent, strings } = useChatConfig();
41
+ const { LinkComponent, strings } = useChatConfig();
97
42
  const tSidebar = strings.sidebar;
98
- const { adapter, navigation, notify, activeConversationId, conversations, sidebar, sidebarPresentation, drawerOpen, setSidebar, setDrawerOpen, toggleSidebar, isLoading, loadConversations, deleteConversation, updateConversationTitle, clearCurrentConversation } = useChat();
99
- const sharingAvailable = !!adapter.sharing;
43
+ const { adapter, navigation, activeConversationId, conversations, sidebar, sidebarPresentation, drawerOpen, setSidebar, setDrawerOpen, toggleSidebar, isLoading, loadConversations, clearCurrentConversation } = useChat();
100
44
  const attachmentsAvailable = !!adapter.attachments;
101
- const [shareDialogOpen, setShareDialogOpen] = useState(false);
102
- const [conversationToShare, setConversationToShare] = useState(null);
103
- const { openMenu, contextMenu } = useRowMenu();
104
- const { requestDelete, requestRename, dialogs } = useRowDialogs({
105
- strings: {
106
- deleteTitle: tSidebar.deleteTitle,
107
- deleteBody: () => tSidebar.deleteBody,
108
- deleteAction: tSidebar.deleteAction,
109
- renameTitle: tSidebar.renameTitle,
110
- renameLabel: tSidebar.renameLabel,
111
- renamePlaceholder: tSidebar.renamePlaceholder,
112
- renameAction: tSidebar.renameAction
113
- },
114
- nameOf: (conv) => conv.title || tSidebar.untitled,
115
- onDelete: (conv) => deleteConversation(conv.id),
116
- onRename: (conv, title) => updateConversationTitle(conv.id, title)
117
- });
45
+ const { menuItems, openMenu, overlays } = useConversationActions();
118
46
  const drawer = sidebarPresentation === "drawer";
119
47
  const sidebarCollapsed = !drawer && sidebar !== "expanded";
120
48
  const closeDrawer = () => setDrawerOpen(false);
@@ -146,23 +74,11 @@ function ChatSidebar({ actions = true, top }) {
146
74
  search,
147
75
  tSidebar.untitled
148
76
  ]);
149
- const groups = useMemo(() => groupByDate(filteredConversations).map(([key, items]) => ({
77
+ const groups = useMemo(() => groupConversationsByDate(filteredConversations).map(({ key, items }) => ({
150
78
  key,
151
79
  label: tSidebar[key],
152
80
  items
153
81
  })), [filteredConversations, tSidebar]);
154
- const handleExport = async (id) => {
155
- try {
156
- const conversation = await adapter.conversations.get(id);
157
- downloadText(exportFilename(conversation.title), conversationToMarkdown(conversation));
158
- } catch {
159
- notify.error(strings.chrome.exportError);
160
- }
161
- };
162
- const handleShareClick = (id) => {
163
- setConversationToShare(id);
164
- setShareDialogOpen(true);
165
- };
166
82
  const handleNewChat = () => {
167
83
  closeDrawer();
168
84
  clearCurrentConversation();
@@ -182,7 +98,6 @@ function ChatSidebar({ actions = true, top }) {
182
98
  close: tSidebar.closeSidebar
183
99
  },
184
100
  resize: {
185
- storageKey: SIDEBAR_WIDTH_STORAGE_KEY,
186
101
  label: tSidebar.resizeLabel,
187
102
  hint: tSidebar.resizeHint
188
103
  },
@@ -191,22 +106,12 @@ function ChatSidebar({ actions = true, top }) {
191
106
  renderRow: (conv) => /* @__PURE__ */ jsx(ConversationRow, {
192
107
  conv,
193
108
  isCurrent: activeConversationId === conv.id,
194
- sharingAvailable,
109
+ menuItems: menuItems(conv),
195
110
  openMenu,
196
- onRename: requestRename,
197
- onShare: handleShareClick,
198
- onDelete: requestDelete,
199
- onExport: handleExport,
200
111
  onOpen: closeDrawer
201
112
  }),
202
113
  loading: isLoading,
203
- placeholder: conversations.length === 0 ? /* @__PURE__ */ jsx("p", {
204
- className: "theme-muted-text text-sm px-2 whitespace-nowrap",
205
- children: tSidebar.noConversations
206
- }) : /* @__PURE__ */ jsx("p", {
207
- className: "theme-muted-text text-sm px-2",
208
- children: strings.chrome.noMatches
209
- }),
114
+ placeholder: /* @__PURE__ */ jsx(SidebarPlaceholder, { children: conversations.length === 0 ? tSidebar.noConversations : strings.chrome.noMatches }),
210
115
  cycle: {
211
116
  current: filteredConversations.find((conv) => conv.id === activeConversationId),
212
117
  onSelect: (conv) => navigation.toConversation(conv.id)
@@ -253,32 +158,9 @@ function ChatSidebar({ actions = true, top }) {
253
158
  })]
254
159
  })
255
160
  })] })] }),
256
- footer: extraLinks.manageProviders && /* @__PURE__ */ jsxs(LinkComponent, {
257
- target: {
258
- kind: "external",
259
- href: extraLinks.manageProviders
260
- },
261
- 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",
262
- title: tSidebar.manageProviders,
263
- children: [/* @__PURE__ */ jsx(IoSettingsOutline, { className: "h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
264
- className: "truncate",
265
- children: tSidebar.manageProviders
266
- })]
267
- })
161
+ footer: /* @__PURE__ */ jsx(ManageProvidersLink, {})
268
162
  });
269
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
270
- (drawer || sidebar !== "hidden") && list,
271
- contextMenu,
272
- dialogs,
273
- conversationToShare && /* @__PURE__ */ jsx(ShareDialog, {
274
- conversationId: conversationToShare,
275
- isOpen: shareDialogOpen,
276
- onClose: () => {
277
- setShareDialogOpen(false);
278
- setConversationToShare(null);
279
- }
280
- })
281
- ] });
163
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [(drawer || sidebar !== "hidden") && list, overlays] });
282
164
  }
283
165
  //#endregion
284
166
  export { ChatSidebar as default };
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ /** Where a new conversation starts: the heading and the composer together, a
3
+ * little above centre, with what the surface offers next under the box. The
4
+ * composer keeps its place however much is under it, so surfaces that share
5
+ * the stage put it in the same spot; a long list scrolls instead. */
6
+ export default function ChatStage({ heading, subheading, children, }: {
7
+ heading?: ReactNode;
8
+ subheading?: ReactNode;
9
+ /** The composer, then whatever goes under it. */
10
+ children: ReactNode;
11
+ }): import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ //#region src/ai/components/ChatStage.tsx
3
+ /** Where a new conversation starts: the heading and the composer together, a
4
+ * little above centre, with what the surface offers next under the box. The
5
+ * composer keeps its place however much is under it, so surfaces that share
6
+ * the stage put it in the same spot; a long list scrolls instead. */
7
+ function ChatStage({ heading, subheading, children }) {
8
+ return /* @__PURE__ */ jsx("div", {
9
+ className: "flex min-h-0 flex-1 flex-col overflow-y-auto [container-type:size] [scrollbar-gutter:stable_both-edges]",
10
+ children: /* @__PURE__ */ jsxs("div", {
11
+ className: "w-full pt-[max(2rem,24cqh)] pb-[12vh]",
12
+ children: [heading && /* @__PURE__ */ jsxs("div", {
13
+ className: "mx-auto mb-6 max-w-[var(--chat-column,50rem)] px-8 text-center",
14
+ children: [/* @__PURE__ */ jsx("h1", {
15
+ className: "text-[1.75rem] leading-tight font-medium tracking-[-0.02em] text-balance text-(--theme-panel)",
16
+ children: heading
17
+ }), subheading && /* @__PURE__ */ jsx("p", {
18
+ className: "mt-2 text-sm theme-muted-text",
19
+ children: subheading
20
+ })]
21
+ }), children]
22
+ })
23
+ });
24
+ }
25
+ //#endregion
26
+ export { ChatStage as default };
@@ -22,4 +22,7 @@ export declare function ComposerUsage({ children }: {
22
22
  export declare function ComposerControls({ targetSession }: {
23
23
  targetSession?: string | null | undefined;
24
24
  }): import("react").JSX.Element;
25
+ /** Where the reader manages the providers behind the model picker, at the
26
+ * foot of a conversation list. */
27
+ export declare function ManageProvidersLink(): import("react").JSX.Element | null;
25
28
  export declare function ConnectToolsLink(): import("react").JSX.Element | null;
@@ -1,3 +1,4 @@
1
+ import { IoSettingsOutline } from "../../icons/io5/IoSettingsOutline.js";
1
2
  import { PiPlugsConnectedFill } from "../../icons/pi/PiPlugsConnectedFill.js";
2
3
  import { useChatConfig } from "../core/config.js";
3
4
  import AllocationSelector from "./AllocationSelector.js";
@@ -8,7 +9,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
9
  * it: the machine, the workspace, and whatever else names the place. */
9
10
  function ComposerContext({ children }) {
10
11
  return /* @__PURE__ */ jsx("div", {
11
- className: "mb-1.5 flex flex-wrap items-center gap-1.5",
12
+ className: "mb-1.5 flex min-h-7 flex-wrap items-center gap-1.5",
12
13
  children
13
14
  });
14
15
  }
@@ -45,6 +46,24 @@ function ComposerControls({ targetSession }) {
45
46
  targetSession
46
47
  }), /* @__PURE__ */ jsx(AllocationSelector, { variant: "bare" })] });
47
48
  }
49
+ /** Where the reader manages the providers behind the model picker, at the
50
+ * foot of a conversation list. */
51
+ function ManageProvidersLink() {
52
+ const { extraLinks, LinkComponent, strings } = useChatConfig();
53
+ if (!extraLinks.manageProviders) return null;
54
+ return /* @__PURE__ */ jsxs(LinkComponent, {
55
+ target: {
56
+ kind: "external",
57
+ href: extraLinks.manageProviders
58
+ },
59
+ 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",
60
+ title: strings.sidebar.manageProviders,
61
+ children: [/* @__PURE__ */ jsx(IoSettingsOutline, { className: "h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
62
+ className: "truncate",
63
+ children: strings.sidebar.manageProviders
64
+ })]
65
+ });
66
+ }
48
67
  function ConnectToolsLink() {
49
68
  const { extraLinks, LinkComponent, strings } = useChatConfig();
50
69
  if (!extraLinks.connectTools) return null;
@@ -58,4 +77,4 @@ function ConnectToolsLink() {
58
77
  });
59
78
  }
60
79
  //#endregion
61
- export { ComposerContext, ComposerControls, ComposerSettings, ComposerUsage, ConnectToolsLink };
80
+ export { ComposerContext, ComposerControls, ComposerSettings, ComposerUsage, ConnectToolsLink, ManageProvidersLink };
@@ -0,0 +1,23 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type OpenMenu, type RowMenuItem } from '../../list/index.js';
3
+ import type { ConversationSummary } from '../types.js';
4
+ export type ConversationGroupKey = 'groupToday' | 'groupYesterday' | 'groupThisWeek' | 'groupThisMonth' | 'groupOlder';
5
+ /** Conversations by how long ago they started, newest group first, leaving
6
+ * out the groups with none. Each key names its label in the sidebar strings. */
7
+ export declare function groupConversationsByDate<T extends {
8
+ createdAt: string;
9
+ }>(conversations: T[]): {
10
+ key: ConversationGroupKey;
11
+ items: T[];
12
+ }[];
13
+ /** What a conversation row offers behind its menu (rename, share, download,
14
+ * delete) and the menu and dialogs that carry them out, for any list of
15
+ * conversations. Render `overlays` once beside the list. */
16
+ export declare function useConversationActions({ menuZClassName, }?: {
17
+ /** The menu's stacking class, for a list inside a layer above the page. */
18
+ menuZClassName?: string;
19
+ }): {
20
+ menuItems: (conv: ConversationSummary) => RowMenuItem[];
21
+ openMenu: OpenMenu;
22
+ overlays: ReactNode;
23
+ };
@@ -0,0 +1,108 @@
1
+ import { BiSolidTrashAlt } from "../../icons/bi/BiSolidTrashAlt.js";
2
+ import { FaCloudDownloadAlt } from "../../icons/fa/FaCloudDownloadAlt.js";
3
+ import { FaEdit } from "../../icons/fa/FaEdit.js";
4
+ import { PiShareBold } from "../../icons/pi/PiShareBold.js";
5
+ import { useRowMenu } from "../../list/RowContextMenu.js";
6
+ import { useChatConfig } from "../core/config.js";
7
+ import { useChat } from "../core/ChatProvider.js";
8
+ import { useRowDialogs } from "../ui/sidebar.js";
9
+ import { conversationToMarkdown, downloadText, exportFilename } from "../core/exportConversation.js";
10
+ import ShareDialog from "./ShareDialog.js";
11
+ import { useState } from "react";
12
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
13
+ import { DateTime } from "luxon";
14
+ //#region src/ai/components/conversationActions.tsx
15
+ /** Conversations by how long ago they started, newest group first, leaving
16
+ * out the groups with none. Each key names its label in the sidebar strings. */
17
+ function groupConversationsByDate(conversations) {
18
+ const now = DateTime.now();
19
+ const groups = {
20
+ groupToday: [],
21
+ groupYesterday: [],
22
+ groupThisWeek: [],
23
+ groupThisMonth: [],
24
+ groupOlder: []
25
+ };
26
+ for (const conv of conversations) {
27
+ const days = now.diff(DateTime.fromISO(conv.createdAt), "days").days;
28
+ groups[days < 1 ? "groupToday" : days < 2 ? "groupYesterday" : days < 7 ? "groupThisWeek" : days < 30 ? "groupThisMonth" : "groupOlder"].push(conv);
29
+ }
30
+ return Object.entries(groups).filter(([, items]) => items.length > 0).map(([key, items]) => ({
31
+ key,
32
+ items
33
+ }));
34
+ }
35
+ /** What a conversation row offers behind its menu (rename, share, download,
36
+ * delete) and the menu and dialogs that carry them out, for any list of
37
+ * conversations. Render `overlays` once beside the list. */
38
+ function useConversationActions({ menuZClassName } = {}) {
39
+ const { strings } = useChatConfig();
40
+ const t = strings.sidebar;
41
+ const { adapter, notify, deleteConversation, updateConversationTitle } = useChat();
42
+ const { openMenu, contextMenu } = useRowMenu(menuZClassName);
43
+ const [sharing, setSharing] = useState(null);
44
+ const { requestDelete, requestRename, dialogs } = useRowDialogs({
45
+ strings: {
46
+ deleteTitle: t.deleteTitle,
47
+ deleteBody: () => t.deleteBody,
48
+ deleteAction: t.deleteAction,
49
+ renameTitle: t.renameTitle,
50
+ renameLabel: t.renameLabel,
51
+ renamePlaceholder: t.renamePlaceholder,
52
+ renameAction: t.renameAction
53
+ },
54
+ nameOf: (conv) => conv.title || t.untitled,
55
+ onDelete: (conv) => deleteConversation(conv.id),
56
+ onRename: (conv, title) => updateConversationTitle(conv.id, title)
57
+ });
58
+ const download = async (id) => {
59
+ try {
60
+ const conversation = await adapter.conversations.get(id);
61
+ downloadText(exportFilename(conversation.title), conversationToMarkdown(conversation));
62
+ } catch {
63
+ notify.error(strings.chrome.exportError);
64
+ }
65
+ };
66
+ const menuItems = (conv) => [
67
+ {
68
+ kind: "action",
69
+ label: t.rename,
70
+ icon: /* @__PURE__ */ jsx(FaEdit, {}),
71
+ onSelect: () => requestRename(conv)
72
+ },
73
+ ...adapter.sharing && conv.isOwner !== false ? [{
74
+ kind: "action",
75
+ label: t.share,
76
+ icon: /* @__PURE__ */ jsx(PiShareBold, {}),
77
+ onSelect: () => setSharing(conv.id)
78
+ }] : [],
79
+ {
80
+ kind: "action",
81
+ label: strings.chrome.download,
82
+ icon: /* @__PURE__ */ jsx(FaCloudDownloadAlt, {}),
83
+ onSelect: () => void download(conv.id)
84
+ },
85
+ {
86
+ kind: "action",
87
+ label: t.delete,
88
+ icon: /* @__PURE__ */ jsx(BiSolidTrashAlt, {}),
89
+ destructive: true,
90
+ onSelect: () => requestDelete(conv)
91
+ }
92
+ ];
93
+ return {
94
+ menuItems,
95
+ openMenu,
96
+ overlays: /* @__PURE__ */ jsxs(Fragment$1, { children: [
97
+ contextMenu,
98
+ dialogs,
99
+ sharing && /* @__PURE__ */ jsx(ShareDialog, {
100
+ conversationId: sharing,
101
+ isOpen: true,
102
+ onClose: () => setSharing(null)
103
+ })
104
+ ] })
105
+ };
106
+ }
107
+ //#endregion
108
+ export { groupConversationsByDate, useConversationActions };
@@ -15,9 +15,10 @@ export { CHAT_COLUMN_SCOPE, ConversationLayout, DRAWER_BELOW_PX, default as Chat
15
15
  export { default as ChatMessageView } from './components/ChatMessage.js';
16
16
  export { default as ChatMessageList } from './components/ChatMessageList.js';
17
17
  export { default as ChatSidebar } from './components/ChatSidebar.js';
18
+ export { default as ChatStage } from './components/ChatStage.js';
18
19
  export { default as ChatThread } from './components/ChatThread.js';
19
- export { ComposerContext, ComposerControls, ComposerSettings, ComposerUsage, ConnectToolsLink, } from './components/ComposerChrome.js';
20
- export { default as ComposerFrame } from './components/ComposerFrame.js';
20
+ export { ComposerContext, ComposerControls, ComposerSettings, ComposerUsage, ConnectToolsLink, ManageProvidersLink, } from './components/ComposerChrome.js';
21
+ export { type ConversationGroupKey, groupConversationsByDate, useConversationActions, } from './components/conversationActions.js';
21
22
  export { default as DragOverlay } from './components/DragOverlay.js';
22
23
  export { default as ModelSelector, getProviderKeyFromModelId, } from './components/ModelSelector.js';
23
24
  export { default as ProviderIssueBanner } from './components/ProviderIssueBanner.js';
@@ -42,5 +43,5 @@ export * from './types.js';
42
43
  export { ConversationSidebar, type ConversationSidebarGroup, useResizableSidebarWidth, } from './ui/ConversationSidebar.js';
43
44
  export { type DropdownOption, default as Dropdown } from './ui/Dropdown.js';
44
45
  export { NoticeBar, NoticeCard, type NoticeTone, OutputCard } from './ui/Notice.js';
45
- export { RenameDialog, type RowDialogStrings, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs, } from './ui/sidebar.js';
46
+ export { RenameDialog, type RowDialogStrings, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs, } from './ui/sidebar.js';
46
47
  export { formatFileSize, isMac, isOrgModel } from './utils.js';
package/dist/ai/index.js CHANGED
@@ -25,24 +25,25 @@ import BlockedGroupBanner from "./components/BlockedGroupBanner.js";
25
25
  import BranchNavigator from "./components/BranchNavigator.js";
26
26
  import { getGreeting } from "./core/greeting.js";
27
27
  import useDragDrop from "./core/useDragDrop.js";
28
- import { ComposerContext, ComposerControls, ComposerSettings, ComposerUsage, ConnectToolsLink } from "./components/ComposerChrome.js";
28
+ import { NoticeBar, NoticeCard, OutputCard } from "./ui/Notice.js";
29
+ import { ComposerContext, ComposerControls, ComposerSettings, ComposerUsage, ConnectToolsLink, ManageProvidersLink } from "./components/ComposerChrome.js";
29
30
  import DragOverlay from "./components/DragOverlay.js";
30
31
  import { pasteInlineMaxBytes } from "./core/pastes.js";
31
32
  import { slashMatches } from "./components/SlashMenu.js";
32
33
  import ChatInput from "./components/ChatInput.js";
34
+ import ChatStage from "./components/ChatStage.js";
33
35
  import ProviderIssueBanner from "./components/ProviderIssueBanner.js";
34
36
  import ChatEmptyState from "./components/ChatEmptyState.js";
35
37
  import { FOCUS_SIDEBAR_SEARCH_EVENT } from "./core/events.js";
36
38
  import { useKeyboardShortcuts } from "./core/useKeyboardShortcuts.js";
37
39
  import { KeyboardShortcutsProvider } from "./core/KeyboardShortcutsProvider.js";
38
- import { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs } from "./ui/sidebar.js";
40
+ import { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs } from "./ui/sidebar.js";
39
41
  import { ConversationSidebar, useResizableSidebarWidth } from "./ui/ConversationSidebar.js";
40
42
  import ShareDialog, { ShareButton } from "./components/ShareDialog.js";
43
+ import { groupConversationsByDate, useConversationActions } from "./components/conversationActions.js";
41
44
  import ChatSidebar from "./components/ChatSidebar.js";
42
45
  import ChatLayout, { CHAT_COLUMN_SCOPE, ConversationLayout, DRAWER_BELOW_PX } from "./components/ChatLayout.js";
43
46
  import ChatMessage_default from "./components/ChatMessage.js";
44
47
  import ChatMessageList from "./components/ChatMessageList.js";
45
48
  import ChatThread from "./components/ChatThread.js";
46
- import ComposerFrame from "./components/ComposerFrame.js";
47
- import { NoticeBar, NoticeCard, OutputCard } from "./ui/Notice.js";
48
- export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, CHAT_COLUMN_SCOPE, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatThread, ComposerContext, ComposerControls, ComposerFrame, ComposerSettings, ComposerUsage, ConnectToolsLink, ConversationLayout, ConversationSidebar, DEFAULT_PERMISSION_MODE, DRAWER_BELOW_PX, 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, SidebarDrawer, 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 };
49
+ export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, CHAT_COLUMN_SCOPE, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatStage, ChatThread, ComposerContext, ComposerControls, ComposerSettings, ComposerUsage, ConnectToolsLink, ConversationLayout, ConversationSidebar, DEFAULT_PERMISSION_MODE, DRAWER_BELOW_PX, DragOverlay, Dropdown, FOCUS_SIDEBAR_SEARCH_EVENT, INTERRUPTED_NOTICE, KeyboardShortcutsProvider, MAX_EDIT_DIFF_LINES, MAX_WRITE_DIFF_LINES, ManageProvidersLink, ModelSelector, NAMED_PERMISSION_MODES, NoticeBar, NoticeCard, OutputCard, PermissionPicker, ProviderIssueBanner, RenameDialog, ShareButton, ShareDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, 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, groupConversationsByDate, 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, useConversationActions, useDragDrop, useKeyboardShortcuts, useResizableSidebarWidth, useRowDialogs, wordEmphasisRanges };
@@ -143,6 +143,7 @@ export interface ChatStrings {
143
143
  download: string;
144
144
  exportError: string;
145
145
  jumpToLatest: string;
146
+ dismiss: string;
146
147
  };
147
148
  messageMeta: {
148
149
  stopped: string;
@@ -142,7 +142,8 @@ var defaultChatStrings = {
142
142
  noMatches: "No conversations match your search",
143
143
  download: "Download",
144
144
  exportError: "Failed to export conversation",
145
- jumpToLatest: "Jump to latest"
145
+ jumpToLatest: "Jump to latest",
146
+ dismiss: "Dismiss"
146
147
  },
147
148
  messageMeta: {
148
149
  stopped: "Stopped",
@@ -27,9 +27,10 @@ export declare function ConversationSidebar<T>({ label, collapsed: collapsedProp
27
27
  open: string;
28
28
  close: string;
29
29
  };
30
- /** Omit for a fixed width. */
30
+ /** Omit for a fixed width. A `storageKey` keeps this list's width apart
31
+ * from the others'. */
31
32
  resize?: {
32
- storageKey: string;
33
+ storageKey?: string;
33
34
  label: string;
34
35
  hint: string;
35
36
  };
@@ -3,6 +3,7 @@ import { SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarToggle } from
3
3
  import cx from "classnames";
4
4
  import { Fragment, useEffect, useRef, useState } from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
+ var SHARED_WIDTH_STORAGE_KEY = "aiChatSidebarWidth";
6
7
  function clampSidebarWidth(width) {
7
8
  return Math.min(480, Math.max(200, width));
8
9
  }
@@ -91,7 +92,7 @@ function useItemCycling(ordered, cycle) {
91
92
  * collapse to a rail, with an optional drag handle for the width. The host
92
93
  * decides what is listed, how it is grouped and how each row reads. */
93
94
  function ConversationSidebar({ label, collapsed: collapsedProp, onToggle, toggleLabels, resize, fill: fillProp = false, drawer, groups, getKey, renderRow, loading = false, placeholder, top, rail, footer, cycle, groupTestId, children }) {
94
- const sized = useResizableSidebarWidth(resize?.storageKey ?? "");
95
+ const sized = useResizableSidebarWidth(resize?.storageKey ?? SHARED_WIDTH_STORAGE_KEY);
95
96
  useItemCycling(groups.flatMap((g) => g.items), cycle);
96
97
  const fill = fillProp || !!drawer;
97
98
  const collapsed = collapsedProp && !fill;
@@ -37,6 +37,12 @@ export declare function SidebarGroupHeading({ children, tone, testId, }: {
37
37
  tone?: 'default' | 'attention';
38
38
  testId?: string | undefined;
39
39
  }): import("react").JSX.Element;
40
+ /** What a list says in place of its rows: none yet, no matches, or why it
41
+ * could not load. Plain text, so an empty list reads the same in every list. */
42
+ export declare function SidebarPlaceholder({ children, testId, }: {
43
+ children: ReactNode;
44
+ testId?: string | undefined;
45
+ }): import("react").JSX.Element;
40
46
  /** One entry in the list: the row highlights while it is the open one or
41
47
  * while its menu is up, and the menu opens from its button or a right-click. */
42
48
  export declare function SidebarRow({ selected, menuItems, openMenu, className, children, }: {
@@ -76,6 +76,15 @@ function SidebarGroupHeading({ children, tone = "default", testId }) {
76
76
  children
77
77
  });
78
78
  }
79
+ /** What a list says in place of its rows: none yet, no matches, or why it
80
+ * could not load. Plain text, so an empty list reads the same in every list. */
81
+ function SidebarPlaceholder({ children, testId }) {
82
+ return /* @__PURE__ */ jsx("p", {
83
+ "data-testid": testId,
84
+ className: "px-2 pt-1 text-sm theme-muted-text",
85
+ children
86
+ });
87
+ }
79
88
  /** One entry in the list: the row highlights while it is the open one or
80
89
  * while its menu is up, and the menu opens from its button or a right-click. */
81
90
  function SidebarRow({ selected, menuItems, openMenu, className, children }) {
@@ -182,4 +191,4 @@ function useRowDialogs({ strings, nameOf, onDelete, onRename }) {
182
191
  };
183
192
  }
184
193
  //#endregion
185
- export { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs };
194
+ export { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs };
@@ -194,6 +194,7 @@ function DropdownHelper({ wrapperClassName = "w-full", className, textBoxClassNa
194
194
  modifiers: popperModifiers
195
195
  });
196
196
  const debounceRef = useRef(null);
197
+ const typedRef = useRef("");
197
198
  const buttonRef = useRef(null);
198
199
  const [expandedCategories, setExpandedCategories] = useState(/* @__PURE__ */ new Set());
199
200
  const selectedCategory = useMemo(() => options.find((o) => isCategory(o) && o.options.some((opt) => checkEquals(value.value, opt.value)))?.category, [options, value]);
@@ -213,6 +214,7 @@ function DropdownHelper({ wrapperClassName = "w-full", className, textBoxClassNa
213
214
  const onQueryChange = (val) => {
214
215
  setQuery(val);
215
216
  if (allowCustomValue) {
217
+ typedRef.current = val;
216
218
  if (debounceRef.current) clearTimeout(debounceRef.current);
217
219
  debounceRef.current = setTimeout(() => {
218
220
  flushCustomValue(val);
@@ -224,7 +226,7 @@ function DropdownHelper({ wrapperClassName = "w-full", className, textBoxClassNa
224
226
  if (allowCustomValue && debounceRef.current) {
225
227
  clearTimeout(debounceRef.current);
226
228
  debounceRef.current = null;
227
- flushCustomValue(query);
229
+ flushCustomValue(typedRef.current);
228
230
  }
229
231
  setQuery("");
230
232
  };
@@ -4,6 +4,7 @@ import { jsx } from "react/jsx-runtime";
4
4
  function FilterPill({ active, onClick, children }) {
5
5
  return /* @__PURE__ */ jsx("button", {
6
6
  type: "button",
7
+ "aria-pressed": active,
7
8
  onClick,
8
9
  className: cx("px-3 py-1.5 text-xs font-medium rounded-full border transition-all duration-200", active ? "bg-[var(--theme-panel-bg)] border-[var(--theme-border)] text-[var(--theme-panel)] shadow-sm" : "bg-transparent border-transparent text-[var(--theme-muted-text-color)] hover:text-[var(--theme-panel)] hover:bg-[var(--theme-hover)]"),
9
10
  children
@@ -100,6 +100,13 @@ export interface UIStrings {
100
100
  submitted: string;
101
101
  expandAll: string;
102
102
  collapseAll: string;
103
+ zoomIn: string;
104
+ zoomOut: string;
105
+ resetView: string;
106
+ previousGraph: string;
107
+ nextGraph: string;
108
+ matrixOf: (name: string) => string;
109
+ jobCount: (count: number) => string;
103
110
  loadingLogs: string;
104
111
  noLogFound: string;
105
112
  logLoadFailed: string;
@@ -321,6 +328,12 @@ export interface UIStrings {
321
328
  invalidDuration: string;
322
329
  durationOutOfRange: (min: string, max: string) => string;
323
330
  durationBelowMin: (min: string) => string;
331
+ /** A repeated wizard page's copy, numbered from 1. */
332
+ copyTitle: (title: string, number: number) => string;
333
+ addCopy: (title: string) => string;
334
+ removeCopy: (title: string) => string;
335
+ /** A wizard's row of step dots. */
336
+ steps: string;
324
337
  };
325
338
  time: {
326
339
  now: string;
@@ -419,6 +432,7 @@ export type UILinkComponent = React.ComponentType<{
419
432
  children: ReactNode;
420
433
  title?: string | undefined;
421
434
  'aria-label'?: string | undefined;
435
+ 'aria-current'?: 'page' | undefined;
422
436
  [dataAttr: `data-${string}`]: string | undefined;
423
437
  }>;
424
438
  /** The provider's link slot, with a plain anchor fallback. */