11import React , { useState , useCallback , useEffect , useRef } from "react" ;
2- import styled from "@emotion/styled " ;
2+ import { cn } from "@/lib/utils " ;
33import { MessageRenderer } from "./Messages/MessageRenderer" ;
44import { InterruptedBarrier } from "./Messages/ChatBarrier/InterruptedBarrier" ;
55import { StreamingBarrier } from "./Messages/ChatBarrier/StreamingBarrier" ;
@@ -30,189 +30,6 @@ import { TooltipWrapper, Tooltip } from "./Tooltip";
3030import type { DisplayedMessage } from "@/types/message" ;
3131import { useAIViewKeybinds } from "@/hooks/useAIViewKeybinds" ;
3232
33- const ViewContainer = styled . div `
34- flex: 1;
35- display: flex;
36- flex-direction: row;
37- background: #1e1e1e;
38- color: #d4d4d4;
39- font-family: var(--font-monospace);
40- font-size: 12px;
41- overflow-x: auto;
42- overflow-y: hidden;
43- container-type: inline-size;
44-
45- /* Mobile: Stack vertically */
46- @media (max-width: 768px) {
47- flex-direction: column;
48- }
49- ` ;
50-
51- const ChatArea = styled . div `
52- flex: 1;
53- min-width: 400px; /* Reduced from 750px to allow narrower layout when Review panel is wide */
54- display: flex;
55- flex-direction: column;
56-
57- /* Mobile: Remove min-width and take full width */
58- @media (max-width: 768px) {
59- min-width: 0;
60- width: 100%;
61- max-height: 100%;
62- }
63- ` ;
64-
65- const ViewHeader = styled . div `
66- padding: 4px 15px;
67- background: #252526;
68- border-bottom: 1px solid #3e3e42;
69- display: flex;
70- justify-content: space-between;
71- align-items: center;
72-
73- /* Mobile: Add padding for hamburger button and adjust spacing */
74- @media (max-width: 768px) {
75- padding: 8px 15px 8px 60px; /* Extra left padding for hamburger button */
76- flex-wrap: wrap;
77- gap: 8px;
78- }
79- ` ;
80-
81- const WorkspaceTitle = styled . div `
82- font-weight: 600;
83- color: #cccccc;
84- display: flex;
85- align-items: center;
86- gap: 8px;
87- min-width: 0; /* Allow flex children to shrink */
88- overflow: hidden;
89- ` ;
90-
91- const WorkspacePath = styled . span `
92- font-family: var(--font-monospace);
93- color: #888;
94- font-weight: 400;
95- font-size: 11px;
96- white-space: nowrap;
97- overflow: hidden;
98- text-overflow: ellipsis;
99- min-width: 0;
100- ` ;
101-
102- const WorkspaceName = styled . span `
103- white-space: nowrap;
104- overflow: hidden;
105- text-overflow: ellipsis;
106- min-width: 0;
107- ` ;
108-
109- const TerminalIconButton = styled . button `
110- background: transparent;
111- border: none;
112- cursor: pointer;
113- padding: 4px;
114- display: flex;
115- align-items: center;
116- justify-content: center;
117- color: #888;
118- transition: color 0.2s;
119-
120- &:hover {
121- color: #ccc;
122- }
123-
124- svg {
125- width: 16px;
126- height: 16px;
127- }
128- ` ;
129-
130- const OutputContainer = styled . div `
131- flex: 1;
132- position: relative;
133- overflow: hidden;
134- ` ;
135-
136- const OutputContent = styled . div `
137- height: 100%;
138- overflow-y: auto;
139- padding: 15px;
140- white-space: pre-wrap;
141- word-break: break-word;
142- line-height: 1.5;
143- ` ;
144-
145- const EmptyState = styled . div `
146- flex: 1;
147- display: flex;
148- flex-direction: column;
149- align-items: center;
150- justify-content: center;
151- height: 100%;
152- color: #6b6b6b;
153- text-align: center;
154-
155- h3 {
156- margin: 0 0 10px 0;
157- font-size: 16px;
158- font-weight: 500;
159- }
160-
161- p {
162- margin: 0;
163- font-size: 13px;
164- }
165- ` ;
166-
167- const EditBarrier = styled . div `
168- margin: 20px 0;
169- padding: 12px 15px;
170- background: var(--color-editing-mode-alpha);
171- border-bottom: 3px solid;
172- border-image: repeating-linear-gradient(
173- 45deg,
174- var(--color-editing-mode),
175- var(--color-editing-mode) 10px,
176- transparent 10px,
177- transparent 20px
178- )
179- 1;
180- color: var(--color-editing-mode);
181- font-size: 12px;
182- font-weight: 500;
183- text-align: center;
184- ` ;
185-
186- const JumpToBottomIndicator = styled . button `
187- position: absolute;
188- bottom: 8px;
189- left: 50%;
190- transform: translateX(-50%);
191- padding: 4px 8px;
192- background: hsl(from var(--color-assistant-border) h s l / 0.1);
193- color: white;
194- border: 1px solid hsl(from var(--color-assistant-border) h s l / 0.4);
195- border-radius: 20px;
196- font-size: 12px;
197- font-weight: 500;
198- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
199- cursor: pointer;
200- transition: all 0.2s ease;
201- z-index: 100;
202- font-family: var(--font-primary);
203- backdrop-filter: blur(1px);
204-
205- &:hover {
206- background: hsl(from var(--color-assistant-border) h s l / 0.4);
207- border-color: hsl(from var(--color-assistant-border) h s l / 0.6);
208- transform: translateX(-50%) scale(1.05);
209- }
210-
211- &:active {
212- transform: translateX(-50%) scale(0.95);
213- }
214- ` ;
215-
21633interface AIViewProps {
21734 workspaceId : string ;
21835 projectName : string ;
@@ -426,11 +243,11 @@ const AIViewInner: React.FC<AIViewProps> = ({
426243 // Return early if workspace state not loaded yet
427244 if ( ! workspaceState ) {
428245 return (
429- < ViewContainer className = { className } >
430- < EmptyState >
431- < h3 > Loading workspace...</ h3 >
432- </ EmptyState >
433- </ ViewContainer >
246+ < div className = { cn ( "flex flex-1 flex-row bg-[#1e1e1e] text-[#d4d4d4] font-mono text-xs overflow-x-auto overflow-y-hidden [@media(max-width:768px)]:flex-col" , className ) } style = { { containerType : "inline-size" } } >
247+ < div className = "flex-1 flex flex-col items-center justify-center h-full text-[#6b6b6b] text-center" >
248+ < h3 className = "m-0 mb-2.5 text-base font-medium" > Loading workspace...</ h3 >
249+ </ div >
250+ </ div >
434251 ) ;
435252 }
436253
@@ -461,30 +278,30 @@ const AIViewInner: React.FC<AIViewProps> = ({
461278
462279 if ( loading ) {
463280 return (
464- < ViewContainer className = { className } >
465- < EmptyState >
466- < h3 > Loading workspace...</ h3 >
467- </ EmptyState >
468- </ ViewContainer >
281+ < div className = { cn ( "flex flex-1 flex-row bg-[#1e1e1e] text-[#d4d4d4] font-mono text-xs overflow-x-auto overflow-y-hidden [@media(max-width:768px)]:flex-col" , className ) } style = { { containerType : "inline-size" } } >
282+ < div className = "flex-1 flex flex-col items-center justify-center h-full text-[#6b6b6b] text-center" >
283+ < h3 className = "m-0 mb-2.5 text-base font-medium" > Loading workspace...</ h3 >
284+ </ div >
285+ </ div >
469286 ) ;
470287 }
471288
472289 if ( ! projectName || ! branch ) {
473290 return (
474- < ViewContainer className = { className } >
475- < EmptyState >
476- < h3 > No Workspace Selected</ h3 >
477- < p > Select a workspace from the sidebar to view and interact with Claude</ p >
478- </ EmptyState >
479- </ ViewContainer >
291+ < div className = { cn ( "flex flex-1 flex-row bg-[#1e1e1e] text-[#d4d4d4] font-mono text-xs overflow-x-auto overflow-y-hidden [@media(max-width:768px)]:flex-col" , className ) } style = { { containerType : "inline-size" } } >
292+ < div className = "flex-1 flex flex-col items-center justify-center h-full text-[#6b6b6b] text-center" >
293+ < h3 className = "m-0 mb-2.5 text-base font-medium" > No Workspace Selected</ h3 >
294+ < p className = "m-0 text-[13px]" > Select a workspace from the sidebar to view and interact with Claude</ p >
295+ </ div >
296+ </ div >
480297 ) ;
481298 }
482299
483300 return (
484- < ViewContainer className = { className } >
485- < ChatArea ref = { chatAreaRef } >
486- < ViewHeader >
487- < WorkspaceTitle >
301+ < div className = { cn ( "flex flex-1 flex-row bg-[#1e1e1e] text-[#d4d4d4] font-mono text-xs overflow-x-auto overflow-y-hidden [@media(max-width:768px)]:flex-col" , className ) } style = { { containerType : "inline-size" } } >
302+ < div ref = { chatAreaRef } className = "flex-1 min-w-[400px] flex flex-col [@media(max-width:768px)]:min-w-0 [@media(max-width:768px)]:w-full [@media(max-width:768px)]:max-h-full" >
303+ < div className = "py-1 px-[15px] bg-[#252526] border-b border-[#3e3e42] flex justify-between items-center [@media(max-width:768px)]:py-2 [@media(max-width:768px)]:pl-[60px] [@media(max-width:768px)]:flex-wrap [@media(max-width:768px)]:gap-2" >
304+ < div className = "font-semibold text-[#ccc] flex items-center gap-2 min-w-0 overflow-hidden" >
488305 < StatusIndicator
489306 streaming = { canInterrupt }
490307 title = {
@@ -496,25 +313,28 @@ const AIViewInner: React.FC<AIViewProps> = ({
496313 workspaceId = { workspaceId }
497314 tooltipPosition = "bottom"
498315 />
499- < WorkspaceName >
316+ < span className = "whitespace-nowrap overflow-hidden text-ellipsis min-w-0" >
500317 { projectName } / { branch }
501- </ WorkspaceName >
502- < WorkspacePath > { namedWorkspacePath } </ WorkspacePath >
318+ </ span >
319+ < span className = "font-mono text-[#888] font-normal text-[11px] whitespace-nowrap overflow-hidden text-ellipsis min-w-0" > { namedWorkspacePath } </ span >
503320 < TooltipWrapper inline >
504- < TerminalIconButton onClick = { handleOpenTerminal } >
321+ < button
322+ onClick = { handleOpenTerminal }
323+ className = "bg-transparent border-none cursor-pointer p-1 flex items-center justify-center text-[#888] transition-colors hover:text-[#ccc] [&_svg]:w-4 [&_svg]:h-4"
324+ >
505325 < svg viewBox = "0 0 16 16" fill = "currentColor" >
506326 < path d = "M0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0114.25 15H1.75A1.75 1.75 0 010 13.25V2.75zm1.75-.25a.25.25 0 00-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25V2.75a.25.25 0 00-.25-.25H1.75zM7.25 8a.75.75 0 01-.22.53l-2.25 2.25a.75.75 0 01-1.06-1.06L5.44 8 3.72 6.28a.75.75 0 111.06-1.06l2.25 2.25c.141.14.22.331.22.53zm1.5 1.5a.75.75 0 000 1.5h3a.75.75 0 000-1.5h-3z" />
507327 </ svg >
508- </ TerminalIconButton >
328+ </ button >
509329 < Tooltip className = "tooltip" position = "bottom" align = "center" >
510330 Open in terminal ({ formatKeybind ( KEYBINDS . OPEN_TERMINAL ) } )
511331 </ Tooltip >
512332 </ TooltipWrapper >
513- </ WorkspaceTitle >
514- </ ViewHeader >
333+ </ div >
334+ </ div >
515335
516- < OutputContainer >
517- < OutputContent
336+ < div className = "flex-1 relative overflow-hidden" >
337+ < div
518338 ref = { contentRef }
519339 onWheel = { markUserInteraction }
520340 onTouchMove = { markUserInteraction }
@@ -524,12 +344,13 @@ const AIViewInner: React.FC<AIViewProps> = ({
524344 aria-busy = { canInterrupt }
525345 aria-label = "Conversation transcript"
526346 tabIndex = { 0 }
347+ className = "h-full overflow-y-auto p-[15px] whitespace-pre-wrap break-words leading-[1.5]"
527348 >
528349 { mergedMessages . length === 0 ? (
529- < EmptyState >
350+ < div className = "flex-1 flex flex-col items-center justify-center h-full text-[#6b6b6b] text-center [&_h3]:m-0 [&_h3]:mb-2.5 [&_h3]:text-base [&_h3]:font-medium [&_p]:m-0 [&_p]:text-[13px]" >
530351 < h3 > No Messages Yet</ h3 >
531352 < p > Send a message below to begin</ p >
532- </ EmptyState >
353+ </ div >
533354 ) : (
534355 < >
535356 { mergedMessages . map ( ( msg ) => {
@@ -551,9 +372,15 @@ const AIViewInner: React.FC<AIViewProps> = ({
551372 />
552373 </ div >
553374 { isAtCutoff && (
554- < EditBarrier >
375+ < div
376+ className = "my-5 py-3 px-[15px] text-xs font-medium text-center text-edit-mode bg-edit-mode/10"
377+ style = { {
378+ borderBottom : "3px solid" ,
379+ borderImage : "repeating-linear-gradient(45deg, var(--color-editing-mode), var(--color-editing-mode) 10px, transparent 10px, transparent 20px) 1" ,
380+ } }
381+ >
555382 β οΈ Messages below this line will be removed when you submit the edit
556- </ EditBarrier >
383+ </ div >
557384 ) }
558385 { shouldShowInterruptedBarrier ( msg ) && < InterruptedBarrier /> }
559386 </ React . Fragment >
@@ -599,13 +426,31 @@ const AIViewInner: React.FC<AIViewProps> = ({
599426 }
600427 />
601428 ) }
602- </ OutputContent >
429+ </ div >
603430 { ! autoScroll && (
604- < JumpToBottomIndicator onClick = { jumpToBottom } type = "button" >
431+ < button
432+ onClick = { jumpToBottom }
433+ type = "button"
434+ className = "absolute bottom-2 left-1/2 -translate-x-1/2 py-1 px-2 text-white border rounded-[20px] text-xs font-medium shadow-[0_4px_12px_rgba(0,0,0,0.3)] cursor-pointer transition-all duration-200 z-[100] font-primary backdrop-blur-[1px] hover:scale-105 active:scale-95"
435+ style = { {
436+ background : "hsl(from var(--color-assistant-border) h s l / 0.1)" ,
437+ borderColor : "hsl(from var(--color-assistant-border) h s l / 0.4)" ,
438+ } }
439+ onMouseEnter = { ( e ) => {
440+ const target = e . currentTarget ;
441+ target . style . background = "hsl(from var(--color-assistant-border) h s l / 0.4)" ;
442+ target . style . borderColor = "hsl(from var(--color-assistant-border) h s l / 0.6)" ;
443+ } }
444+ onMouseLeave = { ( e ) => {
445+ const target = e . currentTarget ;
446+ target . style . background = "hsl(from var(--color-assistant-border) h s l / 0.1)" ;
447+ target . style . borderColor = "hsl(from var(--color-assistant-border) h s l / 0.4)" ;
448+ } }
449+ >
605450 Press { formatKeybind ( KEYBINDS . JUMP_TO_BOTTOM ) } to jump to bottom
606- </ JumpToBottomIndicator >
451+ </ button >
607452 ) }
608- </ OutputContainer >
453+ </ div >
609454
610455 < ChatInput
611456 workspaceId = { workspaceId }
@@ -620,7 +465,7 @@ const AIViewInner: React.FC<AIViewProps> = ({
620465 canInterrupt = { canInterrupt }
621466 onReady = { handleChatInputReady }
622467 />
623- </ ChatArea >
468+ </ div >
624469
625470 < RightSidebar
626471 key = { workspaceId }
@@ -633,7 +478,7 @@ const AIViewInner: React.FC<AIViewProps> = ({
633478 isResizing = { isResizing } // Pass resizing state
634479 onReviewNote = { handleReviewNote } // Pass review note handler to append to chat
635480 />
636- </ ViewContainer >
481+ </ div >
637482 ) ;
638483} ;
639484
0 commit comments