From 91260c8e30c84cb9cc7224f8f0bdd4d094dd36e7 Mon Sep 17 00:00:00 2001 From: MDietermann Date: Wed, 12 Aug 2026 17:51:00 +0200 Subject: [PATCH] feat: implement chat page with conversation list, message view, and polling --- src/components/ConversationList.tsx | 47 +++++++++++++++++++++++++++++ src/components/MessageInput.tsx | 31 +++++++++++++++++++ src/components/MessageList.tsx | 46 ++++++++++++++++++++++++++++ src/pages/ChatPage.tsx | 31 ++++++++++++++++++- 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/components/ConversationList.tsx create mode 100644 src/components/MessageInput.tsx create mode 100644 src/components/MessageList.tsx diff --git a/src/components/ConversationList.tsx b/src/components/ConversationList.tsx new file mode 100644 index 0000000..36402db --- /dev/null +++ b/src/components/ConversationList.tsx @@ -0,0 +1,47 @@ +import { useState, useEffect } from "react"; +import { invoke } from "@tauri-apps/api/core"; + +interface Conversation { + id: string; + name: string | null; + kind: string; +} + +interface Props { + workspaceId: string; + onSelect: (id: string) => void; + selectedId: string | null; +} + +export default function ConversationList({ workspaceId, onSelect, selectedId }: Props) { + const [conversations, setConversations] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + invoke("list_conversations", { workspaceId }) + .then(setConversations) + .catch((e) => setError(String(e))); + }, [workspaceId]); + + return ( +
+
+ Conversations +
+ {error &&

{error}

} + {conversations.map((c) => ( +
onSelect(c.id)} + style={{ + padding: "10px 16px", + cursor: "pointer", + background: selectedId === c.id ? "#e8f0fe" : "transparent", + }} + > + {c.name ?? `(${c.kind})`} +
+ ))} +
+ ); +} diff --git a/src/components/MessageInput.tsx b/src/components/MessageInput.tsx new file mode 100644 index 0000000..f8caa73 --- /dev/null +++ b/src/components/MessageInput.tsx @@ -0,0 +1,31 @@ +import { useState } from "react"; +import { invoke } from "@tauri-apps/api/core"; + +export default function MessageInput({ conversationId }: { conversationId: string }) { + const [content, setContent] = useState(""); + const [error, setError] = useState(null); + + async function handleSend() { + if (!content.trim()) return; + try { + await invoke("send_message", { conversationId, content }); + setContent(""); + setError(null); + } catch (e) { + setError(String(e)); + } + } + + return ( +
+ {error &&

{error}

} + setContent(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleSend()} + placeholder="Type a message and press Enter…" + style={{ width: "100%", padding: 10, boxSizing: "border-box", fontSize: 14 }} + /> +
+ ); +} diff --git a/src/components/MessageList.tsx b/src/components/MessageList.tsx new file mode 100644 index 0000000..88778da --- /dev/null +++ b/src/components/MessageList.tsx @@ -0,0 +1,46 @@ +import { useState, useEffect, useRef } from "react"; +import { invoke } from "@tauri-apps/api/core"; + +interface Message { + id: string; + user_id: string; + content: string; + created_at: string; +} + +export default function MessageList({ conversationId }: { conversationId: string }) { + const [messages, setMessages] = useState([]); + const [error, setError] = useState(null); + const bottomRef = useRef(null); + + useEffect(() => { + const load = () => + invoke("list_messages", { conversationId }) + .then((msgs) => { + setMessages(msgs); + bottomRef.current?.scrollIntoView({ behavior: "smooth" }); + }) + .catch((e) => setError(String(e))); + + load(); + // ponytail: polling every 3s — replace with WebSocket when server supports it + const id = setInterval(load, 3000); + return () => clearInterval(id); + }, [conversationId]); + + return ( +
+ {error &&

{error}

} + {messages.map((m) => ( +
+ {m.user_id} + + {new Date(m.created_at).toLocaleTimeString()} + +
{m.content}
+
+ ))} +
+
+ ); +} diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 99d6928..199dc62 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -1,3 +1,32 @@ +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import ConversationList from "../components/ConversationList"; +import MessageList from "../components/MessageList"; +import MessageInput from "../components/MessageInput"; + export default function ChatPage() { - return
Chat
; + const { workspaceId } = useParams<{ workspaceId: string }>(); + const [conversationId, setConversationId] = useState(null); + + return ( +
+ +
+ {conversationId ? ( + <> + + + + ) : ( +
+ Select a conversation +
+ )} +
+
+ ); }