ai-design-system 0.1.62 → 0.1.64

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.
@@ -16,6 +16,11 @@ export type FileTreeNode = {
16
16
  children?: FileTreeNode[]
17
17
  }
18
18
 
19
+ export interface FileDownloadResult {
20
+ blob: Blob
21
+ filename: string
22
+ }
23
+
19
24
  export interface FileTreeExplorerProps {
20
25
  tree: FileTreeNode[]
21
26
  defaultExpanded?: Set<string>
@@ -24,7 +29,7 @@ export interface FileTreeExplorerProps {
24
29
  searchPlaceholder?: string
25
30
  onCreateClick?: () => void
26
31
  createButtonLabel?: string
27
- onDownloadClick?: () => void
32
+ onDownload?: () => Promise<FileDownloadResult | undefined>
28
33
  headerClassName?: string
29
34
  className?: string
30
35
  }
@@ -85,13 +90,28 @@ export const FileTreeExplorer = React.memo<FileTreeExplorerProps>(
85
90
  searchPlaceholder = "Filter files...",
86
91
  onCreateClick,
87
92
  createButtonLabel,
88
- onDownloadClick,
93
+ onDownload,
89
94
  headerClassName,
90
95
  className,
91
96
  }) => {
97
+ const downloadRef = React.useRef<HTMLAnchorElement>(null)
92
98
  const [searchQuery, setSearchQuery] = React.useState("")
93
99
  const [userExpanded, setUserExpanded] = React.useState<Set<string>>(defaultExpanded || new Set())
94
100
 
101
+ const handleDownloadClick = React.useCallback(async () => {
102
+ if (!onDownload) return
103
+ const result = await onDownload()
104
+ if (!result) return
105
+ const url = URL.createObjectURL(result.blob)
106
+ const anchor = downloadRef.current
107
+ if (anchor) {
108
+ anchor.href = url
109
+ anchor.download = result.filename
110
+ anchor.click()
111
+ }
112
+ URL.revokeObjectURL(url)
113
+ }, [onDownload])
114
+
95
115
  // Auto-expand parents when selectedPath changes
96
116
  React.useEffect(() => {
97
117
  if (selectedPath) {
@@ -147,16 +167,17 @@ export const FileTreeExplorer = React.memo<FileTreeExplorerProps>(
147
167
  {createButtonLabel ?? "New"}
148
168
  </button>
149
169
  ) : null}
150
- {onDownloadClick ? (
170
+ {onDownload ? (
151
171
  <button
152
172
  type="button"
153
- onClick={onDownloadClick}
173
+ onClick={handleDownloadClick}
154
174
  title="Download all files"
155
175
  className="inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
156
176
  >
157
177
  <Icon name="download" size="sm" />
158
178
  </button>
159
179
  ) : null}
180
+ <a ref={downloadRef} style={{ display: 'none' }} />
160
181
  </div>
161
182
  <div className="p-2">
162
183
  <FileTree
@@ -3,6 +3,7 @@ export {
3
3
  } from "./FileTreeExplorer"
4
4
 
5
5
  export type {
6
+ FileDownloadResult,
6
7
  FileTreeExplorerProps,
7
8
  FileTreeNode,
8
9
  } from "./FileTreeExplorer"
@@ -16,12 +16,16 @@ export interface ChatSessionInfo {
16
16
  created_at: string;
17
17
  }
18
18
 
19
+ import type { FileDownloadResult } from '@/components/composites/FileTreeExplorer';
20
+
21
+
19
22
  export interface SessionHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
20
23
  sessions?: ChatSessionInfo[];
21
24
  activeSessionId?: string | null;
22
25
  onNewSession?: () => void;
23
26
  onCloseSession?: (id: string) => void;
24
27
  onSelectSession?: (id: string) => void;
28
+ onDownloadSession?: () => Promise<FileDownloadResult | undefined>;
25
29
  }
26
30
 
27
31
  /**
@@ -31,8 +35,23 @@ export interface SessionHeaderProps extends React.HTMLAttributes<HTMLDivElement>
31
35
  * and a dropdown history of past sessions.
32
36
  */
33
37
  export const SessionHeader = React.forwardRef<HTMLDivElement, SessionHeaderProps>(
34
- ({ sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, className, ...props }, ref) => {
38
+ ({ sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, className, ...props }, ref) => {
35
39
  const activeSession = sessions?.find(s => s.id === activeSessionId);
40
+ const downloadRef = React.useRef<HTMLAnchorElement>(null);
41
+
42
+ const handleDownloadClick = React.useCallback(async () => {
43
+ if (!onDownloadSession) return;
44
+ const result = await onDownloadSession();
45
+ if (!result) return;
46
+ const url = URL.createObjectURL(result.blob);
47
+ const anchor = downloadRef.current;
48
+ if (anchor) {
49
+ anchor.href = url;
50
+ anchor.download = result.filename;
51
+ anchor.click();
52
+ }
53
+ URL.revokeObjectURL(url);
54
+ }, [onDownloadSession]);
36
55
 
37
56
  return (
38
57
  <div
@@ -48,7 +67,12 @@ export const SessionHeader = React.forwardRef<HTMLDivElement, SessionHeaderProps
48
67
  </span>
49
68
  </div>
50
69
  <div className="flex items-center space-x-1 flex-none">
51
- <Button variant="ghost" size="icon" onClick={onNewSession} className="h-8 w-8">
70
+ {onDownloadSession && (
71
+ <Button variant="ghost" size="icon" onClick={handleDownloadClick} className="h-8 w-8" title="Download Chat History">
72
+ <Icon name="download" className="h-4 w-4" />
73
+ </Button>
74
+ )}
75
+ <Button variant="ghost" size="icon" onClick={onNewSession} className="h-8 w-8" title="New Session">
52
76
  <Icon name="plus" className="h-4 w-4" />
53
77
  </Button>
54
78
 
@@ -77,6 +101,7 @@ export const SessionHeader = React.forwardRef<HTMLDivElement, SessionHeaderProps
77
101
  )}
78
102
  </DropdownMenuContent>
79
103
  </DropdownMenu>
104
+ <a ref={downloadRef} style={{ display: 'none' }} />
80
105
  </div>
81
106
  </div>
82
107
  );
@@ -13,6 +13,7 @@ import type { ActionRequest, ReviewConfig, ToolUIState, ToolApproval } from "@/c
13
13
  import type { FormEvent } from "react";
14
14
  import { SessionHeader } from "@/components/composites/SessionHeader";
15
15
  import type { ChatSessionInfo } from "@/components/composites/SessionHeader";
16
+ import type { FileDownloadResult } from "@/components/composites/FileTreeExplorer";
16
17
 
17
18
  /**
18
19
  * RefinementPanel Feature
@@ -127,6 +128,10 @@ export interface RefinementPanelProps {
127
128
  * Handler to switch sessions
128
129
  */
129
130
  onSelectSession?: (id: string) => void;
131
+ /**
132
+ * Handler to download the session chat history
133
+ */
134
+ onDownloadSession?: () => Promise<FileDownloadResult | undefined>;
130
135
  }
131
136
 
132
137
  /**
@@ -154,6 +159,7 @@ export const RefinementPanel = React.memo<RefinementPanelProps>(
154
159
  onNewSession,
155
160
  onCloseSession,
156
161
  onSelectSession,
162
+ onDownloadSession,
157
163
  }) => {
158
164
  // File change queue state
159
165
  const [fileChangeState, setFileChangeState] = React.useState<
@@ -298,6 +304,7 @@ export const RefinementPanel = React.memo<RefinementPanelProps>(
298
304
  onNewSession={onNewSession}
299
305
  onCloseSession={onCloseSession}
300
306
  onSelectSession={onSelectSession}
307
+ onDownloadSession={onDownloadSession}
301
308
  />
302
309
 
303
310
  <AIConversation
@@ -12,6 +12,7 @@ import type { RefinementMessage } from "./RefinementPanel";
12
12
  import type { FileChangeData } from "@/components/composites/FileQueue";
13
13
  import type { ActionRequest, ReviewConfig } from "@/components/composites/ApprovalCard";
14
14
  import type { ToolCall } from "@/components/composites/ToolCallDisplay";
15
+ import type { FileDownloadResult } from "@/components/composites/FileTreeExplorer";
15
16
 
16
17
  /**
17
18
  * Return type for the refinement panel hook
@@ -55,6 +56,9 @@ export interface UseRefinementPanelReturn {
55
56
 
56
57
  /** Action handler for tool interactions */
57
58
  handleToolAction?: (toolCall: ToolCall, action: string) => void;
59
+
60
+ /** Handler to download the session chat history */
61
+ handleDownloadSession?: () => Promise<FileDownloadResult | undefined>;
58
62
  }
59
63
 
60
64
  /**
@@ -37,7 +37,7 @@ import { DocumentEditorWithComments } from '@/components/blocks/DocumentEditorWi
37
37
  import { DocumentTabBar } from '@/components/composites/DocumentTabBar'
38
38
  import { AdjustableLayout } from '@/components/composites/AdjustableLayout'
39
39
  import { FileTreeExplorer } from '@/components/composites/FileTreeExplorer'
40
- import type { FileTreeNode } from '@/components/composites/FileTreeExplorer'
40
+ import type { FileDownloadResult, FileTreeNode } from '@/components/composites/FileTreeExplorer'
41
41
  import { cn } from '@/lib/utils'
42
42
  import type { JSONContent } from '@tiptap/core'
43
43
  import type { Annotation, User } from '@/types/ai-editor/annotations'
@@ -106,7 +106,7 @@ export interface TextEditorMultiTabProps {
106
106
  /** When true, hides the document tab bar — useful for single-file mode where the file tree provides navigation. */
107
107
  hideTabBar?: boolean
108
108
  /** Callback when download all button is clicked in the file tree header */
109
- onDownloadAllFiles?: () => void
109
+ onDownloadAllFiles?: () => Promise<FileDownloadResult | undefined>
110
110
  }
111
111
 
112
112
  /**
@@ -389,7 +389,7 @@ export const TextEditor = React.memo<TextEditorProps>(
389
389
  tree={fileTreeNodes}
390
390
  selectedPath={props.activeDocumentId}
391
391
  onSelect={props.onTabSelect}
392
- onDownloadClick={onDownloadAllFiles}
392
+ onDownload={onDownloadAllFiles}
393
393
  />
394
394
  ),
395
395
  },
package/dist/index.cjs CHANGED
@@ -6005,8 +6005,22 @@ var ApprovalCard = React3__namespace.memo(
6005
6005
  ApprovalCard.displayName = "ApprovalCard";
6006
6006
  var SessionHeader = React3__namespace.forwardRef(
6007
6007
  (_a, ref) => {
6008
- var _b = _a, { sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, className } = _b, props = __objRest(_b, ["sessions", "activeSessionId", "onNewSession", "onCloseSession", "onSelectSession", "className"]);
6008
+ var _b = _a, { sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, className } = _b, props = __objRest(_b, ["sessions", "activeSessionId", "onNewSession", "onCloseSession", "onSelectSession", "onDownloadSession", "className"]);
6009
6009
  const activeSession = sessions == null ? void 0 : sessions.find((s) => s.id === activeSessionId);
6010
+ const downloadRef = React3__namespace.useRef(null);
6011
+ const handleDownloadClick = React3__namespace.useCallback(async () => {
6012
+ if (!onDownloadSession) return;
6013
+ const result = await onDownloadSession();
6014
+ if (!result) return;
6015
+ const url = URL.createObjectURL(result.blob);
6016
+ const anchor = downloadRef.current;
6017
+ if (anchor) {
6018
+ anchor.href = url;
6019
+ anchor.download = result.filename;
6020
+ anchor.click();
6021
+ }
6022
+ URL.revokeObjectURL(url);
6023
+ }, [onDownloadSession]);
6010
6024
  return /* @__PURE__ */ jsxRuntime.jsxs(
6011
6025
  "div",
6012
6026
  __spreadProps(__spreadValues({
@@ -6016,7 +6030,8 @@ var SessionHeader = React3__namespace.forwardRef(
6016
6030
  children: [
6017
6031
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center space-x-2 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate", children: activeSessionId ? (activeSession == null ? void 0 : activeSession.title) || "Untitled Session" : "New Session" }) }),
6018
6032
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-1 flex-none", children: [
6019
- /* @__PURE__ */ jsxRuntime.jsx(Button2, { variant: "ghost", size: "icon", onClick: onNewSession, className: "h-8 w-8", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", className: "h-4 w-4" }) }),
6033
+ onDownloadSession && /* @__PURE__ */ jsxRuntime.jsx(Button2, { variant: "ghost", size: "icon", onClick: handleDownloadClick, className: "h-8 w-8", title: "Download Chat History", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "download", className: "h-4 w-4" }) }),
6034
+ /* @__PURE__ */ jsxRuntime.jsx(Button2, { variant: "ghost", size: "icon", onClick: onNewSession, className: "h-8 w-8", title: "New Session", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", className: "h-4 w-4" }) }),
6020
6035
  /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu2, { children: [
6021
6036
  /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button2, { variant: "ghost", size: "icon", className: "h-8 w-8", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "clock", className: "h-4 w-4" }) }) }),
6022
6037
  /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuContent, { align: "end", className: "w-64 max-h-80 overflow-y-auto", children: [
@@ -6035,7 +6050,8 @@ var SessionHeader = React3__namespace.forwardRef(
6035
6050
  session.id
6036
6051
  )) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-sm text-center text-muted-foreground", children: "No previous sessions" })
6037
6052
  ] })
6038
- ] })
6053
+ ] }),
6054
+ /* @__PURE__ */ jsxRuntime.jsx("a", { ref: downloadRef, style: { display: "none" } })
6039
6055
  ] })
6040
6056
  ]
6041
6057
  })
@@ -6064,7 +6080,8 @@ var RefinementPanel = React3__namespace.memo(
6064
6080
  activeSessionId,
6065
6081
  onNewSession,
6066
6082
  onCloseSession,
6067
- onSelectSession
6083
+ onSelectSession,
6084
+ onDownloadSession
6068
6085
  }) => {
6069
6086
  const [fileChangeState, setFileChangeState] = React3__namespace.useState("approval-requested");
6070
6087
  const [approval, setApproval] = React3__namespace.useState({});
@@ -6177,7 +6194,8 @@ var RefinementPanel = React3__namespace.memo(
6177
6194
  activeSessionId,
6178
6195
  onNewSession,
6179
6196
  onCloseSession,
6180
- onSelectSession
6197
+ onSelectSession,
6198
+ onDownloadSession
6181
6199
  }
6182
6200
  ),
6183
6201
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -11440,12 +11458,26 @@ var FileTreeExplorer = React3__namespace.memo(
11440
11458
  searchPlaceholder = "Filter files...",
11441
11459
  onCreateClick,
11442
11460
  createButtonLabel,
11443
- onDownloadClick,
11461
+ onDownload,
11444
11462
  headerClassName,
11445
11463
  className
11446
11464
  }) => {
11465
+ const downloadRef = React3__namespace.useRef(null);
11447
11466
  const [searchQuery, setSearchQuery] = React3__namespace.useState("");
11448
11467
  const [userExpanded, setUserExpanded] = React3__namespace.useState(defaultExpanded || /* @__PURE__ */ new Set());
11468
+ const handleDownloadClick = React3__namespace.useCallback(async () => {
11469
+ if (!onDownload) return;
11470
+ const result = await onDownload();
11471
+ if (!result) return;
11472
+ const url = URL.createObjectURL(result.blob);
11473
+ const anchor = downloadRef.current;
11474
+ if (anchor) {
11475
+ anchor.href = url;
11476
+ anchor.download = result.filename;
11477
+ anchor.click();
11478
+ }
11479
+ URL.revokeObjectURL(url);
11480
+ }, [onDownload]);
11449
11481
  React3__namespace.useEffect(() => {
11450
11482
  if (selectedPath) {
11451
11483
  const parts = selectedPath.split("/");
@@ -11504,16 +11536,17 @@ var FileTreeExplorer = React3__namespace.memo(
11504
11536
  ]
11505
11537
  }
11506
11538
  ) : null,
11507
- onDownloadClick ? /* @__PURE__ */ jsxRuntime.jsx(
11539
+ onDownload ? /* @__PURE__ */ jsxRuntime.jsx(
11508
11540
  "button",
11509
11541
  {
11510
11542
  type: "button",
11511
- onClick: onDownloadClick,
11543
+ onClick: handleDownloadClick,
11512
11544
  title: "Download all files",
11513
11545
  className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
11514
11546
  children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "download", size: "sm" })
11515
11547
  }
11516
- ) : null
11548
+ ) : null,
11549
+ /* @__PURE__ */ jsxRuntime.jsx("a", { ref: downloadRef, style: { display: "none" } })
11517
11550
  ] }),
11518
11551
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
11519
11552
  FileTree,
@@ -11717,7 +11750,7 @@ ${format === "json" ? formatJson(currentDocument.content) : currentDocument.cont
11717
11750
  tree: fileTreeNodes,
11718
11751
  selectedPath: props.activeDocumentId,
11719
11752
  onSelect: props.onTabSelect,
11720
- onDownloadClick: onDownloadAllFiles
11753
+ onDownload: onDownloadAllFiles
11721
11754
  }
11722
11755
  )
11723
11756
  },