diff --git a/src/chat/MessageHeader.tsx b/src/chat/MessageHeader.tsx index 2ad16f3..dee0209 100644 --- a/src/chat/MessageHeader.tsx +++ b/src/chat/MessageHeader.tsx @@ -1,10 +1,20 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; +import { + MenuContent, + MenuItem, + MenuItemCommand, + MenuItemGroup, + MenuRadioItem, + MenuRadioItemGroup, + MenuRoot, + MenuSeparator, + MenuTrigger, +} from "../components/ui/menu" import { Avatar, Card, HStack, Stack, Text, IconButton, Button, MenuItemGroup, MenuSeparator, Menu, Popover } from '@chakra-ui/react'; import { PopoverRoot, PopoverTrigger, PopoverContent, PopoverArrow, PopoverBody, PopoverTitle } from '../components/ui/popover'; import { useTranslation } from 'react-i18next'; -import { AiOutlineClear } from 'react-icons/ai'; -import { IoChatboxOutline } from "react-icons/io5"; -import { IoSettingsOutline, IoReloadOutline, IoLogoGithub } from 'react-icons/io5'; +import { IoLogoMarkdown, IoSettingsOutline, IoReloadOutline, IoLogoGithub } from 'react-icons/io5'; +import { BiSolidFileJson } from "react-icons/bi"; import { LuPanelLeftClose, LuPanelLeftOpen } from 'react-icons/lu'; import { MdOutlineSimCardDownload } from 'react-icons/md'; import { CgOptions } from "react-icons/cg"; @@ -97,8 +107,22 @@ export function MessageHeader() { <MessageMenu /> {options.openai.mode == "assistant" ? <IconButton variant="ghost" title={t("chat_settings")} onClick={showSettings}><IoSettingsOutline /></IconButton> : null} {false && <IconButton variant="ghost" title={t("reload_thread")} onClick={reloadThread}><IoReloadOutline /></IconButton>} - <IconButton variant="ghost" title={t("download_thread")} onClick={downloadThread}><MdOutlineSimCardDownload /></IconButton> - <GitHubMenu /> + + <MenuRoot> + <MenuTrigger> + <IconButton variant="ghost" title={t("download_thread")}><MdOutlineSimCardDownload /></IconButton> + </MenuTrigger> + <MenuContent> + <MenuItem value="json" onClick={() => downloadThread("json")} > + <BiSolidFileJson /> {t("download_json")} + </MenuItem> + <MenuItem value="markdown" onClick={() => downloadThread("markdown")} > + <IoLogoMarkdown /> {t("download_markdown")} + </MenuItem> + </MenuContent> + </MenuRoot> + + <a href={issueUrl} target="_blank" title={t("open_issue")}><IconButton variant="ghost" aria-label={t("open_issue")}><IoLogoGithub /></IconButton></a> <PopoverRoot> <PopoverTrigger data-testid="UserInformationBtn"> diff --git a/src/chat/context/action.ts b/src/chat/context/action.ts index 79a18b4..598615d 100644 --- a/src/chat/context/action.ts +++ b/src/chat/context/action.ts @@ -182,16 +182,31 @@ export default function action(state: Partial<GlobalState>, dispatch: React.Disp }) }, - downloadThread() { + downloadThread(format = "json") { const chat = state.chat[state.currentChat]; const messages = chat.messages; - const content = JSON.stringify(messages, null, 2); - const blob = new Blob([content], { type: "application/json" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = `chat_${chat.id}.json`; - a.click(); + + if (format === "markdown") { + const content = messages + .map((m) => { + return `${m.role === "assistant" ? "## Assistant\n" : "## User\n"}${m.content}\n\n`; + }) + .join("\n"); + const blob = new Blob([content], { type: "text/markdown" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `chat_${chat.id}.md`; + a.click(); + } else if (format === "json") { + const content = JSON.stringify(messages, null, 2); + const blob = new Blob([content], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `chat_${chat.id}.json`; + a.click(); + } }, reloadThread() { diff --git a/src/chat/context/types.ts b/src/chat/context/types.ts index 435ae0a..928fb89 100644 --- a/src/chat/context/types.ts +++ b/src/chat/context/types.ts @@ -101,7 +101,7 @@ export type GlobalActions = { setMessage: (content: string) => void; clearThread: () => void; reloadThread: () => void; - downloadThread: () => void; + downloadThread: (format?: string) => void; editMessage: (id: number) => void; removeMessage: (id: number) => void; setOptions: (arg: OptionAction) => void; diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 42c5827..be1798f 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -183,12 +183,16 @@ i18n "hide_sidebar": "Seitenleiste ausblenden", "show_sidebar": "Seitenleiste einblenden", "chats": "Chats", + + "download_json": "als JSON herunterladen", + "download_markdown": "als Markdown herunterladen", chat_options: "Chat-Optionen", tool_options: "Werkzeug-Optionen", model_options: "Modell", release_notes: "Versionshinweise", web_search_call: "Web-Suche", web_search_call_description: "Bei dieser Antwort wurde eine Web-Suche durchgeführt.", + }, }, },