ai-design-system 0.1.56 → 0.1.58

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 (45) hide show
  1. package/components/ai-elements/reasoning.tsx +1 -2
  2. package/components/blocks/AIConversation/AIConversation.tsx +113 -28
  3. package/components/blocks/FormReportsSection/FormReportsSection.tsx +3 -0
  4. package/components/composites/AppBreadcrumb/AppBreadcrumb.stories.tsx +46 -0
  5. package/components/composites/AppBreadcrumb/AppBreadcrumb.tsx +77 -0
  6. package/components/composites/AppBreadcrumb/index.ts +2 -0
  7. package/components/composites/AppHeader/AppHeader.tsx +8 -2
  8. package/components/composites/AppHeader/interfaces.ts +4 -0
  9. package/components/composites/DataTable/DraggableRow.tsx +5 -2
  10. package/components/composites/DataTable/EnhancedDataTable.tsx +7 -1
  11. package/components/composites/DataTable/table-types.ts +1 -0
  12. package/components/composites/FormReports/FormReportsTable.tsx +10 -3
  13. package/components/composites/OrchestratorMessage/OrchestratorMessage.tsx +1 -1
  14. package/components/composites/ProjectSwitcher/ProjectSwitcher.tsx +23 -10
  15. package/components/composites/ReasoningDisplay/ReasoningDisplay.tsx +8 -3
  16. package/components/composites/SessionHeader/SessionHeader.stories.tsx +50 -0
  17. package/components/composites/SessionHeader/SessionHeader.tsx +103 -0
  18. package/components/composites/SessionHeader/index.ts +2 -0
  19. package/components/composites/SpecialistMessage/SpecialistMessage.tsx +1 -1
  20. package/components/composites/StreamingMarkdown.tsx +120 -0
  21. package/components/composites/TriggerNode/TriggerNode.tsx +1 -1
  22. package/components/composites/WorkflowSwitcher/WorkflowSwitcher.stories.tsx +32 -0
  23. package/components/composites/WorkflowSwitcher/WorkflowSwitcher.tsx +66 -0
  24. package/components/composites/WorkflowSwitcher/index.ts +1 -0
  25. package/components/composites/WorkflowToolbar/WorkflowToolbar.tsx +10 -5
  26. package/components/composites/index.ts +15 -0
  27. package/components/{ai-elements → composites}/response.tsx +3 -3
  28. package/components/features/AIDocEditor/AIDocEditor.tsx +50 -24
  29. package/components/features/FormReportsFeature/FormReportsFeature.tsx +3 -0
  30. package/components/features/PageLayout/PageLayout.tsx +12 -0
  31. package/components/features/RefinementPanel/RefinementPanel.tsx +36 -0
  32. package/components/index.ts +4 -2
  33. package/components/primitives/Breadcrumb/Breadcrumb.stories.tsx +129 -0
  34. package/components/primitives/Breadcrumb/Breadcrumb.tsx +68 -0
  35. package/components/primitives/Breadcrumb/index.ts +1 -0
  36. package/components/primitives/index.ts +1 -0
  37. package/components/ui/breadcrumb.tsx +109 -0
  38. package/components/ui/command.tsx +1 -1
  39. package/dist/index.cjs +640 -103
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.css +50 -15
  42. package/dist/index.d.ts +90 -5
  43. package/dist/index.js +639 -106
  44. package/dist/index.js.map +1 -1
  45. package/package.json +2 -1
@@ -10,7 +10,6 @@ import { cn } from "@/lib/utils";
10
10
  import { BrainIcon, ChevronDownIcon } from "lucide-react";
11
11
  import type { ComponentProps } from "react";
12
12
  import { createContext, memo, useContext, useEffect, useState } from "react";
13
- import { Response } from "./response";
14
13
  import { Shimmer } from "./shimmer";
15
14
 
16
15
  type ReasoningContextValue = {
@@ -179,7 +178,7 @@ export const ReasoningContent = memo(
179
178
  )}
180
179
  {...props}
181
180
  >
182
- <Response className="grid gap-2">{children}</Response>
181
+ {children}
183
182
  </CollapsibleContent>
184
183
  )
185
184
  );
@@ -101,9 +101,94 @@ export const AIConversation = React.memo<AIConversationProps>(
101
101
  const toMessageString = (content: unknown): string =>
102
102
  typeof content === 'string' ? content : ''
103
103
 
104
+ const groupedMessages = React.useMemo(() => {
105
+ const result: AIMessage[] = [];
106
+ let currentGroup: AIMessage | null = null;
107
+
108
+ for (const msg of messages) {
109
+ if (msg.role === "orchestrator") {
110
+ const isFinalResponse =
111
+ !msg.isLoading &&
112
+ (!msg.toolCalls || msg.toolCalls.length === 0) &&
113
+ (!msg.subAgents || msg.subAgents.length === 0);
114
+
115
+ if (!isFinalResponse) {
116
+ if (currentGroup) {
117
+ if (msg.content) {
118
+ currentGroup.content = currentGroup.content
119
+ ? `${currentGroup.content}\n\n${msg.content}`
120
+ : msg.content;
121
+ }
122
+ if (msg.toolCalls) {
123
+ const existingToolCalls = currentGroup.toolCalls || [];
124
+ const updatedToolCalls = [...existingToolCalls];
125
+ for (const newCall of msg.toolCalls) {
126
+ const existingIndex = updatedToolCalls.findIndex(tc => tc.id === newCall.id);
127
+ if (existingIndex >= 0) {
128
+ updatedToolCalls[existingIndex] = newCall;
129
+ } else {
130
+ updatedToolCalls.push(newCall);
131
+ }
132
+ }
133
+ currentGroup.toolCalls = updatedToolCalls;
134
+ }
135
+ if (msg.subAgents) {
136
+ const existingSubAgents = currentGroup.subAgents || [];
137
+ const updatedSubAgents = [...existingSubAgents];
138
+ for (const newAgent of msg.subAgents) {
139
+ const existingIndex = updatedSubAgents.findIndex(a => a.id === newAgent.id);
140
+ if (existingIndex >= 0) {
141
+ updatedSubAgents[existingIndex] = newAgent;
142
+ } else {
143
+ // Merge consecutive subAgents with the same name
144
+ if (
145
+ updatedSubAgents.length > 0 &&
146
+ updatedSubAgents[updatedSubAgents.length - 1].subAgentName === newAgent.subAgentName
147
+ ) {
148
+ const prev = updatedSubAgents[updatedSubAgents.length - 1];
149
+ let mergedOutput = prev.output;
150
+ if (newAgent.output) {
151
+ const prevStr = typeof prev.output === 'string' ? prev.output : (prev.output ? JSON.stringify(prev.output) : '');
152
+ const newStr = typeof newAgent.output === 'string' ? newAgent.output : JSON.stringify(newAgent.output);
153
+ mergedOutput = prevStr ? `${prevStr}\n\n${newStr}` : newStr;
154
+ }
155
+ updatedSubAgents[updatedSubAgents.length - 1] = {
156
+ ...newAgent,
157
+ id: prev.id, // keep the original id so React keys don't jump
158
+ input: prev.input, // keep original input or combine? Just keep original
159
+ output: mergedOutput
160
+ };
161
+ } else {
162
+ updatedSubAgents.push(newAgent);
163
+ }
164
+ }
165
+ }
166
+ currentGroup.subAgents = updatedSubAgents;
167
+ }
168
+ currentGroup.isLoading = msg.isLoading;
169
+ } else {
170
+ currentGroup = {
171
+ ...msg,
172
+ toolCalls: msg.toolCalls ? [...msg.toolCalls] : undefined,
173
+ subAgents: msg.subAgents ? [...msg.subAgents] : undefined,
174
+ };
175
+ result.push(currentGroup);
176
+ }
177
+ } else {
178
+ currentGroup = null;
179
+ result.push(msg);
180
+ }
181
+ } else {
182
+ currentGroup = null;
183
+ result.push(msg);
184
+ }
185
+ }
186
+ return result;
187
+ }, [messages]);
188
+
104
189
  const renderedMessages = React.useMemo(
105
190
  () =>
106
- messages.map((message) => {
191
+ groupedMessages.map((message, index) => {
107
192
  const contentStr = toMessageString(message.content)
108
193
 
109
194
  // Render based on role field
@@ -137,8 +222,7 @@ export const AIConversation = React.memo<AIConversationProps>(
137
222
  const reasoningCalls = allToolCalls.filter((tc) => tc.visibility === "reasoning")
138
223
  const directToolCalls = allToolCalls.filter((tc) => tc.visibility !== "reasoning")
139
224
 
140
- const hasContent = contentStr.trim() !== ""
141
- const hasReasoning = reasoningCalls.length > 0
225
+ const hasReasoning = reasoningCalls.length > 0 || subAgents.length > 0 || directToolCalls.length > 0 || (message.isLoading && contentStr.trim() !== "");
142
226
  const reasoningText = hasReasoning ? contentStr : undefined
143
227
  const displayContentStr = hasReasoning ? "" : contentStr
144
228
  const hasDisplayContent = displayContentStr.trim() !== ""
@@ -147,7 +231,7 @@ export const AIConversation = React.memo<AIConversationProps>(
147
231
  return null;
148
232
  }
149
233
 
150
- const isStreaming = reasoningCalls.some((tc) => tc.status === "pending")
234
+ const isStreaming = message.isLoading || reasoningCalls.some((tc) => tc.status === "pending") || subAgents.some(sa => sa.status === "active") || directToolCalls.some(tc => tc.status === "pending")
151
235
 
152
236
  return (
153
237
  <OrchestratorMessage
@@ -161,36 +245,37 @@ export const AIConversation = React.memo<AIConversationProps>(
161
245
  }}
162
246
  showAvatar={showAvatars && hasDisplayContent}
163
247
  >
164
- {/* Render reasoning-section for hidden tool results */}
248
+ {/* Render reasoning-section for hidden tool results and subagents */}
165
249
  {hasReasoning && (
166
250
  <ReasoningDisplay
167
251
  content={reasoningText}
168
252
  items={reasoningCalls}
169
253
  isStreaming={isStreaming}
170
254
  onToolAction={onToolAction}
171
- />
172
- )}
255
+ defaultOpen={isStreaming || index === groupedMessages.length - 1}
256
+ >
257
+ {/* Render direct tool calls */}
258
+ {directToolCalls.map((tc) => (
259
+ <ToolCallDisplay key={tc.id} toolCall={tc} onToolAction={onToolAction} />
260
+ ))}
173
261
 
174
- {/* Render direct tool calls */}
175
- {directToolCalls.map((tc) => (
176
- <ToolCallDisplay key={tc.id} toolCall={tc} onToolAction={onToolAction} />
177
- ))}
178
-
179
- {/* Render specialist sub-agents */}
180
- {subAgents.map((subAgent) => (
181
- <SpecialistMessage
182
- key={subAgent.id}
183
- message={{
184
- id: subAgent.id,
185
- name: subAgent.subAgentName,
186
- description: undefined,
187
- content: typeof subAgent.output === 'string' ? subAgent.output : (subAgent.output ? JSON.stringify(subAgent.output) : ''),
188
- status: subAgent.status,
189
- toolCalls: [],
190
- }}
191
- isNested={true}
192
- />
193
- ))}
262
+ {/* Render specialist sub-agents */}
263
+ {subAgents.map((subAgent) => (
264
+ <SpecialistMessage
265
+ key={subAgent.id}
266
+ message={{
267
+ id: subAgent.id,
268
+ name: subAgent.subAgentName,
269
+ description: undefined,
270
+ content: typeof subAgent.output === 'string' ? subAgent.output : (subAgent.output ? JSON.stringify(subAgent.output) : ''),
271
+ status: subAgent.status,
272
+ toolCalls: [],
273
+ }}
274
+ isNested={true}
275
+ />
276
+ ))}
277
+ </ReasoningDisplay>
278
+ )}
194
279
  </OrchestratorMessage>
195
280
  )
196
281
  }
@@ -210,7 +295,7 @@ export const AIConversation = React.memo<AIConversationProps>(
210
295
  />
211
296
  )
212
297
  }),
213
- [messages, showAvatars]
298
+ [groupedMessages, showAvatars, onToolAction]
214
299
  )
215
300
 
216
301
  return (
@@ -17,6 +17,7 @@ export interface FormReportsSectionProps {
17
17
  pagination?: DashboardPaginationState
18
18
  tableHandlers?: FormReportsTableHandlers
19
19
  tableLeftActions?: React.ReactNode
20
+ enableRowSelection?: boolean
20
21
  }
21
22
 
22
23
  export const FormReportsSection = React.memo<FormReportsSectionProps>(
@@ -29,6 +30,7 @@ export const FormReportsSection = React.memo<FormReportsSectionProps>(
29
30
  pagination,
30
31
  tableHandlers,
31
32
  tableLeftActions,
33
+ enableRowSelection,
32
34
  }) => {
33
35
  return (
34
36
  <section>
@@ -41,6 +43,7 @@ export const FormReportsSection = React.memo<FormReportsSectionProps>(
41
43
  leftActions={tableLeftActions}
42
44
  onCreateClick={onCreateClick}
43
45
  createButtonLabel={createButtonLabel}
46
+ enableRowSelection={enableRowSelection}
44
47
  />
45
48
  </section>
46
49
  )
@@ -0,0 +1,46 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { AppBreadcrumb } from "./AppBreadcrumb";
3
+
4
+ /**
5
+ * AppBreadcrumb Composite Stories
6
+ *
7
+ * The AppBreadcrumb provides an easy way to render breadcrumbs by passing
8
+ * an array of data, automatically handling separators and active states.
9
+ */
10
+ const meta = {
11
+ title: "Composites/AppBreadcrumb",
12
+ component: AppBreadcrumb,
13
+ tags: ["autodocs"],
14
+ parameters: { layout: "padded" },
15
+ } satisfies Meta<typeof AppBreadcrumb>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ /**
21
+ * Default AppBreadcrumb usage
22
+ */
23
+ export const Default: Story = {
24
+ args: {
25
+ items: [
26
+ { label: "Home", href: "/" },
27
+ { label: "Workflows", href: "/workflows" },
28
+ { label: "My Workflow" },
29
+ ],
30
+ },
31
+ };
32
+
33
+ /**
34
+ * With an ellipsis
35
+ */
36
+ export const WithEllipsis: Story = {
37
+ args: {
38
+ items: [
39
+ { label: "Home", href: "/" },
40
+ { label: "Folders", href: "/folders" },
41
+ { label: "Subfolder", href: "/folders/1" },
42
+ { label: "Document" },
43
+ ],
44
+ showEllipsis: true,
45
+ },
46
+ };
@@ -0,0 +1,77 @@
1
+ import * as React from "react";
2
+ import {
3
+ Breadcrumb,
4
+ BreadcrumbList,
5
+ BreadcrumbItem,
6
+ BreadcrumbLink,
7
+ BreadcrumbPage,
8
+ BreadcrumbSeparator,
9
+ BreadcrumbEllipsis
10
+ } from "@/components/primitives/Breadcrumb";
11
+
12
+ export interface BreadcrumbItemData {
13
+ label: React.ReactNode;
14
+ href?: string;
15
+ isCurrentPage?: boolean;
16
+ onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
17
+ }
18
+
19
+ export interface AppBreadcrumbProps extends React.HTMLAttributes<HTMLElement> {
20
+ items: BreadcrumbItemData[];
21
+ showEllipsis?: boolean;
22
+ ellipsisIndex?: number;
23
+ }
24
+
25
+ /**
26
+ * AppBreadcrumb Composite
27
+ *
28
+ * A composite wrapper around the Breadcrumb primitive that accepts a data array
29
+ * and handles the complex rendering of links, separators, and ellipses.
30
+ */
31
+ export const AppBreadcrumb = React.forwardRef<HTMLElement, AppBreadcrumbProps>(
32
+ ({ items, showEllipsis = false, ellipsisIndex = 1, className, ...props }, ref) => {
33
+ if (!items || items.length === 0) return null;
34
+
35
+ return (
36
+ <Breadcrumb ref={ref} className={className} {...props}>
37
+ <BreadcrumbList>
38
+ {items.map((item, index) => {
39
+ const isLast = index === items.length - 1;
40
+ const isEllipsis = showEllipsis && index === ellipsisIndex && items.length > 3;
41
+
42
+ if (isEllipsis) {
43
+ return (
44
+ <React.Fragment key={`ellipsis-${index}`}>
45
+ <BreadcrumbItem>
46
+ <BreadcrumbEllipsis />
47
+ </BreadcrumbItem>
48
+ {!isLast && <BreadcrumbSeparator />}
49
+ </React.Fragment>
50
+ );
51
+ }
52
+
53
+ return (
54
+ <React.Fragment key={`item-${index}`}>
55
+ <BreadcrumbItem>
56
+ {item.isCurrentPage || isLast ? (
57
+ <BreadcrumbPage>{item.label}</BreadcrumbPage>
58
+ ) : (
59
+ <BreadcrumbLink
60
+ href={item.href || "#"}
61
+ onClick={item.onClick}
62
+ >
63
+ {item.label}
64
+ </BreadcrumbLink>
65
+ )}
66
+ </BreadcrumbItem>
67
+ {!isLast && <BreadcrumbSeparator />}
68
+ </React.Fragment>
69
+ );
70
+ })}
71
+ </BreadcrumbList>
72
+ </Breadcrumb>
73
+ );
74
+ }
75
+ );
76
+
77
+ AppBreadcrumb.displayName = "AppBreadcrumb";
@@ -0,0 +1,2 @@
1
+ export { AppBreadcrumb } from "./AppBreadcrumb";
2
+ export type { AppBreadcrumbProps, BreadcrumbItemData } from "./AppBreadcrumb";
@@ -2,6 +2,8 @@ import * as React from "react"
2
2
  import { SidebarTrigger } from "@/components/primitives/Sidebar"
3
3
  import { Separator } from "@/components/primitives/Separator"
4
4
  import { Tabs, TabsList, TabsTrigger } from "@/components/primitives/Tabs"
5
+ import { ChatToggleButton } from "@/components/composites/ChatToggleButton"
6
+ import { WorkflowSwitcher } from "@/components/composites/WorkflowSwitcher"
5
7
  import type { AppHeaderProps } from "./interfaces"
6
8
 
7
9
  export const AppHeader = React.memo<AppHeaderProps>(({
@@ -13,14 +15,16 @@ export const AppHeader = React.memo<AppHeaderProps>(({
13
15
  className,
14
16
  tabsPosition = 'center',
15
17
  showSidebarToggle = true,
16
- showTitle = true
18
+ showTitle = true,
19
+ workflowSwitcherProps,
20
+ chatToggleProps
17
21
  }) => {
18
22
  return (
19
23
  <header className={`flex h-14 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-14 ${className || ""}`}>
20
24
  <div className="grid w-full grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center px-4 lg:px-6">
21
25
  <div className="min-w-0 flex items-center gap-1 lg:gap-2">
22
26
  {showSidebarToggle && <SidebarTrigger className="-ml-1" />}
23
- {showSidebarToggle && showTitle && title && (
27
+ {showSidebarToggle && (showTitle && title || workflowSwitcherProps || chatToggleProps) && (
24
28
  <Separator orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" />
25
29
  )}
26
30
  {showTitle && title && (
@@ -28,6 +32,8 @@ export const AppHeader = React.memo<AppHeaderProps>(({
28
32
  ? <h1 className="max-w-[28rem] truncate text-base font-medium">{title}</h1>
29
33
  : title
30
34
  )}
35
+ {chatToggleProps && <ChatToggleButton {...chatToggleProps} />}
36
+ {workflowSwitcherProps && <WorkflowSwitcher {...workflowSwitcherProps} />}
31
37
  </div>
32
38
 
33
39
  <div className="justify-self-center">
@@ -1,4 +1,6 @@
1
1
  import type React from "react";
2
+ import type { WorkflowSwitcherProps } from "@/components/composites/WorkflowSwitcher/WorkflowSwitcher";
3
+ import type { ChatToggleButtonProps } from "@/components/composites/ChatToggleButton/ChatToggleButton";
2
4
 
3
5
  export interface TabItem {
4
6
  value: string;
@@ -15,4 +17,6 @@ export interface AppHeaderProps {
15
17
  className?: string;
16
18
  showSidebarToggle?: boolean;
17
19
  showTitle?: boolean;
20
+ workflowSwitcherProps?: WorkflowSwitcherProps;
21
+ chatToggleProps?: ChatToggleButtonProps;
18
22
  }
@@ -8,9 +8,11 @@ import type { DashboardRow } from "./table-types"
8
8
  export interface DraggableRowProps {
9
9
  row: Row<DashboardRow>
10
10
  rowId: number | string
11
+ onClick?: (row: DashboardRow) => void
12
+ className?: string
11
13
  }
12
14
 
13
- export function DraggableRow({ row, rowId }: DraggableRowProps) {
15
+ export function DraggableRow({ row, rowId, onClick, className }: DraggableRowProps) {
14
16
  const { transform, transition, setNodeRef, isDragging } = useSortable({
15
17
  id: rowId,
16
18
  })
@@ -21,7 +23,8 @@ export function DraggableRow({ row, rowId }: DraggableRowProps) {
21
23
  data-row-id={String(rowId)}
22
24
  data-state={row.getIsSelected() && "selected"}
23
25
  data-dragging={isDragging}
24
- className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
26
+ onClick={() => onClick?.(row.original)}
27
+ className={`relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80 ${className ?? ""}`}
25
28
  style={{
26
29
  transform: CSS.Transform.toString(transform),
27
30
  transition,
@@ -539,7 +539,13 @@ export function EnhancedDataTable({
539
539
  {table.getRowModel().rows?.length ? (
540
540
  <SortableContext items={dataIds} strategy={verticalListSortingStrategy}>
541
541
  {table.getRowModel().rows.map((row) => (
542
- <DraggableRow key={row.id} row={row} rowId={toRowId(row.original, tableSchema)} />
542
+ <DraggableRow
543
+ key={row.id}
544
+ row={row}
545
+ rowId={toRowId(row.original, tableSchema)}
546
+ onClick={handlers?.onRowClick}
547
+ className={handlers?.onRowClick ? "cursor-pointer hover:bg-muted/50" : ""}
548
+ />
543
549
  ))}
544
550
  </SortableContext>
545
551
  ) : (
@@ -34,4 +34,5 @@ export interface DashboardTableActionHandlers {
34
34
  onPaginationChange?: (pageIndex: number, pageSize: number) => void
35
35
  onPageSizeChange?: (pageSize: number) => void
36
36
  onPageChange?: (pageIndex: number) => void
37
+ onRowClick?: (row: DashboardRow) => void
37
38
  }
@@ -38,6 +38,7 @@ export interface FormReportsTableHandlers {
38
38
  onPaginationChange?: (pageIndex: number, pageSize: number) => void
39
39
  onPageSizeChange?: (pageSize: number) => void
40
40
  onPageChange?: (pageIndex: number) => void
41
+ onRowClick?: (row: FormReportsEntity) => void
41
42
  }
42
43
 
43
44
  export interface FormReportsTableProps {
@@ -50,6 +51,7 @@ export interface FormReportsTableProps {
50
51
  rightActions?: React.ReactNode
51
52
  onCreateClick?: () => void
52
53
  createButtonLabel?: string
54
+ enableRowSelection?: boolean
53
55
  }
54
56
 
55
57
  const dashboardToFormReportsActionMap: Record<DashboardRowAction, string> = {
@@ -62,7 +64,7 @@ const dashboardToFormReportsActionMap: Record<DashboardRowAction, string> = {
62
64
  export type { DashboardPaginationState }
63
65
 
64
66
  export const FormReportsTable = React.memo<FormReportsTableProps>(
65
- ({ items, columns, pagination, handlers, leftActions, rightActions, onCreateClick, createButtonLabel }) => {
67
+ ({ items, columns, pagination, handlers, leftActions, rightActions, onCreateClick, createButtonLabel, enableRowSelection }) => {
66
68
  const { rows, originalById, tableSchema } = React.useMemo(() => {
67
69
  const byId = new Map<string, FormReportsEntity>()
68
70
  const tableColumns: DynamicTableSchema["columns"] = columns
@@ -87,10 +89,10 @@ export const FormReportsTable = React.memo<FormReportsTableProps>(
87
89
  columns: tableColumns,
88
90
  enableFiltering: true,
89
91
  enablePagination: true,
90
- enableRowSelection: true,
92
+ enableRowSelection: enableRowSelection ?? true,
91
93
  }),
92
94
  }
93
- }, [columns, items])
95
+ }, [columns, items, enableRowSelection])
94
96
 
95
97
  const adaptedHandlers = React.useMemo<DashboardTableActionHandlers>(
96
98
  () => ({
@@ -164,6 +166,11 @@ export const FormReportsTable = React.memo<FormReportsTableProps>(
164
166
  onPaginationChange: (pageIndex, pageSize) => handlers?.onPaginationChange?.(pageIndex, pageSize),
165
167
  onPageSizeChange: (pageSize) => handlers?.onPageSizeChange?.(pageSize),
166
168
  onPageChange: (pageIndex) => handlers?.onPageChange?.(pageIndex),
169
+ onRowClick: (row) => {
170
+ const originalRow = originalById.get(String(row.id))
171
+ if (!originalRow) return
172
+ handlers?.onRowClick?.(originalRow)
173
+ }
167
174
  }),
168
175
  [handlers, originalById]
169
176
  )
@@ -10,7 +10,7 @@ import {
10
10
  MessageAvatar,
11
11
  MessageTypingIndicator,
12
12
  } from "@/components/ai-elements/message"
13
- import { Response } from "@/components/ai-elements/response"
13
+ import { Response } from "@/components/composites/response"
14
14
 
15
15
  /**
16
16
  * OrchestratorMessage Block
@@ -1,12 +1,12 @@
1
1
  import * as React from "react"
2
2
  import { Icon } from "@/components/primitives/Icon"
3
+ import { Command as CommandPrimitive } from "cmdk"
3
4
 
4
5
  import { Button } from "@/components/primitives/Button"
5
6
  import {
6
7
  Command,
7
8
  CommandEmpty,
8
9
  CommandGroup,
9
- CommandInput,
10
10
  CommandItem,
11
11
  CommandList,
12
12
  CommandSeparator,
@@ -37,6 +37,8 @@ export const ProjectSwitcher = React.memo<ProjectSwitcherProps>(
37
37
  const selectedProject = projects.find((p) => p.id === selectedProjectId)
38
38
  const displayLabel = selectedProject ? selectedProject.name : "Select Project..."
39
39
 
40
+ const defaultValue = selectedProject ? `${selectedProject.name}-${selectedProject.id}` : undefined
41
+
40
42
  return (
41
43
  <Popover open={open} onOpenChange={setOpen}>
42
44
  <PopoverTrigger asChild>
@@ -46,13 +48,21 @@ export const ProjectSwitcher = React.memo<ProjectSwitcherProps>(
46
48
  aria-expanded={open}
47
49
  className={`w-[240px] justify-between font-medium ${className || ""}`}
48
50
  >
49
- {displayLabel}
51
+ <div className="flex items-center">
52
+ {displayLabel}
53
+ </div>
50
54
  <Icon name="chevrons-up-down" size="sm" className="ml-2 shrink-0 opacity-50" />
51
55
  </Button>
52
56
  </PopoverTrigger>
53
57
  <PopoverContent className="w-[240px] p-0" align="start">
54
- <Command>
55
- <CommandInput placeholder="Find Project..." />
58
+ <Command defaultValue={defaultValue}>
59
+ <div className="flex items-center justify-between border-b border-neutral-600 px-3" cmdk-input-wrapper="">
60
+ <CommandPrimitive.Input
61
+ placeholder="Find Project..."
62
+ className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
63
+ />
64
+ <kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-neutral-700 bg-neutral-800 px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">Esc</kbd>
65
+ </div>
56
66
  <CommandList className="max-h-[300px]">
57
67
  <CommandEmpty>
58
68
  <div className="flex flex-col items-center justify-center p-6 text-center text-sm text-muted-foreground">
@@ -60,22 +70,25 @@ export const ProjectSwitcher = React.memo<ProjectSwitcherProps>(
60
70
  </div>
61
71
  </CommandEmpty>
62
72
  {projects.length > 0 && (
63
- <CommandGroup heading="All Projects">
73
+ <CommandGroup>
64
74
  {projects.map((project) => (
65
75
  <CommandItem
66
76
  key={project.id}
67
- value={project.name}
77
+ value={`${project.name}-${project.id}`}
68
78
  onSelect={() => {
69
79
  onSelectProject(project.id)
70
80
  setOpen(false)
71
81
  }}
82
+ className="mb-1 last:mb-0 cursor-pointer flex items-center justify-between"
72
83
  >
84
+ <div className="flex items-center">
85
+ {project.name}
86
+ </div>
73
87
  <Icon name="check"
74
- className={`mr-2 h-4 w-4 ${
88
+ className={`h-4 w-4 ${
75
89
  selectedProjectId === project.id ? "opacity-100" : "opacity-0"
76
90
  }`}
77
91
  />
78
- {project.name}
79
92
  </CommandItem>
80
93
  ))}
81
94
  </CommandGroup>
@@ -91,8 +104,8 @@ export const ProjectSwitcher = React.memo<ProjectSwitcherProps>(
91
104
  onCreateProject()
92
105
  }}
93
106
  >
94
- <Icon name="plus" size="sm" className="mr-2" />
95
- Create App
107
+ <Icon name="plus" size="sm" className="mr-2 h-4 w-4" />
108
+ Create Project
96
109
  </Button>
97
110
  </div>
98
111
  </Command>
@@ -5,7 +5,7 @@ import {
5
5
  ReasoningTrigger,
6
6
  } from "@/components/ai-elements/reasoning";
7
7
  import { CollapsibleContent } from "@/components/primitives/Collapsible";
8
- import { Response } from "@/components/ai-elements/response";
8
+ import { Response } from "@/components/composites/response";
9
9
  import { ToolCallDisplay } from "@/components/composites/ToolCallDisplay";
10
10
  import type { ToolCall } from "@/components/composites/ToolCallDisplay";
11
11
 
@@ -14,6 +14,8 @@ export interface ReasoningDisplayProps {
14
14
  isStreaming?: boolean;
15
15
  content?: string;
16
16
  onToolAction?: (toolCall: ToolCall, action: string) => void;
17
+ defaultOpen?: boolean;
18
+ children?: React.ReactNode;
17
19
  }
18
20
 
19
21
  export const ReasoningDisplay = ({
@@ -21,11 +23,13 @@ export const ReasoningDisplay = ({
21
23
  isStreaming = false,
22
24
  content,
23
25
  onToolAction,
26
+ defaultOpen = false,
27
+ children,
24
28
  }: ReasoningDisplayProps) => {
25
- if (items.length === 0 && !content) return null;
29
+ if (items.length === 0 && !content && !children) return null;
26
30
 
27
31
  return (
28
- <Reasoning isStreaming={isStreaming} defaultOpen={false}>
32
+ <Reasoning isStreaming={isStreaming} defaultOpen={defaultOpen}>
29
33
  <ReasoningTrigger />
30
34
  <CollapsibleContent
31
35
  className="mt-4 flex flex-col gap-2 data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in"
@@ -39,6 +43,7 @@ export const ReasoningDisplay = ({
39
43
  {items.map((item) => (
40
44
  <ToolCallDisplay key={item.id} toolCall={item} onToolAction={onToolAction} />
41
45
  ))}
46
+ {children}
42
47
  </div>
43
48
  </CollapsibleContent>
44
49
  </Reasoning>