11import {
2+ ArrowDownTrayIcon ,
3+ ArrowUpTrayIcon ,
24 BeakerIcon ,
35 ChatBubbleLeftEllipsisIcon ,
46 ChatBubbleLeftRightIcon ,
79 Cog6ToothIcon ,
810 CogIcon ,
911 CpuChipIcon ,
12+ EyeIcon ,
1013 FunnelIcon ,
1114 HandRaisedIcon ,
1215 RocketLaunchIcon ,
@@ -104,7 +107,10 @@ const toInput = (
104107
105108// --- Setting Tabs Configuration ---
106109
107- const getSettingTabsConfiguration = ( config : Configuration ) : SettingTab [ ] => [
110+ const getSettingTabsConfiguration = (
111+ config : Configuration ,
112+ onClose : ( ) => void
113+ ) : SettingTab [ ] => [
108114 /* General */
109115 {
110116 title : (
@@ -184,6 +190,32 @@ const getSettingTabsConfiguration = (config: Configuration): SettingTab[] => [
184190 </ >
185191 ) ,
186192 fields : [
193+ {
194+ type : SettingInputType . SECTION ,
195+ label : (
196+ < >
197+ < ChatBubbleOvalLeftEllipsisIcon className = { ICON_CLASSNAME } />
198+ Chats
199+ </ >
200+ ) ,
201+ } ,
202+ {
203+ type : SettingInputType . CUSTOM ,
204+ key : 'custom' , // dummy key, won't be used
205+ component : ( ) => < ImportExportComponent onClose = { onClose } /> ,
206+ } ,
207+ {
208+ type : SettingInputType . DELIMETER ,
209+ } ,
210+ {
211+ type : SettingInputType . SECTION ,
212+ label : (
213+ < >
214+ < EyeIcon className = { ICON_CLASSNAME } />
215+ Technical Demo
216+ </ >
217+ ) ,
218+ } ,
187219 {
188220 type : SettingInputType . CUSTOM ,
189221 key : 'custom' , // dummy key, won't be used
@@ -193,17 +225,15 @@ const getSettingTabsConfiguration = (config: Configuration): SettingTab[] => [
193225 const res = await fetch ( '/demo-conversation.json' ) ;
194226 if ( ! res . ok ) throw new Error ( `HTTP error! status: ${ res . status } ` ) ;
195227 const demoConv = await res . json ( ) ;
196- StorageUtils . remove ( demoConv . id ) ;
197- for ( const msg of demoConv . messages ) {
198- StorageUtils . appendMsg ( demoConv . id , msg ) ;
199- }
228+ StorageUtils . importDB ( demoConv ) ;
229+ onClose ( ) ;
200230 } catch ( error ) {
201231 console . error ( 'Failed to import demo conversation:' , error ) ;
202232 }
203233 } ;
204234 return (
205235 < button className = "btn" onClick = { debugImportDemoConv } >
206- (debug) Import demo conversation
236+ Import demo conversation
207237 </ button >
208238 ) ;
209239 } ,
@@ -396,7 +426,7 @@ export default function SettingDialog({
396426 JSON . parse ( JSON . stringify ( config ) )
397427 ) ;
398428 const settingTabs = useMemo < SettingTab [ ] > (
399- ( ) => getSettingTabsConfiguration ( localConfig ) ,
429+ ( ) => getSettingTabsConfiguration ( localConfig , onClose ) ,
400430 [ localConfig ]
401431 ) ;
402432
@@ -684,3 +714,61 @@ const SettingsModalCheckbox: React.FC<BaseInputProps & { value: boolean }> = ({
684714 </ >
685715 ) ;
686716} ;
717+ const ImportExportComponent : React . FC < { onClose : ( ) => void } > = ( {
718+ onClose,
719+ } ) => {
720+ const onExport = async ( ) => {
721+ const data = await StorageUtils . exportDB ( ) ;
722+ const conversationJson = JSON . stringify ( data , null , 2 ) ;
723+ const blob = new Blob ( [ conversationJson ] , {
724+ type : 'application/json' ,
725+ } ) ;
726+ const url = URL . createObjectURL ( blob ) ;
727+ const a = document . createElement ( 'a' ) ;
728+ a . href = url ;
729+ a . download = `database.json` ;
730+ document . body . appendChild ( a ) ;
731+ a . click ( ) ;
732+ document . body . removeChild ( a ) ;
733+ URL . revokeObjectURL ( url ) ;
734+ } ;
735+
736+ const onImport = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
737+ try {
738+ const files = e . target . files ;
739+ if ( ! files || files . length != 1 ) return false ;
740+ const data = await files [ 0 ] . text ( ) ;
741+ await StorageUtils . importDB ( JSON . parse ( data ) ) ;
742+ onClose ( ) ;
743+ } catch ( error ) {
744+ console . error ( 'Failed to import file:' , error ) ;
745+ }
746+ } ;
747+
748+ return (
749+ < div className = "grid grid-cols-[min-content_min-content] gap-2" >
750+ < button className = "btn" onClick = { onExport } >
751+ < ArrowDownTrayIcon className = { ICON_CLASSNAME } />
752+ Export
753+ </ button >
754+
755+ < input
756+ id = "file-import"
757+ type = "file"
758+ accept = ".json"
759+ onInput = { onImport }
760+ hidden
761+ />
762+ < label
763+ htmlFor = "file-import"
764+ className = "btn"
765+ aria-label = "Import file"
766+ tabIndex = { 0 }
767+ role = "button"
768+ >
769+ < ArrowUpTrayIcon className = { ICON_CLASSNAME } />
770+ Import
771+ </ label >
772+ </ div >
773+ ) ;
774+ } ;
0 commit comments