30a42036aa
- Implemented a scroll-to-top button in the viewer UI for better navigation. - Added styles for the scroll-to-top button in viewer.html and viewer-template.html. - Created a new ScrollToTop component to manage visibility and scrolling behavior. - Updated Feed component to include the ScrollToTop component. - Enhanced pagination logic in usePagination hook to prevent stale closures and improve performance. - Modified SDKAgent to include additional observation fields for better data handling.
120 lines
4.3 KiB
TypeScript
120 lines
4.3 KiB
TypeScript
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
import { Header } from './components/Header';
|
|
import { Feed } from './components/Feed';
|
|
import { Sidebar } from './components/Sidebar';
|
|
import { useSSE } from './hooks/useSSE';
|
|
import { useSettings } from './hooks/useSettings';
|
|
import { useStats } from './hooks/useStats';
|
|
import { usePagination } from './hooks/usePagination';
|
|
import { useTheme } from './hooks/useTheme';
|
|
import { Observation, Summary, UserPrompt } from './types';
|
|
import { mergeAndDeduplicateByProject } from './utils/data';
|
|
|
|
export function App() {
|
|
const [currentFilter, setCurrentFilter] = useState('');
|
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
const [paginatedObservations, setPaginatedObservations] = useState<Observation[]>([]);
|
|
const [paginatedSummaries, setPaginatedSummaries] = useState<Summary[]>([]);
|
|
const [paginatedPrompts, setPaginatedPrompts] = useState<UserPrompt[]>([]);
|
|
|
|
const { observations, summaries, prompts, projects, isProcessing, isConnected } = useSSE();
|
|
const { settings, saveSettings, isSaving, saveStatus } = useSettings();
|
|
const { stats } = useStats();
|
|
const { preference, resolvedTheme, setThemePreference } = useTheme();
|
|
const pagination = usePagination(currentFilter);
|
|
|
|
// Reset paginated data when filter changes
|
|
useEffect(() => {
|
|
setPaginatedObservations([]);
|
|
setPaginatedSummaries([]);
|
|
setPaginatedPrompts([]);
|
|
}, [currentFilter]);
|
|
|
|
// Merge real-time data with paginated data, removing duplicates and filtering by project
|
|
const allObservations = useMemo(
|
|
() => mergeAndDeduplicateByProject(observations, paginatedObservations, currentFilter),
|
|
[observations, paginatedObservations, currentFilter]
|
|
);
|
|
|
|
const allSummaries = useMemo(
|
|
() => mergeAndDeduplicateByProject(summaries, paginatedSummaries, currentFilter),
|
|
[summaries, paginatedSummaries, currentFilter]
|
|
);
|
|
|
|
const allPrompts = useMemo(
|
|
() => mergeAndDeduplicateByProject(prompts, paginatedPrompts, currentFilter),
|
|
[prompts, paginatedPrompts, currentFilter]
|
|
);
|
|
|
|
// Toggle sidebar
|
|
const toggleSidebar = useCallback(() => {
|
|
setSidebarOpen(prev => !prev);
|
|
}, []);
|
|
|
|
// Handle loading more data
|
|
const handleLoadMore = useCallback(async () => {
|
|
try {
|
|
const [newObservations, newSummaries, newPrompts] = await Promise.all([
|
|
pagination.observations.loadMore(),
|
|
pagination.summaries.loadMore(),
|
|
pagination.prompts.loadMore()
|
|
]);
|
|
|
|
if (newObservations.length > 0) {
|
|
setPaginatedObservations(prev => [...prev, ...newObservations]);
|
|
}
|
|
if (newSummaries.length > 0) {
|
|
setPaginatedSummaries(prev => [...prev, ...newSummaries]);
|
|
}
|
|
if (newPrompts.length > 0) {
|
|
setPaginatedPrompts(prev => [...prev, ...newPrompts]);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load more data:', error);
|
|
}
|
|
}, [pagination.observations, pagination.summaries, pagination.prompts]);
|
|
|
|
// Load first page only when filter changes
|
|
useEffect(() => {
|
|
handleLoadMore();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [currentFilter]); // Only re-run when filter changes, not when handleLoadMore changes
|
|
|
|
return (
|
|
<div className="container">
|
|
<div className="main-col">
|
|
<Header
|
|
isConnected={isConnected}
|
|
projects={projects}
|
|
currentFilter={currentFilter}
|
|
onFilterChange={setCurrentFilter}
|
|
onSettingsToggle={toggleSidebar}
|
|
sidebarOpen={sidebarOpen}
|
|
isProcessing={isProcessing}
|
|
themePreference={preference}
|
|
onThemeChange={setThemePreference}
|
|
/>
|
|
<Feed
|
|
observations={allObservations}
|
|
summaries={allSummaries}
|
|
prompts={allPrompts}
|
|
onLoadMore={handleLoadMore}
|
|
isLoading={pagination.observations.isLoading || pagination.summaries.isLoading || pagination.prompts.isLoading}
|
|
hasMore={pagination.observations.hasMore || pagination.summaries.hasMore || pagination.prompts.hasMore}
|
|
/>
|
|
</div>
|
|
|
|
<Sidebar
|
|
isOpen={sidebarOpen}
|
|
settings={settings}
|
|
stats={stats}
|
|
isSaving={isSaving}
|
|
saveStatus={saveStatus}
|
|
isConnected={isConnected}
|
|
onSave={saveSettings}
|
|
onClose={toggleSidebar}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|