Skip to content

Commit 263a5eb

Browse files
cursoragentmsukkari
andcommitted
fix: simplify Ask view layout - reduce minSize to allow narrower panels
Remove vertical layout system and toggle button. Instead, simply reduce the minSize from 30% to 20% for both panels, allowing each to shrink more in narrow/split windows without breaking the layout. Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com>
1 parent ddbcf1a commit 263a5eb

File tree

1 file changed

+92
-215
lines changed

1 file changed

+92
-215
lines changed

packages/web/src/features/chat/components/chatThread/chatThreadListItem.tsx

Lines changed: 92 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
import { AnimatedResizableHandle } from '@/components/ui/animatedResizableHandle';
44
import { ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
55
import { Skeleton } from '@/components/ui/skeleton';
6-
import { Button } from '@/components/ui/button';
7-
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
8-
import { CheckCircle, Loader2, PanelLeftClose, PanelLeft } from 'lucide-react';
6+
import { CheckCircle, Loader2 } from 'lucide-react';
97
import { CSSProperties, forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
108
import scrollIntoView from 'scroll-into-view-if-needed';
119
import { Reference, referenceSchema, SBChatMessage, Source } from "../../types";
@@ -18,9 +16,6 @@ import { ReferencedSourcesListView } from './referencedSourcesListView';
1816
import isEqual from "fast-deep-equal/react";
1917
import { ANSWER_TAG } from '../../constants';
2018

21-
// Minimum width in pixels for the horizontal layout to work well
22-
const MIN_HORIZONTAL_LAYOUT_WIDTH = 768;
23-
2419
interface ChatThreadListItemProps {
2520
userMessage: SBChatMessage;
2621
assistantMessage?: SBChatMessage;
@@ -38,7 +33,6 @@ const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThreadListIte
3833
chatId,
3934
index,
4035
}, ref) => {
41-
const containerRef = useRef<HTMLDivElement>(null);
4236
const leftPanelRef = useRef<HTMLDivElement>(null);
4337
const [leftPanelHeight, setLeftPanelHeight] = useState<number | null>(null);
4438
const answerRef = useRef<HTMLDivElement>(null);
@@ -49,32 +43,6 @@ const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThreadListIte
4943
const hasAutoCollapsed = useRef(false);
5044
const userHasManuallyExpanded = useRef(false);
5145

52-
// Layout state: track container width and user preference
53-
const [containerWidth, setContainerWidth] = useState<number | null>(null);
54-
const [isReferencePanelCollapsed, setIsReferencePanelCollapsed] = useState(false);
55-
56-
// Determine if we should use vertical layout based on container width
57-
const shouldUseVerticalLayout = containerWidth !== null && containerWidth < MIN_HORIZONTAL_LAYOUT_WIDTH;
58-
59-
// Monitor container width for responsive layout
60-
useEffect(() => {
61-
if (!containerRef.current) {
62-
return;
63-
}
64-
65-
const resizeObserver = new ResizeObserver((entries) => {
66-
for (const entry of entries) {
67-
setContainerWidth(entry.contentRect.width);
68-
}
69-
});
70-
71-
resizeObserver.observe(containerRef.current);
72-
73-
return () => {
74-
resizeObserver.disconnect();
75-
};
76-
}, []);
77-
7846
const userQuestion = useMemo(() => {
7947
return userMessage.parts.length > 0 && userMessage.parts[0].type === 'text' ? userMessage.parts[0].text : '';
8048
}, [userMessage]);
@@ -337,162 +305,10 @@ const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThreadListIte
337305
}, [references, sources]);
338306

339307

340-
// Content for the answer/question panel
341-
const answerPanelContent = (
342-
<div
343-
ref={leftPanelRef}
344-
className="py-4 h-full"
345-
>
346-
<div className="flex flex-row gap-2 mb-4">
347-
{isStreaming ? (
348-
<Loader2 className="w-4 h-4 animate-spin flex-shrink-0 mt-1.5" />
349-
) : (
350-
<CheckCircle className="w-4 h-4 text-green-700 flex-shrink-0 mt-1.5" />
351-
)}
352-
<MarkdownRenderer
353-
content={userQuestion.trim()}
354-
className="prose-p:m-0"
355-
escapeHtml={true}
356-
/>
357-
</div>
358-
359-
{isThinking && (
360-
<div className="space-y-4 mb-4">
361-
<Skeleton className="h-4 max-w-32" />
362-
<div className="space-y-2">
363-
<Skeleton className="h-3 max-w-72" />
364-
<Skeleton className="h-3 max-w-64" />
365-
<Skeleton className="h-3 max-w-56" />
366-
</div>
367-
</div>
368-
)}
369-
370-
<DetailsCard
371-
chatId={chatId}
372-
isExpanded={isDetailsPanelExpanded}
373-
onExpandedChanged={onExpandDetailsPanel}
374-
isThinking={isThinking}
375-
isStreaming={isStreaming}
376-
thinkingSteps={uiVisibleThinkingSteps}
377-
metadata={assistantMessage?.metadata}
378-
/>
379-
380-
{(answerPart && assistantMessage) ? (
381-
<AnswerCard
382-
ref={answerRef}
383-
answerText={answerPart.text}
384-
chatId={chatId}
385-
messageId={assistantMessage.id}
386-
traceId={assistantMessage.metadata?.traceId}
387-
/>
388-
) : !isStreaming && (
389-
<p className="text-destructive">Error: No answer response was provided</p>
390-
)}
391-
</div>
392-
);
393-
394-
// Content for the references panel
395-
const referencesPanelContent = (
396-
<>
397-
{referencedFileSources.length > 0 ? (
398-
<ReferencedSourcesListView
399-
index={index}
400-
references={references}
401-
sources={referencedFileSources}
402-
hoveredReference={hoveredReference}
403-
selectedReference={selectedReference}
404-
onSelectedReferenceChanged={setSelectedReference}
405-
onHoveredReferenceChanged={setHoveredReference}
406-
style={shouldUseVerticalLayout || isReferencePanelCollapsed ? {} : rightPanelStyle}
407-
/>
408-
) : isStreaming ? (
409-
<div className="space-y-4">
410-
{Array.from({ length: 3 }).map((_, index) => (
411-
<Skeleton key={index} className="w-full h-48" />
412-
))}
413-
</div>
414-
) : (
415-
<div className="p-4 text-center text-muted-foreground text-sm">
416-
No file references found
417-
</div>
418-
)}
419-
</>
420-
);
421-
422-
// Layout toggle button for narrow containers
423-
const layoutToggleButton = referencedFileSources.length > 0 && (
424-
<Tooltip>
425-
<TooltipTrigger asChild>
426-
<Button
427-
variant="ghost"
428-
size="sm"
429-
className="h-7 w-7 p-0"
430-
onClick={() => setIsReferencePanelCollapsed(!isReferencePanelCollapsed)}
431-
>
432-
{isReferencePanelCollapsed ? (
433-
<PanelLeft className="h-4 w-4" />
434-
) : (
435-
<PanelLeftClose className="h-4 w-4" />
436-
)}
437-
</Button>
438-
</TooltipTrigger>
439-
<TooltipContent side="bottom">
440-
{isReferencePanelCollapsed ? 'Show code references' : 'Hide code references'}
441-
</TooltipContent>
442-
</Tooltip>
443-
);
444-
445-
// Vertical layout for narrow containers
446-
if (shouldUseVerticalLayout) {
447-
return (
448-
<div
449-
className="flex flex-col relative"
450-
ref={(node) => {
451-
// Handle both refs
452-
containerRef.current = node;
453-
if (typeof ref === 'function') {
454-
ref(node);
455-
} else if (ref) {
456-
ref.current = node;
457-
}
458-
}}
459-
>
460-
{/* Answer panel with toggle button */}
461-
<div className="relative">
462-
{referencedFileSources.length > 0 && (
463-
<div className="absolute top-4 right-0 z-10">
464-
{layoutToggleButton}
465-
</div>
466-
)}
467-
{answerPanelContent}
468-
</div>
469-
470-
{/* References panel - collapsible in vertical layout */}
471-
{!isReferencePanelCollapsed && referencedFileSources.length > 0 && (
472-
<div className="border-t pt-4 mt-4">
473-
<div className="text-sm font-medium text-muted-foreground mb-3">Code References</div>
474-
<div className="max-h-[50vh] overflow-y-auto">
475-
{referencesPanelContent}
476-
</div>
477-
</div>
478-
)}
479-
</div>
480-
);
481-
}
482-
483-
// Horizontal layout for wider containers
484308
return (
485309
<div
486-
className="flex flex-col relative min-h-[calc(100vh-250px)]"
487-
ref={(node) => {
488-
// Handle both refs
489-
containerRef.current = node;
490-
if (typeof ref === 'function') {
491-
ref(node);
492-
} else if (ref) {
493-
ref.current = node;
494-
}
495-
}}
310+
className="flex flex-col md:flex-row relative min-h-[calc(100vh-250px)]"
311+
ref={ref}
496312
>
497313
<ResizablePanelGroup
498314
direction="horizontal"
@@ -503,42 +319,103 @@ const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThreadListIte
503319
>
504320
<ResizablePanel
505321
order={1}
506-
minSize={isReferencePanelCollapsed ? 100 : 30}
507-
maxSize={isReferencePanelCollapsed ? 100 : 70}
508-
defaultSize={isReferencePanelCollapsed ? 100 : 50}
322+
minSize={20}
323+
maxSize={80}
324+
defaultSize={50}
509325
style={{
510326
overflow: 'visible',
511327
}}
512328
>
513-
<div className="relative">
514-
{referencedFileSources.length > 0 && (
515-
<div className="absolute top-4 right-0 z-10">
516-
{layoutToggleButton}
329+
<div
330+
ref={leftPanelRef}
331+
className="py-4 h-full"
332+
>
333+
<div className="flex flex-row gap-2 mb-4">
334+
{isStreaming ? (
335+
<Loader2 className="w-4 h-4 animate-spin flex-shrink-0 mt-1.5" />
336+
) : (
337+
<CheckCircle className="w-4 h-4 text-green-700 flex-shrink-0 mt-1.5" />
338+
)}
339+
<MarkdownRenderer
340+
content={userQuestion.trim()}
341+
className="prose-p:m-0"
342+
escapeHtml={true}
343+
/>
344+
</div>
345+
346+
{isThinking && (
347+
<div className="space-y-4 mb-4">
348+
<Skeleton className="h-4 max-w-32" />
349+
<div className="space-y-2">
350+
<Skeleton className="h-3 max-w-72" />
351+
<Skeleton className="h-3 max-w-64" />
352+
<Skeleton className="h-3 max-w-56" />
353+
</div>
517354
</div>
518355
)}
519-
{answerPanelContent}
356+
357+
<DetailsCard
358+
chatId={chatId}
359+
isExpanded={isDetailsPanelExpanded}
360+
onExpandedChanged={onExpandDetailsPanel}
361+
isThinking={isThinking}
362+
isStreaming={isStreaming}
363+
thinkingSteps={uiVisibleThinkingSteps}
364+
metadata={assistantMessage?.metadata}
365+
/>
366+
367+
{(answerPart && assistantMessage) ? (
368+
<AnswerCard
369+
ref={answerRef}
370+
answerText={answerPart.text}
371+
chatId={chatId}
372+
messageId={assistantMessage.id}
373+
traceId={assistantMessage.metadata?.traceId}
374+
/>
375+
) : !isStreaming && (
376+
<p className="text-destructive">Error: No answer response was provided</p>
377+
)}
520378
</div>
521379
</ResizablePanel>
522-
{!isReferencePanelCollapsed && (
523-
<>
524-
<AnimatedResizableHandle className='mx-4' />
525-
<ResizablePanel
526-
order={2}
527-
minSize={30}
528-
maxSize={70}
529-
defaultSize={50}
530-
style={{
531-
overflow: 'clip',
532-
maxHeight: '100%',
533-
minWidth: 0,
534-
}}
535-
>
536-
<div className="sticky top-0">
537-
{referencesPanelContent}
380+
<AnimatedResizableHandle className='mx-4' />
381+
<ResizablePanel
382+
order={2}
383+
minSize={20}
384+
maxSize={80}
385+
defaultSize={50}
386+
style={{
387+
overflow: 'clip',
388+
maxHeight: '100%',
389+
minWidth: 0,
390+
}}
391+
>
392+
<div
393+
className="sticky top-0"
394+
>
395+
{referencedFileSources.length > 0 ? (
396+
<ReferencedSourcesListView
397+
index={index}
398+
references={references}
399+
sources={referencedFileSources}
400+
hoveredReference={hoveredReference}
401+
selectedReference={selectedReference}
402+
onSelectedReferenceChanged={setSelectedReference}
403+
onHoveredReferenceChanged={setHoveredReference}
404+
style={rightPanelStyle}
405+
/>
406+
) : isStreaming ? (
407+
<div className="space-y-4">
408+
{Array.from({ length: 3 }).map((_, index) => (
409+
<Skeleton key={index} className="w-full h-48" />
410+
))}
538411
</div>
539-
</ResizablePanel>
540-
</>
541-
)}
412+
) : (
413+
<div className="p-4 text-center text-muted-foreground text-sm">
414+
No file references found
415+
</div>
416+
)}
417+
</div>
418+
</ResizablePanel>
542419
</ResizablePanelGroup>
543420
</div>
544421
)
@@ -588,4 +465,4 @@ const getNearestReferenceElement = (referenceElements: Element[]) => {
588465

589466
return currentDistance < nearestDistance ? current : nearest;
590467
});
591-
}
468+
}

0 commit comments

Comments
 (0)