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.
- package/components/composites/FileTreeExplorer/FileTreeExplorer.tsx +25 -4
- package/components/composites/FileTreeExplorer/index.ts +1 -0
- package/components/composites/SessionHeader/SessionHeader.tsx +27 -2
- package/components/features/RefinementPanel/RefinementPanel.tsx +7 -0
- package/components/features/RefinementPanel/useRefinementPanel.d.ts +4 -0
- package/components/features/TextEditor/TextEditor.tsx +3 -3
- package/dist/index.cjs +43 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +43 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
170
|
+
{onDownload ? (
|
|
151
171
|
<button
|
|
152
172
|
type="button"
|
|
153
|
-
onClick={
|
|
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
|
|
@@ -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
|
-
|
|
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?: () =>
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
11539
|
+
onDownload ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
11508
11540
|
"button",
|
|
11509
11541
|
{
|
|
11510
11542
|
type: "button",
|
|
11511
|
-
onClick:
|
|
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
|
-
|
|
11753
|
+
onDownload: onDownloadAllFiles
|
|
11721
11754
|
}
|
|
11722
11755
|
)
|
|
11723
11756
|
},
|