Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ pub async fn run() {
.level_for("portable_pty", log::LevelFilter::Info)
.level_for("russh", log::LevelFilter::Info)
.targets(log_targets)
.rotation_strategy(RotationStrategy::KeepSome(30))
.rotation_strategy(RotationStrategy::KeepSome(3))
.max_file_size(10 * 1024 * 1024)
.timezone_strategy(TimezoneStrategy::UseLocal)
.clear_format()
Expand Down
2 changes: 1 addition & 1 deletion src/apps/desktop/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::thread;
use tauri_plugin_log::{fern, Target, TargetKind};

const SESSION_DIR_PATTERN: &str = r"^\d{8}T\d{6}$";
const MAX_LOG_SESSIONS: usize = 50;
const MAX_LOG_SESSIONS: usize = 10;
static SESSION_LOG_DIR: OnceLock<PathBuf> = OnceLock::new();
// Default to Debug in early development for easier diagnostics
static CURRENT_LOG_LEVEL: AtomicU8 = AtomicU8::new(level_filter_to_u8(log::LevelFilter::Debug));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Renders merged explore-only rounds as a collapsible region.
*/

import React, { useRef, useMemo, useCallback, useEffect } from 'react';
import React, { useRef, useMemo, useCallback, useEffect, useState } from 'react';
import { ChevronRight } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import type { FlowItem, FlowToolItem, FlowTextItem, FlowThinkingItem } from '../../types/flow-chat';
Expand All @@ -26,6 +26,7 @@ export const ExploreGroupRenderer: React.FC<ExploreGroupRendererProps> = ({
}) => {
const { t } = useTranslation('flow-chat');
const containerRef = useRef<HTMLDivElement>(null);
const [scrollState, setScrollState] = useState({ hasScroll: false, atTop: true, atBottom: true });

const {
exploreGroupStates,
Expand Down Expand Up @@ -61,6 +62,19 @@ export const ExploreGroupRenderer: React.FC<ExploreGroupRendererProps> = ({
const isCollapsed = !isExpanded;
const allowManualToggle = !isGroupStreaming;

const checkScrollState = useCallback(() => {
const el = containerRef.current;
if (!el) {
return;
}

setScrollState({
hasScroll: el.scrollHeight > el.clientHeight + 1,
atTop: el.scrollTop <= 5,
atBottom: el.scrollTop + el.clientHeight >= el.scrollHeight - 5,
});
}, []);

useEffect(() => {
if (isGroupStreaming && !hasExplicitState) {
applyExpandedState(false, true, () => {
Expand Down Expand Up @@ -95,10 +109,39 @@ export const ExploreGroupRenderer: React.FC<ExploreGroupRendererProps> = ({
requestAnimationFrame(() => {
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
checkScrollState();
}
});
}
}, [allItems, isCollapsed, isGroupStreaming]);
}, [allItems, checkScrollState, isCollapsed, isGroupStreaming]);

useEffect(() => {
if (!isExpanded) {
setScrollState({ hasScroll: false, atTop: true, atBottom: true });
return;
}

const el = containerRef.current;
if (!el) {
return;
}

const frameId = requestAnimationFrame(checkScrollState);

if (typeof ResizeObserver === 'undefined') {
return () => cancelAnimationFrame(frameId);
}

const observer = new ResizeObserver(() => {
checkScrollState();
});
observer.observe(el);

return () => {
cancelAnimationFrame(frameId);
observer.disconnect();
};
}, [allItems, checkScrollState, isExpanded]);

// Build summary text with i18n.
const displaySummary = useMemo(() => {
Expand Down Expand Up @@ -141,6 +184,9 @@ export const ExploreGroupRenderer: React.FC<ExploreGroupRendererProps> = ({
allowManualToggle ? 'explore-region--collapsible' : null,
isCollapsed ? 'explore-region--collapsed' : 'explore-region--expanded',
isGroupStreaming ? 'explore-region--streaming' : null,
scrollState.hasScroll ? 'explore-region--has-scroll' : null,
scrollState.atTop ? 'explore-region--at-top' : null,
scrollState.atBottom ? 'explore-region--at-bottom' : null,
].filter(Boolean).join(' ');
return (
<div
Expand All @@ -156,7 +202,7 @@ export const ExploreGroupRenderer: React.FC<ExploreGroupRendererProps> = ({
)}
<div className="explore-region__content-wrapper">
<div className="explore-region__content-inner">
<div ref={containerRef} className="explore-region__content">
<div ref={containerRef} className="explore-region__content" onScroll={checkScrollState}>
{allItems.map((item, idx) => (
<ExploreItemRenderer
key={item.id}
Expand Down
26 changes: 18 additions & 8 deletions src/web-ui/src/flow_chat/components/modern/ExploreRegion.scss
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,18 @@

// ==================== Streaming adjustments ====================

// Top/bottom gradient masks during streaming.
// Top gradient mask during streaming.
.explore-region--expanded.explore-region--streaming {
// Top mask
&::before {
content: none;
}

&::after {
content: none;
}
}

.explore-region--expanded.explore-region--streaming.explore-region--has-scroll:not(.explore-region--at-top) {
&::before {
content: '';
position: absolute;
Expand All @@ -158,7 +167,13 @@
);
}

// Bottom mask
// Non-collapsible has no header, start mask from the very top.
&:not(.explore-region--collapsible)::before {
top: 0;
}
}

.explore-region--expanded.explore-region--streaming.explore-region--has-scroll:not(.explore-region--at-bottom) {
&::after {
content: '';
position: absolute;
Expand All @@ -175,11 +190,6 @@
transparent 100%
);
}

// Non-collapsible has no header, start mask from the very top.
&:not(.explore-region--collapsible)::before {
top: 0;
}
}

// Limit height and enable scroll during streaming.
Expand Down
Loading