diff --git a/src/App.tsx b/src/App.tsx index 37c1ebd..bfb98a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import { useEffect } from "react"; +import { ChatFullscreen } from "./components/ChatFullscreen"; import { FloorPlan } from "./components/FloorPlan"; import { HeaderBar } from "./components/HeaderBar"; import { SidePanel } from "./components/SidePanel"; @@ -8,12 +9,17 @@ import { loadPreferredMode, useOfficeStore } from "./store/office-store"; export default function App() { const theme = useOfficeStore((s) => s.theme); const panelOpen = useOfficeStore((s) => s.panelOpen); + const chatFullscreen = useOfficeStore((s) => s.chatFullscreen); + const selectedAgentId = useOfficeStore((s) => s.selectedAgentId); + const mode = useOfficeStore((s) => s.mode); useEffect(() => { startRuntime(); if (loadPreferredMode() === "live") setMode("live"); }, []); + const showFullscreenChat = chatFullscreen && mode === "live" && selectedAgentId !== null; + return (
@@ -21,6 +27,7 @@ export default function App() { {panelOpen && }
+ {showFullscreenChat && } ); } diff --git a/src/components/ChatConversation.tsx b/src/components/ChatConversation.tsx new file mode 100644 index 0000000..22459ed --- /dev/null +++ b/src/components/ChatConversation.tsx @@ -0,0 +1,109 @@ +import { useEffect, useRef, useState } from "react"; +import { useOfficeStore } from "@/store/office-store"; +import { resetChat, sendChat } from "@/gateway/live"; + +/** + * Two-way chat with one project — each message runs headless and is stitched + * to the previous with --resume. Shared by the side panel and the full-screen + * overlay via the `variant` prop. + */ +export function ChatConversation({ + agentId, + variant, +}: { + agentId: string; + variant: "panel" | "full"; +}) { + const thread = useOfficeStore((s) => s.chats.get(agentId)); + const setChatFullscreen = useOfficeStore((s) => s.setChatFullscreen); + const isFull = useOfficeStore((s) => s.chatFullscreen); + const [input, setInput] = useState(""); + const [fromProject, setFromProject] = useState(false); + const listRef = useRef(null); + + const busy = thread?.busy ?? false; + const messages = thread?.messages ?? []; + const started = messages.length > 0; + + useEffect(() => { + listRef.current?.scrollTo({ top: listRef.current.scrollHeight }); + }, [messages.length, busy]); + + const send = () => { + const text = input.trim(); + if (!text || busy) return; + sendChat(agentId, text, fromProject); + setInput(""); + }; + + return ( +
+
+

對話

+
+ {started && ( + + )} + +
+
+ + {started && ( +
+ {messages.map((m, i) => ( +
+ {m.text} +
+ ))} + {busy &&
⋯ 執行中,過程看辦公室裡的小人
} +
+ )} + +