diff --git a/backend/routes/export.py b/backend/routes/export.py index 8eb9405..35b7846 100644 --- a/backend/routes/export.py +++ b/backend/routes/export.py @@ -72,7 +72,7 @@ async def export_messages(req: ExportMessagesRequest): if req.format == ExportFormat.json: content = json.dumps({"messages": messages, "exported_at": ts}, indent=2, ensure_ascii=False) media = "application/json" - filename = f"localmind_messages_{ts.replace(' ', '_')}.json" + filename = f"localmind_messages_{ts.replace(' ', '_').replace(':', '-')}.json" elif req.format == ExportFormat.markdown: lines = ["# LocalMind – Exported Messages\n", f"*Exported: {ts}*\n\n---\n"] @@ -84,7 +84,7 @@ async def export_messages(req: ExportMessagesRequest): lines.append("\n---\n") content = "\n".join(lines) media = "text/markdown" - filename = f"localmind_messages_{ts.replace(' ', '_')}.md" + filename = f"localmind_messages_{ts.replace(' ', '_').replace(':', '-')}.md" else: lines = ["LocalMind Export — Selected Messages", f"Exported: {ts}", "=" * 50, ""] @@ -93,7 +93,7 @@ async def export_messages(req: ExportMessagesRequest): lines += [f"[{role}]", m["content"], ""] content = "\n".join(lines) media = "text/plain" - filename = f"localmind_messages_{ts.replace(' ', '_')}.txt" + filename = f"localmind_messages_{ts.replace(' ', '_').replace(':', '-')}.txt" return Response( content=content.encode("utf-8"), diff --git a/frontend/src/components/ChatWindow.jsx b/frontend/src/components/ChatWindow.jsx index 2faadb0..90d32ab 100644 --- a/frontend/src/components/ChatWindow.jsx +++ b/frontend/src/components/ChatWindow.jsx @@ -1,82 +1,23 @@ import { useState, useRef, useEffect } from "react"; import { exportSession } from "../utils/api"; -import { AppLogoIcon, CloseIcon, FileIcon, LockIcon, PlusCircleIcon, TemplateIcon } from "./Icons"; -import CodeBlockWithCopy from "./CodeBlockWithCopy"; -import PromptTemplateDialog from "./PromptTemplateDialog"; +import { AppLogoIcon, FileIcon, LockIcon } from "./Icons"; export default function ChatWindow({ messages, loading, onSend, sessionId }) { const [input, setInput] = useState(""); const bottomRef = useRef(null); const textareaRef = useRef(null); - // NEW: state for selected messages and export format - const [selectedMessages, setSelectedMessages] = useState([]); - const [exportFormat, setExportFormat] = useState("markdown"); - useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); - // Close plus menu on outside click - useEffect(() => { - function handleClickOutside(e) { - if (plusMenuRef.current && !plusMenuRef.current.contains(e.target)) { - setShowPlusMenu(false); - } - } - if (showPlusMenu) document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, [showPlusMenu]); - - function handleSelectTemplate(template) { - setSelectedTemplate(template); - setShowTemplateDialog(false); - setShowPlusMenu(false); - setTimeout(() => textareaRef.current?.focus(), 0); - } - - // Parse code blocks for copy button - function parseMessageWithCodeBlocks(content) { - if (!content) return [{ type: "text", content: "" }]; - const parts = []; - const regex = /```(\w*)\n([\s\S]*?)```/g; - let lastIndex = 0; - let match; - while ((match = regex.exec(content)) !== null) { - if (match.index > lastIndex) { - parts.push({ type: "text", content: content.slice(lastIndex, match.index) }); - } - parts.push({ - type: "code", - language: match[1] || "text", - code: match[2].trim() - }); - lastIndex = match.index + match[0].length; - } - if (lastIndex < content.length) { - parts.push({ type: "text", content: content.slice(lastIndex) }); - } - if (parts.length === 0) { - parts.push({ type: "text", content }); - } - return parts; - } - function send() { - if ((!input.trim() && !selectedTemplate) || loading) return; - const message = selectedTemplate - ? `${selectedTemplate.prompt}\n\n${input.trim()}`.trim() - : input.trim(); - onSend(message); + if (!input.trim() || loading) return; + onSend(input.trim()); setInput(""); - if (textareaRef.current) { - textareaRef.current.style.height = "auto"; - } + if (textareaRef.current) { textareaRef.current.style.height = "auto"; } } function handleKey(e) { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - send(); - } + if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } } function autoResize(e) { @@ -84,56 +25,6 @@ export default function ChatWindow({ messages, loading, onSend, sessionId }) { e.target.style.height = Math.min(e.target.scrollHeight, 160) + "px"; } - // Message selection and export - const toggleSelectMessage = (msgId) => { - setSelectedMessages(prev => - prev.includes(msgId) ? prev.filter(id => id !== msgId) : [...prev, msgId] - ); - }; - - const handleExportSelected = async () => { - if (selectedMessages.length === 0) return; - try { - const response = await fetch("/api/export/messages", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message_ids: selectedMessages, format: exportFormat }), - }); - if (!response.ok) throw new Error("Export failed"); - const blob = await response.blob(); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = `localmind_export.${exportFormat === "markdown" ? "md" : exportFormat}`; - a.click(); - URL.revokeObjectURL(url); - } catch (err) { - console.error(err); - alert("Failed to export messages"); - } - }; - - const exportSingleMessage = async (msgId) => { - try { - const response = await fetch("/api/export/messages", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message_ids: [msgId], format: exportFormat }), - }); - if (!response.ok) throw new Error("Export failed"); - const blob = await response.blob(); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = `localmind_message_${msgId}.${exportFormat === "markdown" ? "md" : exportFormat}`; - a.click(); - URL.revokeObjectURL(url); - } catch (err) { - console.error(err); - alert("Failed to export message"); - } - }; - const SUGGESTIONS = [ "Summarize the uploaded document", "What are the key points?", @@ -143,67 +34,31 @@ export default function ChatWindow({ messages, loading, onSend, sessionId }) { return (
LocalMind is ready
100% private · runs offline · no cloud