Skip to content

Commit 3b9efbf

Browse files
feat: Messages import/export (#20)
- ability to export messages from settings screen - ability to import messages from settings screen - fix bug with demo conversation upload
1 parent 2dc54f9 commit 3b9efbf

File tree

4 files changed

+549
-143
lines changed

4 files changed

+549
-143
lines changed

public/demo-conversation.json

Lines changed: 82 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,83 @@
1-
{
2-
"demo": true,
3-
"id": "conv-1734086746930",
4-
"lastModified": 1734087548943,
5-
"messages": [
6-
{
7-
"id": 1734086764521,
8-
"role": "user",
9-
"content": "this is a demo conversation, used in dev mode"
10-
},
11-
{
12-
"id": 1734087548327,
13-
"role": "assistant",
14-
"content": "This is the formula:\n\n$\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}$\n\nGiven an input vector \\(\\mathbf{x} = [x_1, x_2, \\ldots, x_n]\\)\n\n\\[\ny_i = \\frac{e^{x_i}}{\\sum_{j=1}^n e^{x_j}}\n\\]\n\n$2x + y = z$\n\nCode block latex:\n```latex\n\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}\n```\n\nTest dollar sign: $1234 $4567\n\nInvalid latex syntax: $E = mc^$ and $$E = mc^$$",
15-
"timings": {
16-
"prompt_n": 1,
17-
"prompt_ms": 28.923,
18-
"predicted_n": 25,
19-
"predicted_ms": 573.016
1+
[
2+
{
3+
"table": "conversations",
4+
"rows": [
5+
{
6+
"id": "conv-1734086746930",
7+
"lastModified": 1734087548943,
8+
"currNode": 1755372088111,
9+
"name": "Technical Demo"
2010
}
21-
},
22-
{
23-
"id": 1734087548328,
24-
"role": "user",
25-
"content": "this is a demo conversation, used in dev mode"
26-
},
27-
{
28-
"id": 1734087548329,
29-
"role": "assistant",
30-
"content": "Code block:\n```js\nconsole.log('hello world')\n```\n```sh\nls -la /dev\n```"
31-
}
32-
]
33-
}
11+
]
12+
},
13+
{
14+
"table": "messages",
15+
"rows": [
16+
{
17+
"id": 1734086746930,
18+
"convId": "conv-1734086746930",
19+
"type": "root",
20+
"timestamp": 1734086746930,
21+
"role": "system",
22+
"content": "",
23+
"parent": -1,
24+
"children": [
25+
1734086764521,
26+
1734087548328
27+
]
28+
},
29+
{
30+
"id": 1734086764521,
31+
"timestamp": 1734086764521,
32+
"type": "text",
33+
"convId": "conv-1734086746930",
34+
"role": "user",
35+
"content": "A LaTeX block demo conversation",
36+
"parent": 1734086746930,
37+
"children": [
38+
1734087548327
39+
]
40+
},
41+
{
42+
"id": 1734087548327,
43+
"timestamp": 1734087548327,
44+
"type": "text",
45+
"convId": "conv-1734086746930",
46+
"model": "gpt-3.5-turbo",
47+
"role": "assistant",
48+
"content": "This is the formula:\n\n$\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}$\n\nGiven an input vector \\(\\mathbf{x} = [x_1, x_2, \\ldots, x_n]\\)\n\n\\[\ny_i = \\frac{e^{x_i}}{\\sum_{j=1}^n e^{x_j}}\n\\]\n\n$2x + y = z$\n\nCode block latex:\n```latex\n\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}\n```\n\nTest dollar sign: $1234 $4567\n\nInvalid latex syntax: $E = mc^$ and $$E = mc^$$",
49+
"parent": 1734086764521,
50+
"children": [],
51+
"timings": {
52+
"prompt_n": 1,
53+
"prompt_ms": 28.923,
54+
"predicted_n": 25,
55+
"predicted_ms": 573.016
56+
}
57+
},
58+
{
59+
"id": 1734087548328,
60+
"timestamp": 1734087548328,
61+
"type": "text",
62+
"convId": "conv-1734086746930",
63+
"role": "user",
64+
"content": "A code block demo conversation",
65+
"parent": 1734086746930,
66+
"children": [
67+
1734087548329
68+
]
69+
},
70+
{
71+
"id": 1734087548329,
72+
"timestamp": 1734087548329,
73+
"type": "text",
74+
"convId": "conv-1734086746930",
75+
"model": "gpt-3.5-turbo",
76+
"role": "assistant",
77+
"content": "Code block:\n```js\nconsole.log('hello world')\n```\n```sh\nls -la /dev\n```",
78+
"parent": 1734087548328,
79+
"children": []
80+
}
81+
]
82+
}
83+
]

src/components/SettingDialog.tsx

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
ArrowDownTrayIcon,
3+
ArrowUpTrayIcon,
24
BeakerIcon,
35
ChatBubbleLeftEllipsisIcon,
46
ChatBubbleLeftRightIcon,
@@ -7,6 +9,7 @@ import {
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

Comments
 (0)