對話可切全螢幕,亦可縮回側邊面板回到辦公室
摘要: LIVE 模式對話新增「⛶ 全螢幕」按鈕,展開為置中的全螢幕聊天卡片 (辦公室模糊在後);全螢幕內有「⊟ 回到辦公室」鈕、對話列有「縮小」, 按 Esc 亦可返回。 根本原因: 對話擠在 300px 側欄裡,長對話讀起來侷促,需要一個能專心對話的大畫面, 且要能隨時縮回辦公室繼續看小人。 影響: 新增純 UI 覆蓋層,不影響資料流;側欄與全螢幕共用同一對話元件, 狀態同步(同一 store thread)。取消選取或切換模式時自動關閉全螢幕。 修法: - 抽出 ChatConversation 共用元件(variant: panel | full) - ChatFullscreen 覆蓋層(頂欄 agent 資訊 + Esc 關閉) - store 新增 chatFullscreen / setChatFullscreen;selectAgent(null) 與 clearWorld 時歸零;App 依 chatFullscreen && live && 已選取 渲染覆蓋層 - styles:.chat-full-overlay 毛玻璃背景、卡片版面、full variant 撐滿高度 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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<HTMLDivElement>(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 (
|
||||
<div className={`chat-panel ${variant === "full" ? "chat-panel-full" : ""}`}>
|
||||
<div className="chat-head">
|
||||
<h3>對話</h3>
|
||||
<div className="chat-head-actions">
|
||||
{started && (
|
||||
<button className="link-btn" onClick={() => resetChat(agentId)} disabled={busy}>
|
||||
🔄 新對話
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="link-btn"
|
||||
onClick={() => setChatFullscreen(!isFull)}
|
||||
title={isFull ? "縮回側邊面板" : "全螢幕對話"}
|
||||
>
|
||||
{isFull ? "⊟ 縮小" : "⛶ 全螢幕"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{started && (
|
||||
<div className="chat-list" ref={listRef}>
|
||||
{messages.map((m, i) => (
|
||||
<div key={i} className={`chat-msg ${m.role}`}>
|
||||
{m.text}
|
||||
</div>
|
||||
))}
|
||||
{busy && <div className="chat-msg pending">⋯ 執行中,過程看辦公室裡的小人</div>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<textarea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder={started ? "繼續對話…(Enter 送出)" : "跟這個專案的 Claude 對話…(Enter 送出)"}
|
||||
rows={variant === "full" ? 3 : 2}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
send();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="task-composer-row">
|
||||
{!started ? (
|
||||
<label
|
||||
className="tip"
|
||||
data-tip="第一句話從該專案最近一段「已結束」的 session 接續(--resume)。注意:無法插入正在終端機進行中的互動對話。"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={fromProject}
|
||||
onChange={(e) => setFromProject(e.target.checked)}
|
||||
/>
|
||||
接續專案最近 session
|
||||
</label>
|
||||
) : (
|
||||
<span
|
||||
className="chat-hint tip"
|
||||
data-tip="辦公室對話有自己的 session,每則訊息自動以 --resume 延續,像 claudecodeui 一樣逐則接力。"
|
||||
>
|
||||
✓ 對話自動延續
|
||||
</span>
|
||||
)}
|
||||
<button className="btn assign-btn" onClick={send} disabled={busy || !input.trim()}>
|
||||
{busy ? "執行中…" : "送出"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user