11import { useEffect , useMemo , useState } from 'react' ;
22import { classNames } from '../utils/misc' ;
3- import { Conversation } from '../utils/types' ;
3+ import { Conversation , Message } from '../utils/types' ;
44import StorageUtils from '../utils/storage' ;
55import { useNavigate , useParams } from 'react-router' ;
66import {
77 ArrowDownTrayIcon ,
8+ CloudArrowUpIcon ,
89 EllipsisVerticalIcon ,
910 PencilIcon ,
1011 PencilSquareIcon ,
@@ -15,6 +16,7 @@ import { BtnWithTooltips } from '../utils/common';
1516import { useAppContext } from '../utils/app.context' ;
1617import toast from 'react-hot-toast' ;
1718import { useModals } from './ModalProvider' ;
19+ import { db } from '../utils/storage' ; // am Anfang der Datei
1820
1921export default function Sidebar ( ) {
2022 const params = useParams ( ) ;
@@ -46,6 +48,102 @@ export default function Sidebar() {
4648 [ conversations ]
4749 ) ;
4850
51+ // Export all conversations
52+ const onExportAll = async ( ) => {
53+ if ( conversations . length === 0 ) {
54+ toast . error ( 'No conversations to export' ) ;
55+ return ;
56+ }
57+
58+ try {
59+ const allData = await Promise . all (
60+ conversations . map ( async ( conv ) => {
61+ const messages = await StorageUtils . getMessages ( conv . id ) ;
62+ return { conv, messages } ;
63+ } )
64+ ) ;
65+
66+ const blob = new Blob ( [ JSON . stringify ( allData , null , 2 ) ] , {
67+ type : 'application/json' ,
68+ } ) ;
69+ const url = URL . createObjectURL ( blob ) ;
70+ const a = document . createElement ( 'a' ) ;
71+ a . href = url ;
72+ a . download = `all_conversations_${ new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] } .json` ;
73+ document . body . appendChild ( a ) ;
74+ a . click ( ) ;
75+ document . body . removeChild ( a ) ;
76+ URL . revokeObjectURL ( url ) ;
77+ toast . success ( 'All conversations exported' ) ;
78+ } catch ( err ) {
79+ console . error ( err ) ;
80+ toast . error ( 'Failed to export conversations' ) ;
81+ }
82+ } ;
83+
84+ // Upload and import all conversations
85+ const onUploadAll = async ( ) => {
86+ const input = document . createElement ( 'input' ) ;
87+ input . type = 'file' ;
88+ input . accept = '.json' ;
89+
90+ input . onchange = async ( e ) => {
91+ const file = ( e . target as HTMLInputElement ) ?. files ?. [ 0 ] ;
92+ if ( ! file ) return ;
93+
94+ try {
95+ const text = await file . text ( ) ;
96+ const importedData : { conv : Conversation ; messages : Message [ ] } [ ] =
97+ JSON . parse ( text ) ;
98+
99+ if ( ! Array . isArray ( importedData ) ) {
100+ toast . error ( 'Invalid file format' ) ;
101+ return ;
102+ }
103+
104+ await db . transaction ( 'rw' , db . conversations , db . messages , async ( ) => {
105+ for ( const item of importedData ) {
106+ const { conv, messages } = item ;
107+
108+ // Check if conversation already exists
109+ const existing = await db . conversations . get ( conv . id ) ;
110+ if ( existing ) {
111+ // Optional: skip, overwrite, or rename?
112+ toast ( `Conversation "${ conv . name } " already exists, skipping...` , {
113+ icon : '⚠️' ,
114+ } ) ;
115+ continue ;
116+ }
117+
118+ // Save conversation
119+ await db . conversations . add ( conv ) ;
120+
121+ // Save messages
122+ for ( const msg of messages ) {
123+ await db . messages . put ( msg ) ;
124+ }
125+ }
126+ } ) ;
127+
128+ toast . success (
129+ `Successfully imported ${ importedData . length } conversation(s)`
130+ ) ;
131+ // Refresh the list
132+ setConversations ( await StorageUtils . getAllConversations ( ) ) ;
133+ } catch ( err : unknown ) {
134+ if ( err instanceof Error ) {
135+ console . error ( err ) ;
136+ toast . error ( 'Failed to import: ' + err . message ) ;
137+ } else {
138+ console . error ( err ) ;
139+ toast . error ( 'Failed to import: unknown error' ) ;
140+ }
141+ }
142+ } ;
143+
144+ input . click ( ) ;
145+ } ;
146+
49147 return (
50148 < >
51149 < input
@@ -106,6 +204,26 @@ export default function Sidebar() {
106204 New conversation
107205 </ button >
108206
207+ { /* Export All Button */ }
208+ < button
209+ className = "btn btn-ghost justify-start px-2"
210+ onClick = { onExportAll }
211+ aria-label = "Export all conversations"
212+ >
213+ < ArrowDownTrayIcon className = "w-5 h-5" />
214+ Export all
215+ </ button >
216+
217+ { /* Upload All Button */ }
218+ < button
219+ className = "btn btn-ghost justify-start px-2"
220+ onClick = { onUploadAll }
221+ aria-label = "Upload all conversations"
222+ >
223+ < CloudArrowUpIcon className = "w-5 h-5" />
224+ Upload all
225+ </ button >
226+
109227 { /* list of conversations */ }
110228 { groupedConv . map ( ( group , i ) => (
111229 < div key = { i } role = "group" >
0 commit comments