@parallelworks/ui 0.70.0 → 0.71.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/README.md CHANGED
@@ -874,6 +874,14 @@ passes `ChatInput`'s props; `ComposerControls` takes `model` and `allocation`
874
874
  as a `BoundSetting` for a session that owns its own, and `tools={false}` keeps
875
875
  the chat's tools out of a composer they don't apply to.
876
876
 
877
+ A host's own list of conversations or sessions builds on `ConversationSidebar`
878
+ with the same pieces the chat's list uses: `SidebarRow` with `SidebarRowBody`
879
+ inside its link (an optional `leading` mark such as a status dot, the title
880
+ and time, and an optional `detail` line), `useSessionMenu` for the row menu
881
+ (rename, share, download, then delete apart, each with an optional
882
+ `disabledReason`), `useRowDialogs` for rename and delete, and `SidebarEmpty`
883
+ for an empty list or a failed load.
884
+
877
885
  `ChatLayout sidebarMode="auto"` shows the conversation list as a drawer over
878
886
  the thread when the chat is narrower than `drawerBelowPx` (`DRAWER_BELOW_PX`, 896, by default),
879
887
  measured on the chat's own box; `"drawer"` always does, and `"inline"` (the
@@ -1,14 +1,13 @@
1
1
  import { cx } from "../../cx.js";
2
2
  import { ImageIcon, NewChatIcon, UsersIcon } from "../../icons.js";
3
3
  import { isPlainClick } from "../../components/AppLink.js";
4
- import { EmptyState } from "../../components/EmptyState.js";
5
4
  import { RelativeTime } from "../../components/relativeTime.js";
6
5
  import { SearchInput } from "../../components/SearchInput.js";
7
6
  import { useChatConfig } from "../core/config.js";
8
7
  import { useChat } from "../core/ChatProvider.js";
9
8
  import { ManageProvidersLink } from "./ComposerChrome.js";
10
9
  import { FOCUS_SIDEBAR_SEARCH_EVENT } from "../core/events.js";
11
- import { SidebarRow, SidebarRowTitle } from "../ui/sidebar.js";
10
+ import { SidebarEmpty, SidebarRow, SidebarRowBody } from "../ui/sidebar.js";
12
11
  import { ConversationSidebar } from "../ui/ConversationSidebar.js";
13
12
  import { groupConversationsByDate, useConversationActions } from "./conversationActions.js";
14
13
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -21,25 +20,26 @@ function ConversationRow({ conv, isCurrent, menuItems, openMenu, onOpen }) {
21
20
  selected: isCurrent,
22
21
  menuItems,
23
22
  openMenu,
24
- children: /* @__PURE__ */ jsxs(LinkComponent, {
23
+ children: /* @__PURE__ */ jsx(LinkComponent, {
25
24
  target: {
26
25
  kind: "conversation",
27
26
  id: conv.id
28
27
  },
29
- className: "flex min-w-0 flex-1 items-center gap-1.5 py-1 text-left",
28
+ className: "flex min-w-0 flex-1 py-1 text-left",
30
29
  onClick: (e) => {
31
30
  if (isPlainClick(e)) onOpen();
32
31
  },
33
- children: [conv.isOwner === false && /* @__PURE__ */ jsx(UsersIcon, {
34
- className: "h-3 w-3 shrink-0 theme-muted-text",
35
- title: t.sharedWithYou
36
- }), /* @__PURE__ */ jsx(SidebarRowTitle, {
32
+ children: /* @__PURE__ */ jsx(SidebarRowBody, {
33
+ leading: conv.isOwner === false && /* @__PURE__ */ jsx(UsersIcon, {
34
+ className: "h-3 w-3 theme-muted-text",
35
+ title: t.sharedWithYou
36
+ }),
37
+ title: conv.title || t.untitled,
37
38
  time: /* @__PURE__ */ jsx(RelativeTime, {
38
39
  value: conv.updatedAt ?? conv.createdAt,
39
40
  format: "compact"
40
- }),
41
- children: conv.title || t.untitled
42
- })]
41
+ })
42
+ })
43
43
  })
44
44
  });
45
45
  }
@@ -117,10 +117,7 @@ function ChatSidebar({ actions = true, top }) {
117
117
  onOpen: closeDrawer
118
118
  }),
119
119
  loading: isLoading,
120
- placeholder: /* @__PURE__ */ jsx(EmptyState, {
121
- variant: "bare",
122
- title: conversations.length === 0 ? tSidebar.noConversations : strings.chrome.noMatches
123
- }),
120
+ placeholder: /* @__PURE__ */ jsx(SidebarEmpty, { title: conversations.length === 0 ? tSidebar.noConversations : strings.chrome.noMatches }),
124
121
  cycle: {
125
122
  current: filteredConversations.find((conv) => conv.id === activeConversationId),
126
123
  onSelect: (conv) => navigation.toConversation(conv.id)
@@ -3,10 +3,12 @@ import { type OpenMenu } from '../../components/ContextMenu.js';
3
3
  import type { MenuEntry } from '../../components/MenuEntries.js';
4
4
  import type { ConversationSummary } from '../types.js';
5
5
  export type ConversationGroupKey = 'groupToday' | 'groupYesterday' | 'groupThisWeek' | 'groupThisMonth' | 'groupOlder';
6
- /** Conversations by how long ago they started, newest group first, leaving
7
- * out the groups with none. Each key names its label in the sidebar strings. */
6
+ /** Conversations by how long ago they last changed, the time each row shows,
7
+ * newest group first, leaving out the groups with none. Each key names its
8
+ * label in the sidebar strings. */
8
9
  export declare function groupConversationsByDate<T extends {
9
10
  createdAt: string;
11
+ updatedAt?: string | null | undefined;
10
12
  }>(conversations: T[]): {
11
13
  key: ConversationGroupKey;
12
14
  items: T[];
@@ -1,18 +1,18 @@
1
- import { DownloadIcon, RenameIcon, ShareIcon, TrashIcon } from "../../icons.js";
2
1
  import { toast } from "../../components/Toast.js";
3
2
  import { useStrings } from "../../components/Provider.js";
4
3
  import { useContextMenu } from "../../components/ContextMenu.js";
5
4
  import { useChatConfig } from "../core/config.js";
6
5
  import { useChat } from "../core/ChatProvider.js";
7
- import { useRowDialogs } from "../ui/sidebar.js";
6
+ import { useRowDialogs, useSessionMenu } from "../ui/sidebar.js";
8
7
  import { conversationToMarkdown, downloadText, exportFilename } from "../core/exportConversation.js";
9
8
  import ShareDialog from "./ShareDialog.js";
10
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
10
  import { useState } from "react";
12
11
  import { DateTime } from "luxon";
13
12
  //#region src/ai/components/conversationActions.tsx
14
- /** Conversations by how long ago they started, newest group first, leaving
15
- * out the groups with none. Each key names its label in the sidebar strings. */
13
+ /** Conversations by how long ago they last changed, the time each row shows,
14
+ * newest group first, leaving out the groups with none. Each key names its
15
+ * label in the sidebar strings. */
16
16
  function groupConversationsByDate(conversations) {
17
17
  const now = DateTime.now();
18
18
  const groups = {
@@ -23,7 +23,7 @@ function groupConversationsByDate(conversations) {
23
23
  groupOlder: []
24
24
  };
25
25
  for (const conv of conversations) {
26
- const days = now.diff(DateTime.fromISO(conv.createdAt), "days").days;
26
+ const days = now.diff(DateTime.fromISO(conv.updatedAt ?? conv.createdAt), "days").days;
27
27
  groups[days < 1 ? "groupToday" : days < 2 ? "groupYesterday" : days < 7 ? "groupThisWeek" : days < 30 ? "groupThisMonth" : "groupOlder"].push(conv);
28
28
  }
29
29
  return Object.entries(groups).filter(([, items]) => items.length > 0).map(([key, items]) => ({
@@ -63,29 +63,13 @@ function useConversationActions() {
63
63
  toast.error(strings.chrome.exportError);
64
64
  }
65
65
  };
66
- const menuItems = (conv) => [
67
- {
68
- label: common.rename,
69
- icon: /* @__PURE__ */ jsx(RenameIcon, {}),
70
- onClick: () => requestRename(conv)
71
- },
72
- ...adapter.sharing && conv.isOwner !== false ? [{
73
- label: common.share,
74
- icon: /* @__PURE__ */ jsx(ShareIcon, {}),
75
- onClick: () => setSharing(conv.id)
76
- }] : [],
77
- {
78
- label: common.download,
79
- icon: /* @__PURE__ */ jsx(DownloadIcon, {}),
80
- onClick: () => void download(conv.id)
81
- },
82
- {
83
- label: common.delete,
84
- icon: /* @__PURE__ */ jsx(TrashIcon, {}),
85
- destructive: true,
86
- onClick: () => requestDelete(conv)
87
- }
88
- ];
66
+ const sessionMenu = useSessionMenu();
67
+ const menuItems = (conv) => sessionMenu({
68
+ rename: { onClick: () => requestRename(conv) },
69
+ share: adapter.sharing && conv.isOwner !== false ? { onClick: () => setSharing(conv.id) } : void 0,
70
+ download: { onClick: () => void download(conv.id) },
71
+ remove: { onClick: () => requestDelete(conv) }
72
+ });
89
73
  return {
90
74
  menuItems,
91
75
  openMenu,
@@ -42,5 +42,5 @@ export { type ChatStrings, defaultChatStrings } from './strings.js';
42
42
  export * from './types.js';
43
43
  export { ConversationSidebar, type ConversationSidebarGroup, sidebarDrawerProps, } from './ui/ConversationSidebar.js';
44
44
  export { NoticeBar, NoticeCard, type NoticeTone, OutputCard } from './ui/Notice.js';
45
- export { RenameDialog, type RowDialogStrings, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarRowTitle, SidebarToggle, useRowDialogs, } from './ui/sidebar.js';
45
+ export { RenameDialog, type RowDialogStrings, type SessionMenuAction, SidebarEmpty, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarRowBody, SidebarRowTitle, SidebarToggle, useRowDialogs, useSessionMenu, } from './ui/sidebar.js';
46
46
  export { isMac, isOrgModel } from './utils.js';
package/dist/ai/index.js CHANGED
@@ -35,7 +35,7 @@ import ChatEmptyState from "./components/ChatEmptyState.js";
35
35
  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
- import { RenameDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarRowTitle, SidebarToggle, useRowDialogs } from "./ui/sidebar.js";
38
+ import { RenameDialog, SidebarEmpty, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarRowBody, SidebarRowTitle, SidebarToggle, useRowDialogs, useSessionMenu } from "./ui/sidebar.js";
39
39
  import { ConversationSidebar, sidebarDrawerProps } from "./ui/ConversationSidebar.js";
40
40
  import ShareDialog, { ShareButton } from "./components/ShareDialog.js";
41
41
  import { groupConversationsByDate, useConversationActions } from "./components/conversationActions.js";
@@ -46,4 +46,4 @@ import ChatMessageList from "./components/ChatMessageList.js";
46
46
  import SessionView from "./components/SessionView.js";
47
47
  import ChatThread from "./components/ChatThread.js";
48
48
  import { NoticeBar, NoticeCard, OutputCard } from "./ui/Notice.js";
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, 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, SessionView, ShareButton, ShareDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarRowTitle, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, computeEditDiff, computeWriteRows, defaultChatStrings, defaultToolStrings, editSummary, finalizeParts, firstNonEmptyLine, fullToolArgs, getGreeting, getProviderKeyFromModelId, groupConversationsByDate, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, pasteInlineMaxBytes, permissionModesUpTo, planFromArgs, providerIssueFor, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, sidebarDrawerProps, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useChatStream, useConversationActions, useDragDrop, useKeyboardShortcuts, 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, 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, SessionView, ShareButton, ShareDialog, SidebarEmpty, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarRowBody, SidebarRowTitle, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, computeEditDiff, computeWriteRows, defaultChatStrings, defaultToolStrings, editSummary, finalizeParts, firstNonEmptyLine, fullToolArgs, getGreeting, getProviderKeyFromModelId, groupConversationsByDate, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, pasteInlineMaxBytes, permissionModesUpTo, planFromArgs, providerIssueFor, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, sidebarDrawerProps, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useChatStream, useConversationActions, useDragDrop, useKeyboardShortcuts, useRowDialogs, useSessionMenu, wordEmphasisRanges };
@@ -1,6 +1,6 @@
1
1
  import { type CSSProperties, type ReactNode } from 'react';
2
2
  import type { OpenMenu } from '../../components/ContextMenu.js';
3
- import type { MenuEntry } from '../../components/MenuEntries.js';
3
+ import { type MenuEntry } from '../../components/MenuEntries.js';
4
4
  export declare const SIDEBAR_DEFAULT_WIDTH_PX = 256;
5
5
  /** The column a conversation list lives in: it animates between its width
6
6
  * and a rail, unless a drag is setting the width right now. */
@@ -34,6 +34,34 @@ export declare function SidebarRowTitle({ children, time, }: {
34
34
  /** When it last changed, short: a compact `RelativeTime`. */
35
35
  time?: ReactNode;
36
36
  }): import("react").JSX.Element;
37
+ /** A row's content: an optional mark before it (a status dot, a shared
38
+ * icon), the title with its time, and an optional line under it for what the
39
+ * mark cannot say, such as the machine or a state that needs the reader. */
40
+ export declare function SidebarRowBody({ leading, title, time, detail, }: {
41
+ leading?: ReactNode;
42
+ title: ReactNode;
43
+ time?: ReactNode;
44
+ detail?: ReactNode;
45
+ }): import("react").JSX.Element;
46
+ /** An action a row's menu offers, and why it is unavailable when it is. */
47
+ export interface SessionMenuAction {
48
+ onClick: () => void;
49
+ disabledReason?: string | undefined;
50
+ }
51
+ /** A row's menu in one order wherever a list of conversations or sessions
52
+ * shows: rename, share, download, then delete apart. Each is left out unless given. */
53
+ export declare function useSessionMenu(): (actions: {
54
+ rename?: SessionMenuAction | undefined;
55
+ share?: SessionMenuAction | undefined;
56
+ download?: SessionMenuAction | undefined;
57
+ remove?: SessionMenuAction | undefined;
58
+ }) => MenuEntry[];
59
+ /** In place of a list with nothing to show: none yet, nothing matching, or
60
+ * why it could not load. */
61
+ export declare function SidebarEmpty({ title, error }: {
62
+ title: string;
63
+ error?: string | undefined;
64
+ }): import("react").JSX.Element;
37
65
  /** One entry in the list: the row highlights while it is the open one or
38
66
  * while its menu is up, and the menu opens from its button or a right-click. */
39
67
  export declare function SidebarRow({ selected, menuItems, openMenu, className, children, }: {
@@ -1,7 +1,10 @@
1
1
  import { cx } from "../../cx.js";
2
- import { SidebarIcon } from "../../icons.js";
2
+ import { DownloadIcon, RenameIcon, ShareIcon, SidebarIcon, TrashIcon } from "../../icons.js";
3
3
  import { Tooltip } from "../../components/Tooltip.js";
4
+ import { useStrings } from "../../components/Provider.js";
4
5
  import { AppLink, isPlainClick } from "../../components/AppLink.js";
6
+ import { EmptyState } from "../../components/EmptyState.js";
7
+ import { menuDivider } from "../../components/MenuEntries.js";
5
8
  import { IconButton } from "../../components/IconButton.js";
6
9
  import { fieldBox } from "../../components/fieldBox.js";
7
10
  import { focusOnMount } from "../../components/focus.js";
@@ -56,6 +59,65 @@ function SidebarRowTitle({ children, time }) {
56
59
  })]
57
60
  });
58
61
  }
62
+ /** A row's content: an optional mark before it (a status dot, a shared
63
+ * icon), the title with its time, and an optional line under it for what the
64
+ * mark cannot say, such as the machine or a state that needs the reader. */
65
+ function SidebarRowBody({ leading, title, time, detail }) {
66
+ return /* @__PURE__ */ jsxs("span", {
67
+ className: "flex min-w-0 flex-1 items-start gap-2",
68
+ children: [leading && /* @__PURE__ */ jsx("span", {
69
+ className: "mt-1 flex h-3 shrink-0 items-center",
70
+ children: leading
71
+ }), /* @__PURE__ */ jsxs("span", {
72
+ className: "min-w-0 flex-1",
73
+ children: [/* @__PURE__ */ jsx(SidebarRowTitle, {
74
+ time,
75
+ children: title
76
+ }), detail && /* @__PURE__ */ jsx("span", {
77
+ className: "mt-0.5 flex min-w-0 items-center gap-1.5 text-[11px]",
78
+ children: detail
79
+ })]
80
+ })]
81
+ });
82
+ }
83
+ /** A row's menu in one order wherever a list of conversations or sessions
84
+ * shows: rename, share, download, then delete apart. Each is left out unless given. */
85
+ function useSessionMenu() {
86
+ const { common } = useStrings();
87
+ return ({ rename, share, download, remove }) => {
88
+ const entry = (label, icon, action) => ({
89
+ label,
90
+ icon,
91
+ onClick: action.onClick,
92
+ ...action.disabledReason ? {
93
+ disabled: true,
94
+ disabledReason: action.disabledReason
95
+ } : {}
96
+ });
97
+ const items = [
98
+ ...rename ? [entry(common.rename, /* @__PURE__ */ jsx(RenameIcon, {}), rename)] : [],
99
+ ...share ? [entry(common.share, /* @__PURE__ */ jsx(ShareIcon, {}), share)] : [],
100
+ ...download ? [entry(common.download, /* @__PURE__ */ jsx(DownloadIcon, {}), download)] : []
101
+ ];
102
+ if (remove) {
103
+ if (items.length > 0) items.push(menuDivider);
104
+ items.push({
105
+ ...entry(common.delete, /* @__PURE__ */ jsx(TrashIcon, {}), remove),
106
+ destructive: true
107
+ });
108
+ }
109
+ return items;
110
+ };
111
+ }
112
+ /** In place of a list with nothing to show: none yet, nothing matching, or
113
+ * why it could not load. */
114
+ function SidebarEmpty({ title, error }) {
115
+ return /* @__PURE__ */ jsx(EmptyState, {
116
+ variant: "bare",
117
+ title: error ?? title,
118
+ ...error ? { testId: "sidebar-list-error" } : {}
119
+ });
120
+ }
59
121
  /** One entry in the list: the row highlights while it is the open one or
60
122
  * while its menu is up, and the menu opens from its button or a right-click. */
61
123
  function SidebarRow({ selected, menuItems, openMenu, className, children }) {
@@ -176,4 +238,4 @@ function useRowDialogs({ strings, nameOf, onDelete, onRename }) {
176
238
  };
177
239
  }
178
240
  //#endregion
179
- export { RenameDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarRowTitle, SidebarToggle, useRowDialogs };
241
+ export { RenameDialog, SidebarEmpty, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarRowBody, SidebarRowTitle, SidebarToggle, useRowDialogs, useSessionMenu };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parallelworks/ui",
3
- "version": "0.70.0",
3
+ "version": "0.71.0",
4
4
  "description": "React UI components: a theme contract, icons, brand logos, primitives, lists, forms, a job graph, a code editor, a log viewer, a file explorer and an AI chat, each on its own subpath",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {