add import/export (#71)
This commit is contained in:
parent
972a5aff23
commit
f0c575b40d
|
@ -0,0 +1,34 @@
|
||||||
|
import { Conversation } from "@/types";
|
||||||
|
import { IconFileImport } from "@tabler/icons-react";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onImport: (conversations: Conversation[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Import: FC<Props> = ({ onImport }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex py-3 px-3 gap-3 rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer w-full items-center">
|
||||||
|
<input
|
||||||
|
className="opacity-0 absolute w-[200px] cursor-pointer"
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!e.target.files?.length) return;
|
||||||
|
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const conversations: Conversation[] = JSON.parse(e.target?.result as string);
|
||||||
|
onImport(conversations);
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center gap-3 text-left">
|
||||||
|
<IconFileImport size={16} />
|
||||||
|
<div>Import conversations</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -19,9 +19,11 @@ interface Props {
|
||||||
onUpdateConversation: (conversation: Conversation, data: KeyValuePair) => void;
|
onUpdateConversation: (conversation: Conversation, data: KeyValuePair) => void;
|
||||||
onApiKeyChange: (apiKey: string) => void;
|
onApiKeyChange: (apiKey: string) => void;
|
||||||
onClearConversations: () => void;
|
onClearConversations: () => void;
|
||||||
|
onExportConversations: () => void;
|
||||||
|
onImportConversations: (conversations: Conversation[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, apiKey, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onUpdateConversation, onApiKeyChange, onClearConversations }) => {
|
export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, apiKey, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onUpdateConversation, onApiKeyChange, onClearConversations, onExportConversations, onImportConversations }) => {
|
||||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||||
const [filteredConversations, setFilteredConversations] = useState<Conversation[]>(conversations);
|
const [filteredConversations, setFilteredConversations] = useState<Conversation[]>(conversations);
|
||||||
|
|
||||||
|
@ -34,7 +36,7 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected
|
||||||
}, [searchTerm, conversations]);
|
}, [searchTerm, conversations]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`h-full flex flex-col flex-none space-y-2 p-2 flex-col bg-[#202123] w-[260px] z-10 sm:relative sm:top-0 absolute top-12 bottom-0`}>
|
<div className={`h-full flex flex-none space-y-2 p-2 flex-col bg-[#202123] w-[260px] z-10 sm:relative sm:top-0 absolute top-12 bottom-0`}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<button
|
<button
|
||||||
className="flex gap-3 p-3 items-center w-full sm:w-[200px] rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer text-sm flex-shrink-0 border border-white/20"
|
className="flex gap-3 p-3 items-center w-full sm:w-[200px] rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer text-sm flex-shrink-0 border border-white/20"
|
||||||
|
@ -87,6 +89,8 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected
|
||||||
onToggleLightMode={onToggleLightMode}
|
onToggleLightMode={onToggleLightMode}
|
||||||
onApiKeyChange={onApiKeyChange}
|
onApiKeyChange={onApiKeyChange}
|
||||||
onClearConversations={onClearConversations}
|
onClearConversations={onClearConversations}
|
||||||
|
onExportConversations={onExportConversations}
|
||||||
|
onImportConversations={onImportConversations}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const SidebarButton: FC<Props> = ({ text, icon, onClick }) => {
|
||||||
className="flex py-3 px-3 gap-3 rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer w-full items-center"
|
className="flex py-3 px-3 gap-3 rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer w-full items-center"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div className="">{icon}</div>
|
<div>{icon}</div>
|
||||||
<div>{text}</div>
|
<div>{text}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { IconMoon, IconSun } from "@tabler/icons-react";
|
import { Conversation } from "@/types";
|
||||||
|
import { IconFileExport, IconMoon, IconSun } from "@tabler/icons-react";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { ClearConversations } from "./ClearConversations";
|
import { ClearConversations } from "./ClearConversations";
|
||||||
|
import { Import } from "./Import";
|
||||||
import { Key } from "./Key";
|
import { Key } from "./Key";
|
||||||
import { SidebarButton } from "./SidebarButton";
|
import { SidebarButton } from "./SidebarButton";
|
||||||
|
|
||||||
|
@ -10,18 +12,29 @@ interface Props {
|
||||||
onToggleLightMode: (mode: "light" | "dark") => void;
|
onToggleLightMode: (mode: "light" | "dark") => void;
|
||||||
onApiKeyChange: (apiKey: string) => void;
|
onApiKeyChange: (apiKey: string) => void;
|
||||||
onClearConversations: () => void;
|
onClearConversations: () => void;
|
||||||
|
onExportConversations: () => void;
|
||||||
|
onImportConversations: (conversations: Conversation[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SidebarSettings: FC<Props> = ({ lightMode, apiKey, onToggleLightMode, onApiKeyChange, onClearConversations }) => {
|
export const SidebarSettings: FC<Props> = ({ lightMode, apiKey, onToggleLightMode, onApiKeyChange, onClearConversations, onExportConversations, onImportConversations }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col pt-1 items-center border-t border-white/20 text-sm space-y-1">
|
<div className="flex flex-col pt-1 items-center border-t border-white/20 text-sm space-y-1">
|
||||||
<ClearConversations onClearConversations={onClearConversations} />
|
<ClearConversations onClearConversations={onClearConversations} />
|
||||||
|
|
||||||
|
<Import onImport={onImportConversations} />
|
||||||
|
|
||||||
|
<SidebarButton
|
||||||
|
text="Export conversations"
|
||||||
|
icon={<IconFileExport size={16} />}
|
||||||
|
onClick={() => onExportConversations()}
|
||||||
|
/>
|
||||||
|
|
||||||
<SidebarButton
|
<SidebarButton
|
||||||
text={lightMode === "light" ? "Dark mode" : "Light mode"}
|
text={lightMode === "light" ? "Dark mode" : "Light mode"}
|
||||||
icon={lightMode === "light" ? <IconMoon size={16} /> : <IconSun size={16} />}
|
icon={lightMode === "light" ? <IconMoon size={16} /> : <IconSun size={16} />}
|
||||||
onClick={() => onToggleLightMode(lightMode === "light" ? "dark" : "light")}
|
onClick={() => onToggleLightMode(lightMode === "light" ? "dark" : "light")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Key
|
<Key
|
||||||
apiKey={apiKey}
|
apiKey={apiKey}
|
||||||
onApiKeyChange={onApiKeyChange}
|
onApiKeyChange={onApiKeyChange}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { ChatBody, Conversation, KeyValuePair, Message, OpenAIModel, OpenAIModel
|
||||||
import { cleanConversationHistory, cleanSelectedConversation } from "@/utils/app/clean";
|
import { cleanConversationHistory, cleanSelectedConversation } from "@/utils/app/clean";
|
||||||
import { DEFAULT_SYSTEM_PROMPT } from "@/utils/app/const";
|
import { DEFAULT_SYSTEM_PROMPT } from "@/utils/app/const";
|
||||||
import { saveConversation, saveConversations, updateConversation } from "@/utils/app/conversation";
|
import { saveConversation, saveConversations, updateConversation } from "@/utils/app/conversation";
|
||||||
|
import { exportConversations, importConversations } from "@/utils/app/data";
|
||||||
import { IconArrowBarLeft, IconArrowBarRight } from "@tabler/icons-react";
|
import { IconArrowBarLeft, IconArrowBarRight } from "@tabler/icons-react";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
@ -181,6 +182,16 @@ export default function Home() {
|
||||||
localStorage.setItem("apiKey", apiKey);
|
localStorage.setItem("apiKey", apiKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleExportConversations = () => {
|
||||||
|
exportConversations();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImportConversations = (conversations: Conversation[]) => {
|
||||||
|
importConversations(conversations);
|
||||||
|
setConversations(conversations);
|
||||||
|
setSelectedConversation(conversations[conversations.length - 1]);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectConversation = (conversation: Conversation) => {
|
const handleSelectConversation = (conversation: Conversation) => {
|
||||||
setSelectedConversation(conversation);
|
setSelectedConversation(conversation);
|
||||||
saveConversation(conversation);
|
saveConversation(conversation);
|
||||||
|
@ -343,6 +354,8 @@ export default function Home() {
|
||||||
onUpdateConversation={handleUpdateConversation}
|
onUpdateConversation={handleUpdateConversation}
|
||||||
onApiKeyChange={handleApiKeyChange}
|
onApiKeyChange={handleApiKeyChange}
|
||||||
onClearConversations={handleClearConversations}
|
onClearConversations={handleClearConversations}
|
||||||
|
onExportConversations={handleExportConversations}
|
||||||
|
onImportConversations={handleImportConversations}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconArrowBarLeft
|
<IconArrowBarLeft
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Conversation } from "@/types";
|
||||||
|
|
||||||
|
export const exportConversations = () => {
|
||||||
|
const history = localStorage.getItem("conversationHistory");
|
||||||
|
|
||||||
|
if (!history) return;
|
||||||
|
|
||||||
|
const blob = new Blob([history], { type: "application/json" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = "chatbot_ui_history.json";
|
||||||
|
link.href = url;
|
||||||
|
link.style.display = "none";
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const importConversations = (conversations: Conversation[]) => {
|
||||||
|
localStorage.setItem("conversationHistory", JSON.stringify(conversations));
|
||||||
|
};
|
Loading…
Reference in New Issue