aboutsummaryrefslogtreecommitdiffstats
path: root/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'renderer')
-rw-r--r--renderer/src/App.tsx42
-rw-r--r--renderer/src/components/ChatPane.tsx80
-rw-r--r--renderer/src/styles/globals.css77
3 files changed, 169 insertions, 30 deletions
diff --git a/renderer/src/App.tsx b/renderer/src/App.tsx
index 7d75196..c3eafd4 100644
--- a/renderer/src/App.tsx
+++ b/renderer/src/App.tsx
@@ -63,15 +63,47 @@ export function App() {
63 () => (localStorage.getItem("cf-theme") as Theme) ?? "dark" 63 () => (localStorage.getItem("cf-theme") as Theme) ?? "dark"
64 ); 64 );
65 65
66 const [chatWidth, setChatWidth] = useState<number>(
67 () => Number(localStorage.getItem("cf-chat-width")) || 380
68 );
69 const [chatCollapsed, setChatCollapsed] = useState<boolean>(
70 () => localStorage.getItem("cf-chat-collapsed") === "true"
71 );
72
66 // Keep document.documentElement in sync and persist to localStorage 73 // Keep document.documentElement in sync and persist to localStorage
67 useEffect(() => { 74 useEffect(() => {
68 document.documentElement.setAttribute("data-theme", theme); 75 document.documentElement.setAttribute("data-theme", theme);
69 localStorage.setItem("cf-theme", theme); 76 localStorage.setItem("cf-theme", theme);
70 }, [theme]); 77 }, [theme]);
71 78
79 useEffect(() => {
80 localStorage.setItem("cf-chat-width", String(chatWidth));
81 }, [chatWidth]);
82
83 useEffect(() => {
84 localStorage.setItem("cf-chat-collapsed", String(chatCollapsed));
85 }, [chatCollapsed]);
86
72 const handleToggleTheme = () => 87 const handleToggleTheme = () =>
73 setTheme((t) => (t === "dark" ? "light" : "dark")); 88 setTheme((t) => (t === "dark" ? "light" : "dark"));
74 89
90 const handleResizeMouseDown = (e: React.MouseEvent) => {
91 e.preventDefault();
92 const startX = e.clientX;
93 const startWidth = chatWidth;
94 const onMove = (ev: MouseEvent) => {
95 const delta = startX - ev.clientX; // drag left = wider chat
96 const next = Math.max(180, Math.min(700, startWidth + delta));
97 setChatWidth(next);
98 };
99 const onUp = () => {
100 document.removeEventListener("mousemove", onMove);
101 document.removeEventListener("mouseup", onUp);
102 };
103 document.addEventListener("mousemove", onMove);
104 document.addEventListener("mouseup", onUp);
105 };
106
75 const hasChanges = documentContent !== originalContent; 107 const hasChanges = documentContent !== originalContent;
76 108
77 // Clear error after 5 seconds 109 // Clear error after 5 seconds
@@ -380,6 +412,13 @@ export function App() {
380 theme={theme} 412 theme={theme}
381 /> 413 />
382 414
415 {!chatCollapsed && (
416 <div
417 className="chat-resize-handle"
418 onMouseDown={handleResizeMouseDown}
419 />
420 )}
421
383 <ChatPane 422 <ChatPane
384 messages={messages} 423 messages={messages}
385 onSend={handleSendMessage} 424 onSend={handleSendMessage}
@@ -390,6 +429,9 @@ export function App() {
390 ? `Chat with Claude (${selectedSession.phase})...` 429 ? `Chat with Claude (${selectedSession.phase})...`
391 : "Select a session to start" 430 : "Select a session to start"
392 } 431 }
432 collapsed={chatCollapsed}
433 chatWidth={chatWidth}
434 onToggleCollapse={() => setChatCollapsed((c) => !c)}
393 /> 435 />
394 </div> 436 </div>
395 437
diff --git a/renderer/src/components/ChatPane.tsx b/renderer/src/components/ChatPane.tsx
index 917d462..40e682c 100644
--- a/renderer/src/components/ChatPane.tsx
+++ b/renderer/src/components/ChatPane.tsx
@@ -7,6 +7,9 @@ interface ChatPaneProps {
7 isLoading: boolean; 7 isLoading: boolean;
8 disabled: boolean; 8 disabled: boolean;
9 placeholder: string; 9 placeholder: string;
10 collapsed: boolean;
11 chatWidth: number;
12 onToggleCollapse: () => void;
10} 13}
11 14
12export function ChatPane({ 15export function ChatPane({
@@ -15,6 +18,9 @@ export function ChatPane({
15 isLoading, 18 isLoading,
16 disabled, 19 disabled,
17 placeholder, 20 placeholder,
21 collapsed,
22 chatWidth,
23 onToggleCollapse,
18}: ChatPaneProps) { 24}: ChatPaneProps) {
19 const [input, setInput] = useState(""); 25 const [input, setInput] = useState("");
20 const messagesEndRef = useRef<HTMLDivElement>(null); 26 const messagesEndRef = useRef<HTMLDivElement>(null);
@@ -30,37 +36,53 @@ export function ChatPane({
30 }; 36 };
31 37
32 return ( 38 return (
33 <div className="chat-pane"> 39 <div
34 <div className="chat-messages"> 40 className="chat-pane"
35 {messages.map((msg) => ( 41 style={{ width: collapsed ? 28 : chatWidth }}
36 <div key={msg.id} className={`message ${msg.role}`}> 42 >
37 <div className="message-content">{msg.content}</div> 43 <div className="chat-header">
38 </div> 44 {!collapsed && <span>Chat</span>}
39 ))} 45 <button className="chat-collapse-btn" onClick={onToggleCollapse}>
40 {isLoading && ( 46 {collapsed ? "⟨" : "⟩"}
41 <div className="message assistant loading">
42 <div className="message-content">Thinking...</div>
43 </div>
44 )}
45 <div ref={messagesEndRef} />
46 </div>
47
48 <div className="chat-input">
49 <input
50 type="text"
51 value={input}
52 onChange={(e) => setInput(e.target.value)}
53 onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && handleSend()}
54 placeholder={placeholder}
55 disabled={disabled || isLoading}
56 />
57 <button
58 onClick={handleSend}
59 disabled={disabled || isLoading || !input.trim()}
60 >
61 Send
62 </button> 47 </button>
63 </div> 48 </div>
49
50 {!collapsed && (
51 <>
52 <div className="chat-messages">
53 {messages.map((msg) => (
54 <div key={msg.id} className={`message ${msg.role}`}>
55 <div className="message-content">{msg.content}</div>
56 </div>
57 ))}
58 {isLoading && (
59 <div className="message assistant loading">
60 <div className="message-content">Thinking...</div>
61 </div>
62 )}
63 <div ref={messagesEndRef} />
64 </div>
65
66 <div className="chat-input">
67 <input
68 type="text"
69 value={input}
70 onChange={(e) => setInput(e.target.value)}
71 onKeyDown={(e) =>
72 e.key === "Enter" && !e.shiftKey && handleSend()
73 }
74 placeholder={placeholder}
75 disabled={disabled || isLoading}
76 />
77 <button
78 onClick={handleSend}
79 disabled={disabled || isLoading || !input.trim()}
80 >
81 Send
82 </button>
83 </div>
84 </>
85 )}
64 </div> 86 </div>
65 ); 87 );
66} 88}
diff --git a/renderer/src/styles/globals.css b/renderer/src/styles/globals.css
index 8e43f3a..03c7443 100644
--- a/renderer/src/styles/globals.css
+++ b/renderer/src/styles/globals.css
@@ -408,10 +408,57 @@ html[data-theme="light"] .session-rename-input {
408 408
409/* ── Chat Pane ───────────────────────────────────────────────── */ 409/* ── Chat Pane ───────────────────────────────────────────────── */
410.chat-pane { 410.chat-pane {
411 width: 380px;
412 display: flex; 411 display: flex;
413 flex-direction: column; 412 flex-direction: column;
414 background: var(--bg-secondary); 413 background: var(--bg-secondary);
414 min-width: 0;
415 overflow: hidden;
416 transition: width 0.2s ease;
417}
418
419/* ── Chat Resize Handle ──────────────────────────────────────── */
420.chat-resize-handle {
421 width: 5px;
422 background: transparent;
423 cursor: col-resize;
424 flex-shrink: 0;
425 transition: background 0.15s;
426}
427
428.chat-resize-handle:hover {
429 background: var(--accent);
430}
431
432/* ── Chat Header Strip ───────────────────────────────────────── */
433.chat-header {
434 display: flex;
435 justify-content: space-between;
436 align-items: center;
437 padding: 7px 12px;
438 background: var(--bg-secondary);
439 border-bottom: 1px solid var(--border);
440 font-size: 11px;
441 letter-spacing: 0.07em;
442 text-transform: uppercase;
443 color: var(--text-secondary);
444 flex-shrink: 0;
445}
446
447.chat-collapse-btn {
448 background: transparent;
449 border: 1px solid var(--border);
450 border-radius: 2px;
451 color: var(--text-secondary);
452 cursor: pointer;
453 font-size: 12px;
454 padding: 2px 7px;
455 font-family: inherit;
456 transition: background 0.15s, color 0.15s;
457}
458
459.chat-collapse-btn:hover {
460 background: var(--bg-tertiary);
461 color: var(--text-primary);
415} 462}
416 463
417.chat-messages { 464.chat-messages {
@@ -998,3 +1045,31 @@ html[data-theme="light"] .settings-textarea:focus {
998 background: var(--bg-tertiary); 1045 background: var(--bg-tertiary);
999 color: var(--text-primary); 1046 color: var(--text-primary);
1000} 1047}
1048
1049/* ── Scrollbars ──────────────────────────────────────────────── */
1050.chat-messages::-webkit-scrollbar,
1051.document-content::-webkit-scrollbar,
1052.cm-scroller::-webkit-scrollbar {
1053 width: 8px;
1054 height: 8px;
1055}
1056
1057.chat-messages::-webkit-scrollbar-track,
1058.document-content::-webkit-scrollbar-track,
1059.cm-scroller::-webkit-scrollbar-track {
1060 background: var(--bg-primary);
1061}
1062
1063.chat-messages::-webkit-scrollbar-thumb,
1064.document-content::-webkit-scrollbar-thumb,
1065.cm-scroller::-webkit-scrollbar-thumb {
1066 background: var(--bg-tertiary);
1067 border-radius: 4px;
1068 border: 2px solid var(--bg-primary);
1069}
1070
1071.chat-messages::-webkit-scrollbar-thumb:hover,
1072.document-content::-webkit-scrollbar-thumb:hover,
1073.cm-scroller::-webkit-scrollbar-thumb:hover {
1074 background: var(--border);
1075}