From f29658fee8c6a06df3ed3d9a67362e074ac571ac Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Fri, 14 Mar 2025 08:54:24 -0400 Subject: [PATCH 1/7] ui styling --- frontend/common/dist/styles/global.css | 137 ++++++----- .../common/src/components/SessionDrawer.tsx | 26 +- .../common/src/components/SessionSidebar.tsx | 4 +- .../common/src/components/TimelineFeed.tsx | 134 +++-------- .../common/src/components/TimelineStep.tsx | 39 ++- frontend/common/src/components/ui/sheet.tsx | 4 +- frontend/web/src/components/Layout.tsx | 99 ++++++++ frontend/web/src/index.tsx | 224 +++++++++--------- 8 files changed, 366 insertions(+), 301 deletions(-) create mode 100644 frontend/web/src/components/Layout.tsx diff --git a/frontend/common/dist/styles/global.css b/frontend/common/dist/styles/global.css index 13af638..7e46c9e 100644 --- a/frontend/common/dist/styles/global.css +++ b/frontend/common/dist/styles/global.css @@ -623,17 +623,24 @@ video { .top-4 { top: 1rem; } +.z-40 { + z-index: 40; +} .z-50 { z-index: 50; } +.mx-auto { + margin-left: auto; + margin-right: auto; +} .mb-2 { margin-bottom: 0.5rem; } -.mb-4 { - margin-bottom: 1rem; +.mb-5 { + margin-bottom: 1.25rem; } -.mr-2 { - margin-right: 0.5rem; +.mr-1 { + margin-right: 0.25rem; } .mr-3 { margin-right: 0.75rem; @@ -644,8 +651,8 @@ video { .mt-1\.5 { margin-top: 0.375rem; } -.mt-3 { - margin-top: 0.75rem; +.mt-4 { + margin-top: 1rem; } .mt-6 { margin-top: 1.5rem; @@ -659,9 +666,6 @@ video { .inline-flex { display: inline-flex; } -.hidden { - display: none; -} .h-10 { height: 2.5rem; } @@ -671,6 +675,9 @@ video { .h-3 { height: 0.75rem; } +.h-3\.5 { + height: 0.875rem; +} .h-4 { height: 1rem; } @@ -689,30 +696,27 @@ video { .h-full { height: 100%; } -.h-screen { - height: 100vh; -} .w-2\.5 { width: 0.625rem; } .w-3 { width: 0.75rem; } +.w-3\.5 { + width: 0.875rem; +} .w-3\/4 { width: 75%; } .w-4 { width: 1rem; } -.w-5 { - width: 1.25rem; +.w-8 { + width: 2rem; } .w-9 { width: 2.25rem; } -.w-\[250px\] { - width: 250px; -} .w-\[85\%\] { width: 85%; } @@ -722,8 +726,8 @@ video { .min-w-0 { min-width: 0px; } -.max-w-xs { - max-width: 20rem; +.max-w-md { + max-width: 28rem; } .flex-1 { flex: 1 1 0%; @@ -766,12 +770,14 @@ video { .justify-between { justify-content: space-between; } -.gap-2 { - gap: 0.5rem; -} .gap-4 { gap: 1rem; } +.space-x-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.75rem * var(--tw-space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); +} .space-y-1\.5 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse))); @@ -787,12 +793,17 @@ video { margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(1rem * var(--tw-space-y-reverse)); } +.space-y-5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.25rem * var(--tw-space-y-reverse)); +} +.overflow-auto { + overflow: auto; +} .overflow-hidden { overflow: hidden; } -.overflow-x-auto { - overflow-x: auto; -} .truncate { overflow: hidden; text-overflow: ellipsis; @@ -804,9 +815,6 @@ video { .whitespace-pre-wrap { white-space: pre-wrap; } -.rounded { - border-radius: 0.25rem; -} .rounded-\[inherit\] { border-radius: inherit; } @@ -840,9 +848,15 @@ video { .border-t { border-top-width: 1px; } +.border-dashed { + border-style: dashed; +} .border-border { border-color: hsl(var(--border)); } +.border-border\/50 { + border-color: hsl(var(--border) / 0.5); +} .border-input { border-color: hsl(var(--input)); } @@ -874,6 +888,9 @@ video { .bg-card { background-color: hsl(var(--card)); } +.bg-card\/50 { + background-color: hsl(var(--card) / 0.5); +} .bg-destructive { background-color: hsl(var(--destructive)); } @@ -908,6 +925,9 @@ video { .p-4 { padding: 1rem; } +.p-5 { + padding: 1.25rem; +} .p-6 { padding: 1.5rem; } @@ -930,21 +950,22 @@ video { padding-left: 2rem; padding-right: 2rem; } +.py-0\.5 { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} .py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; } +.py-12 { + padding-top: 3rem; + padding-bottom: 3rem; +} .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; } -.py-8 { - padding-top: 2rem; - padding-bottom: 2rem; -} -.pb-2 { - padding-bottom: 0.5rem; -} .pt-0 { padding-top: 0px; } @@ -981,6 +1002,9 @@ video { .leading-none { line-height: 1; } +.leading-relaxed { + line-height: 1.625; +} .tracking-tight { letter-spacing: -0.025em; } @@ -996,6 +1020,9 @@ video { .text-muted-foreground { color: hsl(var(--muted-foreground)); } +.text-muted-foreground\/50 { + color: hsl(var(--muted-foreground) / 0.5); +} .text-primary { color: hsl(var(--primary)); } @@ -1034,12 +1061,17 @@ video { --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } +.ring-1 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} +.ring-ring\/20 { + --tw-ring-color: hsl(var(--ring) / 0.2); +} .ring-offset-background { --tw-ring-offset-color: hsl(var(--background)); } -.filter { - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); -} .backdrop-blur-sm { --tw-backdrop-blur: blur(4px); -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); @@ -1120,6 +1152,9 @@ video { .hover\:bg-accent:hover { background-color: hsl(var(--accent)); } +.hover\:bg-accent\/30:hover { + background-color: hsl(var(--accent) / 0.3); +} .hover\:bg-accent\/50:hover { background-color: hsl(var(--accent) / 0.5); } @@ -1141,6 +1176,11 @@ video { .hover\:opacity-100:hover { opacity: 1; } +.hover\:shadow-md:hover { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} .focus\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; @@ -1188,6 +1228,11 @@ video { .disabled\:opacity-50:disabled { opacity: 0.5; } +.group:hover .group-hover\:scale-110 { + --tw-scale-x: 1.1; + --tw-scale-y: 1.1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} .data-\[state\=checked\]\:translate-x-4[data-state="checked"] { --tw-translate-x: 1rem; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -1318,20 +1363,4 @@ video { .sm\:text-left { text-align: left; } -} -@media (min-width: 768px) { - - .md\:block { - display: block; - } - - .md\:hidden { - display: none; - } -} -@media (min-width: 1024px) { - - .lg\:w-\[300px\] { - width: 300px; - } } \ No newline at end of file diff --git a/frontend/common/src/components/SessionDrawer.tsx b/frontend/common/src/components/SessionDrawer.tsx index 67fbf65..ad3596e 100644 --- a/frontend/common/src/components/SessionDrawer.tsx +++ b/frontend/common/src/components/SessionDrawer.tsx @@ -1,14 +1,11 @@ import React from 'react'; -import { Menu } from 'lucide-react'; import { Sheet, - SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetClose } from './ui/sheet'; -import { Button } from './ui/button'; import { ScrollArea } from './ui/scroll-area'; import { AgentSession, getSampleAgentSessions } from '../utils/sample-data'; @@ -16,12 +13,16 @@ interface SessionDrawerProps { onSelectSession?: (sessionId: string) => void; currentSessionId?: string; sessions?: AgentSession[]; + isOpen?: boolean; + onClose?: () => void; } export const SessionDrawer: React.FC = ({ onSelectSession, currentSessionId, - sessions = getSampleAgentSessions() + sessions = getSampleAgentSessions(), + isOpen = false, + onClose }) => { // Get status color const getStatusColor = (status: string) => { @@ -48,19 +49,16 @@ export const SessionDrawer: React.FC = ({ }; return ( - - - - - + + Sessions - -
+ +
{sessions.map((session) => ( -
- -
- {filterTypes.map(type => ( - - ))} -
+
+
+ {sortedSteps.length > 0 ? ( + sortedSteps.map((step) => ( + + )) + ) : ( +
+ + + +

No steps to display

+
+ )}
- - -
- {sortedSteps.length > 0 ? ( - sortedSteps.map((step) => ( - - )) - ) : ( -
- No steps to display -
- )} -
-
); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/common/src/components/TimelineStep.tsx b/frontend/common/src/components/TimelineStep.tsx index 52522fa..45df5d8 100644 --- a/frontend/common/src/components/TimelineStep.tsx +++ b/frontend/common/src/components/TimelineStep.tsx @@ -47,34 +47,47 @@ export const TimelineStep: React.FC = ({ step }) => { }; return ( - - -
-
-
{getTypeIcon(step.type)}
+ + +
+
+
{getTypeIcon(step.type)}
-
{step.title}
-
+
{step.title}
+
{step.type === 'tool-execution' ? 'Run tool' : step.content.substring(0, 60)} {step.content.length > 60 ? '...' : ''}
- {formatTime(step.timestamp)} + {formatTime(step.timestamp)} {step.duration && ( - {(step.duration / 1000).toFixed(1)}s + + {(step.duration / 1000).toFixed(1)}s + )}
-
-
+
+
{step.content}
{step.duration && ( -
-
+
+
+ + + + Duration: {(step.duration / 1000).toFixed(1)} seconds
diff --git a/frontend/common/src/components/ui/sheet.tsx b/frontend/common/src/components/ui/sheet.tsx index e8b5a68..41ef9a9 100644 --- a/frontend/common/src/components/ui/sheet.tsx +++ b/frontend/common/src/components/ui/sheet.tsx @@ -19,7 +19,7 @@ const SheetOverlay = React.forwardRef< >(({ className, ...props }, ref) => ( = ({ header, sidebar, drawer, children }) => { + return ( + <> + + +
+
+ {header} +
+ + {sidebar && ( + + )} + + {/* Mobile drawer - rendered outside grid */} + {drawer} + +
+ {children} +
+
+ + ); +}; \ No newline at end of file diff --git a/frontend/web/src/index.tsx b/frontend/web/src/index.tsx index f60a342..e482dc7 100644 --- a/frontend/web/src/index.tsx +++ b/frontend/web/src/index.tsx @@ -8,6 +8,7 @@ import { getSampleAgentSessions, getSampleAgentSteps } from '@ra-aid/common'; +import { Layout } from './components/Layout'; // The CSS import happens through the common package's index.ts // Theme management helper function @@ -85,122 +86,125 @@ const App = () => { localStorage.setItem('theme', newIsDark ? 'dark' : 'light'); }; - return ( -
- {/* Header */} -
-
-

- RA-Aid -

- -
- {/* Theme toggle button */} - - - {/* Mobile drawer toggle - show only on small screens */} -
- -
-
-
-
- - {/* Main content */} -
- {/* Desktop sidebar - hidden on mobile */} - - - {/* Mobile drawer */} - - - {/* Main content area */} -
- {selectedSessionId ? ( - <> -

- Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'} -

- - + + + + + + + + + + ) : ( -
-

Select a session to view details

-
+ // Moon icon for dark mode toggle + + + )} -
+ + + {/* Mobile drawer toggle - show only on small screens */} +
+ +
- -
-

Built with shadcn/ui components from the RA-Aid common package

-
); + + // Render sidebar content + const sidebarContent = ( + + ); + + // Render drawer + const drawerContent = ( + setIsDrawerOpen(false)} + /> + ); + + // Render main content + const mainContent = ( + selectedSessionId ? ( + <> +

+ Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'} +

+ + + ) : ( +
+

Select a session to view details

+
+ ) + ); + + return ( + + {mainContent} + + ); }; // Initialize theme before rendering the app From af16879dd61193d9332307837970a9bd9809822f Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Fri, 14 Mar 2025 09:15:11 -0400 Subject: [PATCH 2/7] ui styling --- frontend/common/dist/components/ui/index.d.ts | 1 + frontend/common/dist/components/ui/index.js | 1 + frontend/common/dist/index.d.ts | 3 +- frontend/common/dist/index.js | 6 +- frontend/common/dist/styles/global.css | 77 +++++++++++++-- .../common/src/components/SessionDrawer.tsx | 3 +- .../common/src/components/SessionSidebar.tsx | 3 +- .../common/src/components/TimelineFeed.tsx | 2 +- .../common/src/components/TimelineStep.tsx | 2 +- frontend/common/src/components/ui/index.ts | 1 + frontend/common/src/components/ui/layout.tsx | 41 ++++++++ frontend/common/src/index.ts | 11 ++- frontend/common/src/utils/sample-data.ts | 25 +---- frontend/common/src/utils/types.ts | 28 ++++++ frontend/web/src/components/Layout.tsx | 99 ------------------- frontend/web/src/index.tsx | 2 +- 16 files changed, 163 insertions(+), 142 deletions(-) create mode 100644 frontend/common/src/components/ui/layout.tsx create mode 100644 frontend/common/src/utils/types.ts delete mode 100644 frontend/web/src/components/Layout.tsx diff --git a/frontend/common/dist/components/ui/index.d.ts b/frontend/common/dist/components/ui/index.d.ts index 744def9..89b9725 100644 --- a/frontend/common/dist/components/ui/index.d.ts +++ b/frontend/common/dist/components/ui/index.d.ts @@ -2,6 +2,7 @@ export * from './button'; export * from './card'; export * from './collapsible'; export * from './input'; +export * from './layout'; export * from './sheet'; export * from './switch'; export * from './scroll-area'; diff --git a/frontend/common/dist/components/ui/index.js b/frontend/common/dist/components/ui/index.js index 744def9..89b9725 100644 --- a/frontend/common/dist/components/ui/index.js +++ b/frontend/common/dist/components/ui/index.js @@ -2,6 +2,7 @@ export * from './button'; export * from './card'; export * from './collapsible'; export * from './input'; +export * from './layout'; export * from './sheet'; export * from './switch'; export * from './scroll-area'; diff --git a/frontend/common/dist/index.d.ts b/frontend/common/dist/index.d.ts index debff1f..06d95ab 100644 --- a/frontend/common/dist/index.d.ts +++ b/frontend/common/dist/index.d.ts @@ -1,4 +1,5 @@ import './styles/global.css'; +export * from './utils/types'; export * from './utils'; export * from './components/ui'; export * from './components/TimelineStep'; @@ -6,4 +7,4 @@ export * from './components/TimelineFeed'; export * from './components/SessionDrawer'; export * from './components/SessionSidebar'; export declare const hello: () => void; -export { getSampleAgentSteps, getSampleAgentSessions, type AgentStep, type AgentSession } from './utils/sample-data'; +export { getSampleAgentSteps, getSampleAgentSessions } from './utils/sample-data'; diff --git a/frontend/common/dist/index.js b/frontend/common/dist/index.js index 6a1ebf0..6ad90fa 100644 --- a/frontend/common/dist/index.js +++ b/frontend/common/dist/index.js @@ -1,6 +1,8 @@ // Entry point for @ra-aid/common package import './styles/global.css'; -// Export utility functions (excluding sample data to avoid circular references) +// Export types first to avoid circular references +export * from './utils/types'; +// Export utility functions export * from './utils'; // Export all UI components export * from './components/ui'; @@ -14,5 +16,5 @@ export * from './components/SessionSidebar'; export const hello = () => { console.log("Hello from @ra-aid/common"); }; -// Directly export sample data functions and types to avoid circular references +// Directly export sample data functions export { getSampleAgentSteps, getSampleAgentSessions } from './utils/sample-data'; diff --git a/frontend/common/dist/styles/global.css b/frontend/common/dist/styles/global.css index 7e46c9e..2813e8d 100644 --- a/frontend/common/dist/styles/global.css +++ b/frontend/common/dist/styles/global.css @@ -585,6 +585,9 @@ video { .pointer-events-none { pointer-events: none; } +.visible { + visibility: visible; +} .fixed { position: fixed; } @@ -594,6 +597,9 @@ video { .relative { position: relative; } +.sticky { + position: sticky; +} .inset-0 { inset: 0px; } @@ -620,14 +626,26 @@ video { .top-0 { top: 0px; } +.top-16 { + top: 4rem; +} .top-4 { top: 1rem; } -.z-40 { - z-index: 40; +.z-20 { + z-index: 20; } -.z-50 { - z-index: 50; +.z-30 { + z-index: 30; +} +.col-span-full { + grid-column: 1 / -1; +} +.col-start-1 { + grid-column-start: 1; +} +.row-start-2 { + grid-row-start: 2; } .mx-auto { margin-left: auto; @@ -666,9 +684,18 @@ video { .inline-flex { display: inline-flex; } +.grid { + display: grid; +} +.hidden { + display: none; +} .h-10 { height: 2.5rem; } +.h-16 { + height: 4rem; +} .h-2\.5 { height: 0.625rem; } @@ -690,12 +717,18 @@ video { .h-9 { height: 2.25rem; } -.h-\[calc\(100vh-5rem\)\] { - height: calc(100vh - 5rem); +.h-\[calc\(100vh-64px\)\] { + height: calc(100vh - 64px); +} +.h-\[calc\(100vh-9rem\)\] { + height: calc(100vh - 9rem); } .h-full { height: 100%; } +.min-h-screen { + min-height: 100vh; +} .w-2\.5 { width: 0.625rem; } @@ -749,6 +782,12 @@ video { -moz-user-select: none; user-select: none; } +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} +.grid-rows-\[64px_1fr\] { + grid-template-rows: 64px 1fr; +} .flex-col { flex-direction: column; } @@ -804,6 +843,9 @@ video { .overflow-hidden { overflow: hidden; } +.overflow-y-auto { + overflow-y: auto; +} .truncate { overflow: hidden; text-overflow: ellipsis; @@ -919,6 +961,9 @@ video { --tw-bg-opacity: 1; background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1)); } +.p-2 { + padding: 0.5rem; +} .p-3 { padding: 0.75rem; } @@ -1363,4 +1408,24 @@ video { .sm\:text-left { text-align: left; } +} +@media (min-width: 768px) { + + .md\:col-start-2 { + grid-column-start: 2; + } + + .md\:block { + display: block; + } + + .md\:grid-cols-\[250px_1fr\] { + grid-template-columns: 250px 1fr; + } +} +@media (min-width: 1024px) { + + .lg\:grid-cols-\[300px_1fr\] { + grid-template-columns: 300px 1fr; + } } \ No newline at end of file diff --git a/frontend/common/src/components/SessionDrawer.tsx b/frontend/common/src/components/SessionDrawer.tsx index ad3596e..1343a63 100644 --- a/frontend/common/src/components/SessionDrawer.tsx +++ b/frontend/common/src/components/SessionDrawer.tsx @@ -7,7 +7,8 @@ import { SheetClose } from './ui/sheet'; import { ScrollArea } from './ui/scroll-area'; -import { AgentSession, getSampleAgentSessions } from '../utils/sample-data'; +import { AgentSession } from '../utils/types'; +import { getSampleAgentSessions } from '../utils/sample-data'; interface SessionDrawerProps { onSelectSession?: (sessionId: string) => void; diff --git a/frontend/common/src/components/SessionSidebar.tsx b/frontend/common/src/components/SessionSidebar.tsx index 9724dee..0b52e8c 100644 --- a/frontend/common/src/components/SessionSidebar.tsx +++ b/frontend/common/src/components/SessionSidebar.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { ScrollArea } from './ui/scroll-area'; -import { AgentSession, getSampleAgentSessions } from '../utils/sample-data'; +import { AgentSession } from '../utils/types'; +import { getSampleAgentSessions } from '../utils/sample-data'; interface SessionSidebarProps { onSelectSession?: (sessionId: string) => void; diff --git a/frontend/common/src/components/TimelineFeed.tsx b/frontend/common/src/components/TimelineFeed.tsx index b6a0de0..9ed22fb 100644 --- a/frontend/common/src/components/TimelineFeed.tsx +++ b/frontend/common/src/components/TimelineFeed.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { TimelineStep } from './TimelineStep'; -import { AgentStep } from '../utils/sample-data'; +import { AgentStep } from '../utils/types'; interface TimelineFeedProps { steps: AgentStep[]; diff --git a/frontend/common/src/components/TimelineStep.tsx b/frontend/common/src/components/TimelineStep.tsx index 45df5d8..d4a4754 100644 --- a/frontend/common/src/components/TimelineStep.tsx +++ b/frontend/common/src/components/TimelineStep.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible'; -import { AgentStep } from '../utils/sample-data'; +import { AgentStep } from '../utils/types'; interface TimelineStepProps { step: AgentStep; diff --git a/frontend/common/src/components/ui/index.ts b/frontend/common/src/components/ui/index.ts index b84f12d..1b2cc61 100644 --- a/frontend/common/src/components/ui/index.ts +++ b/frontend/common/src/components/ui/index.ts @@ -2,6 +2,7 @@ export * from './button'; export * from './card'; export * from './collapsible'; export * from './input'; +export * from './layout'; export * from './sheet'; export * from './switch'; export * from './scroll-area'; \ No newline at end of file diff --git a/frontend/common/src/components/ui/layout.tsx b/frontend/common/src/components/ui/layout.tsx new file mode 100644 index 0000000..b2da5be --- /dev/null +++ b/frontend/common/src/components/ui/layout.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +/** + * Layout component using Tailwind Grid utilities + * This component creates a responsive layout with: + * - Sticky header at the top (z-index 30) + * - Sidebar on desktop (hidden on mobile) + * - Main content area with proper positioning + */ +export interface LayoutProps { + header: React.ReactNode; + sidebar?: React.ReactNode; + drawer?: React.ReactNode; + children: React.ReactNode; +} + +export const Layout: React.FC = ({ header, sidebar, drawer, children }) => { + return ( +
+ {/* Header - always visible, spans full width */} +
+ {header} +
+ + {/* Sidebar - hidden on mobile, visible on tablet/desktop */} + {sidebar && ( + + )} + + {/* Main content area */} +
+ {children} +
+ + {/* Mobile drawer - rendered outside grid */} + {drawer} +
+ ); +}; \ No newline at end of file diff --git a/frontend/common/src/index.ts b/frontend/common/src/index.ts index 56ce87c..6a63b0c 100644 --- a/frontend/common/src/index.ts +++ b/frontend/common/src/index.ts @@ -1,7 +1,10 @@ // Entry point for @ra-aid/common package import './styles/global.css'; -// Export utility functions (excluding sample data to avoid circular references) +// Export types first to avoid circular references +export * from './utils/types'; + +// Export utility functions export * from './utils'; // Export all UI components @@ -20,10 +23,8 @@ export const hello = (): void => { console.log("Hello from @ra-aid/common"); }; -// Directly export sample data functions and types to avoid circular references +// Directly export sample data functions export { getSampleAgentSteps, - getSampleAgentSessions, - type AgentStep, - type AgentSession + getSampleAgentSessions } from './utils/sample-data'; \ No newline at end of file diff --git a/frontend/common/src/utils/sample-data.ts b/frontend/common/src/utils/sample-data.ts index c4c2fc8..58c6dbc 100644 --- a/frontend/common/src/utils/sample-data.ts +++ b/frontend/common/src/utils/sample-data.ts @@ -2,30 +2,7 @@ * Sample data utility for agent UI components demonstration */ -/** - * Represents a single step in the agent process - */ -export interface AgentStep { - id: string; - timestamp: Date; - status: 'completed' | 'in-progress' | 'error' | 'pending'; - type: 'tool-execution' | 'thinking' | 'planning' | 'implementation' | 'user-input'; - title: string; - content: string; - duration?: number; // in milliseconds -} - -/** - * Represents a session with multiple steps - */ -export interface AgentSession { - id: string; - name: string; - created: Date; - updated: Date; - status: 'active' | 'completed' | 'error'; - steps: AgentStep[]; -} +import { AgentStep, AgentSession } from './types'; /** * Returns an array of sample agent steps diff --git a/frontend/common/src/utils/types.ts b/frontend/common/src/utils/types.ts new file mode 100644 index 0000000..86f12f8 --- /dev/null +++ b/frontend/common/src/utils/types.ts @@ -0,0 +1,28 @@ +/** + * Common types for agent UI components + */ + +/** + * Represents a single step in the agent process + */ +export interface AgentStep { + id: string; + timestamp: Date; + status: 'completed' | 'in-progress' | 'error' | 'pending'; + type: 'tool-execution' | 'thinking' | 'planning' | 'implementation' | 'user-input'; + title: string; + content: string; + duration?: number; // in milliseconds +} + +/** + * Represents a session with multiple steps + */ +export interface AgentSession { + id: string; + name: string; + created: Date; + updated: Date; + status: 'active' | 'completed' | 'error'; + steps: AgentStep[]; +} \ No newline at end of file diff --git a/frontend/web/src/components/Layout.tsx b/frontend/web/src/components/Layout.tsx deleted file mode 100644 index a3b7eee..0000000 --- a/frontend/web/src/components/Layout.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { ReactNode } from 'react'; - -interface LayoutProps { - header: ReactNode; - sidebar?: ReactNode; - drawer?: ReactNode; - children: ReactNode; -} - -/** - * Layout component using CSS Grid with named areas - * This component creates a responsive layout with: - * - Sticky header at the top (z-index 30) - * - Sidebar on desktop (hidden on mobile) - * - Main content area with proper positioning - */ -export const Layout: React.FC = ({ header, sidebar, drawer, children }) => { - return ( - <> - - -
-
- {header} -
- - {sidebar && ( - - )} - - {/* Mobile drawer - rendered outside grid */} - {drawer} - -
- {children} -
-
- - ); -}; \ No newline at end of file diff --git a/frontend/web/src/index.tsx b/frontend/web/src/index.tsx index e482dc7..f46832b 100644 --- a/frontend/web/src/index.tsx +++ b/frontend/web/src/index.tsx @@ -2,13 +2,13 @@ import React, { useState, useEffect } from 'react'; import ReactDOM from 'react-dom/client'; import { Button, + Layout, SessionDrawer, SessionSidebar, TimelineFeed, getSampleAgentSessions, getSampleAgentSteps } from '@ra-aid/common'; -import { Layout } from './components/Layout'; // The CSS import happens through the common package's index.ts // Theme management helper function From 8a507f245e856dd07d817609753ea36741414645 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Fri, 14 Mar 2025 10:08:17 -0400 Subject: [PATCH 3/7] floating action button for sessions panel --- frontend/common/dist/components/ui/index.d.ts | 1 + frontend/common/dist/components/ui/index.js | 1 + frontend/common/dist/index.d.ts | 1 + frontend/common/dist/index.js | 2 + frontend/common/dist/styles/global.css | 102 +++++++++ .../src/components/DefaultAgentScreen.tsx | 215 ++++++++++++++++++ .../components/ui/floating-action-button.tsx | 36 +++ frontend/common/src/components/ui/index.ts | 1 + frontend/common/src/components/ui/layout.tsx | 19 +- frontend/common/src/index.ts | 3 + frontend/web/src/index.tsx | 215 +----------------- 11 files changed, 387 insertions(+), 209 deletions(-) create mode 100644 frontend/common/src/components/DefaultAgentScreen.tsx create mode 100644 frontend/common/src/components/ui/floating-action-button.tsx diff --git a/frontend/common/dist/components/ui/index.d.ts b/frontend/common/dist/components/ui/index.d.ts index 89b9725..838adde 100644 --- a/frontend/common/dist/components/ui/index.d.ts +++ b/frontend/common/dist/components/ui/index.d.ts @@ -1,6 +1,7 @@ export * from './button'; export * from './card'; export * from './collapsible'; +export * from './floating-action-button'; export * from './input'; export * from './layout'; export * from './sheet'; diff --git a/frontend/common/dist/components/ui/index.js b/frontend/common/dist/components/ui/index.js index 89b9725..838adde 100644 --- a/frontend/common/dist/components/ui/index.js +++ b/frontend/common/dist/components/ui/index.js @@ -1,6 +1,7 @@ export * from './button'; export * from './card'; export * from './collapsible'; +export * from './floating-action-button'; export * from './input'; export * from './layout'; export * from './sheet'; diff --git a/frontend/common/dist/index.d.ts b/frontend/common/dist/index.d.ts index 06d95ab..3f13bba 100644 --- a/frontend/common/dist/index.d.ts +++ b/frontend/common/dist/index.d.ts @@ -6,5 +6,6 @@ export * from './components/TimelineStep'; export * from './components/TimelineFeed'; export * from './components/SessionDrawer'; export * from './components/SessionSidebar'; +export * from './components/DefaultAgentScreen'; export declare const hello: () => void; export { getSampleAgentSteps, getSampleAgentSessions } from './utils/sample-data'; diff --git a/frontend/common/dist/index.js b/frontend/common/dist/index.js index 6ad90fa..863663b 100644 --- a/frontend/common/dist/index.js +++ b/frontend/common/dist/index.js @@ -12,6 +12,8 @@ export * from './components/TimelineFeed'; // Export session navigation components export * from './components/SessionDrawer'; export * from './components/SessionSidebar'; +// Export the main screen component +export * from './components/DefaultAgentScreen'; // Export the hello function (temporary example) export const hello = () => { console.log("Hello from @ra-aid/common"); diff --git a/frontend/common/dist/styles/global.css b/frontend/common/dist/styles/global.css index 2813e8d..d68f322 100644 --- a/frontend/common/dist/styles/global.css +++ b/frontend/common/dist/styles/global.css @@ -614,6 +614,9 @@ video { .bottom-0 { bottom: 0px; } +.bottom-6 { + bottom: 1.5rem; +} .left-0 { left: 0px; } @@ -623,6 +626,9 @@ video { .right-4 { right: 1rem; } +.right-6 { + right: 1.5rem; +} .top-0 { top: 0px; } @@ -638,6 +644,9 @@ video { .z-30 { z-index: 30; } +.z-50 { + z-index: 50; +} .col-span-full { grid-column: 1 / -1; } @@ -654,12 +663,18 @@ video { .mb-2 { margin-bottom: 0.5rem; } +.mb-4 { + margin-bottom: 1rem; +} .mb-5 { margin-bottom: 1.25rem; } .mr-1 { margin-right: 0.25rem; } +.mr-2 { + margin-right: 0.5rem; +} .mr-3 { margin-right: 0.75rem; } @@ -678,6 +693,9 @@ video { .block { display: block; } +.inline-block { + display: inline-block; +} .flex { display: flex; } @@ -693,6 +711,9 @@ video { .h-10 { height: 2.5rem; } +.h-14 { + height: 3.5rem; +} .h-16 { height: 4rem; } @@ -711,6 +732,9 @@ video { .h-5 { height: 1.25rem; } +.h-6 { + height: 1.5rem; +} .h-8 { height: 2rem; } @@ -729,6 +753,9 @@ video { .min-h-screen { min-height: 100vh; } +.w-14 { + width: 3.5rem; +} .w-2\.5 { width: 0.625rem; } @@ -744,6 +771,9 @@ video { .w-4 { width: 1rem; } +.w-6 { + width: 1.5rem; +} .w-8 { width: 2rem; } @@ -809,6 +839,9 @@ video { .justify-between { justify-content: space-between; } +.gap-2 { + gap: 0.5rem; +} .gap-4 { gap: 1rem; } @@ -905,6 +938,10 @@ video { .border-transparent { border-color: transparent; } +.border-white { + --tw-border-opacity: 1; + border-color: rgb(255 255 255 / var(--tw-border-opacity, 1)); +} .border-l-transparent { border-left-color: transparent; } @@ -924,6 +961,10 @@ video { --tw-bg-opacity: 1; background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1)); } +.bg-blue-600 { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); +} .bg-border { background-color: hsl(var(--border)); } @@ -951,6 +992,10 @@ video { --tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)); } +.bg-red-600 { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1)); +} .bg-secondary { background-color: hsl(var(--secondary)); } @@ -961,6 +1006,21 @@ video { --tw-bg-opacity: 1; background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1)); } +.bg-gradient-to-r { + background-image: linear-gradient(to right, var(--tw-gradient-stops)); +} +.from-blue-400 { + --tw-gradient-from: #60a5fa var(--tw-gradient-from-position); + --tw-gradient-to: rgb(96 165 250 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} +.to-purple-500 { + --tw-gradient-to: #a855f7 var(--tw-gradient-to-position); +} +.bg-clip-text { + -webkit-background-clip: text; + background-clip: text; +} .p-2 { padding: 0.5rem; } @@ -1023,6 +1083,10 @@ video { .text-center { text-align: center; } +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} .text-lg { font-size: 1.125rem; line-height: 1.75rem; @@ -1031,10 +1095,17 @@ video { font-size: 0.875rem; line-height: 1.25rem; } +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} .text-xs { font-size: 0.75rem; line-height: 1rem; } +.font-bold { + font-weight: 700; +} .font-medium { font-weight: 500; } @@ -1077,6 +1148,13 @@ video { .text-secondary-foreground { color: hsl(var(--secondary-foreground)); } +.text-transparent { + color: transparent; +} +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity, 1)); +} .underline-offset-4 { text-underline-offset: 4px; } @@ -1098,6 +1176,11 @@ video { --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.shadow-xl { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} .outline { outline-style: solid; } @@ -1117,6 +1200,9 @@ video { .ring-offset-background { --tw-ring-offset-color: hsl(var(--background)); } +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} .backdrop-blur-sm { --tw-backdrop-blur: blur(4px); -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); @@ -1203,12 +1289,20 @@ video { .hover\:bg-accent\/50:hover { background-color: hsl(var(--accent) / 0.5); } +.hover\:bg-blue-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1)); +} .hover\:bg-destructive\/90:hover { background-color: hsl(var(--destructive) / 0.9); } .hover\:bg-primary\/90:hover { background-color: hsl(var(--primary) / 0.9); } +.hover\:bg-red-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1)); +} .hover\:bg-secondary\/80:hover { background-color: hsl(var(--secondary) / 0.8); } @@ -1381,6 +1475,10 @@ video { .data-\[state\=open\]\:duration-500[data-state="open"] { animation-duration: 500ms; } +.dark\:border-gray-800:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(31 41 55 / var(--tw-border-opacity, 1)); +} @media (min-width: 640px) { .sm\:max-w-md { @@ -1419,6 +1517,10 @@ video { display: block; } + .md\:hidden { + display: none; + } + .md\:grid-cols-\[250px_1fr\] { grid-template-columns: 250px 1fr; } diff --git a/frontend/common/src/components/DefaultAgentScreen.tsx b/frontend/common/src/components/DefaultAgentScreen.tsx new file mode 100644 index 0000000..916fe5b --- /dev/null +++ b/frontend/common/src/components/DefaultAgentScreen.tsx @@ -0,0 +1,215 @@ +import React, { useState, useEffect } from 'react'; +import { PanelLeft } from 'lucide-react'; +import { + Button, + Layout +} from './ui'; +import { SessionDrawer } from './SessionDrawer'; +import { SessionSidebar } from './SessionSidebar'; +import { TimelineFeed } from './TimelineFeed'; +import { getSampleAgentSessions, getSampleAgentSteps } from '../utils/sample-data'; + +/** + * DefaultAgentScreen component + * + * Main application screen for displaying agent sessions and their steps. + * Handles state management, responsive design, and UI interactions. + */ +export const DefaultAgentScreen: React.FC = () => { + // State for drawer open/close + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + + // State for selected session + const [selectedSessionId, setSelectedSessionId] = useState(null); + + // State for theme (dark is default) + const [isDarkTheme, setIsDarkTheme] = useState(true); + + // Get sample data + const sessions = getSampleAgentSessions(); + const allSteps = getSampleAgentSteps(); + + // Set up theme on component mount + useEffect(() => { + const isDark = setupTheme(); + setIsDarkTheme(isDark); + }, []); + + // Set initial selected session if none selected + useEffect(() => { + if (!selectedSessionId && sessions.length > 0) { + setSelectedSessionId(sessions[0].id); + } + }, [sessions, selectedSessionId]); + + // Filter steps for selected session + const selectedSessionSteps = selectedSessionId + ? allSteps.filter(step => sessions.find(s => s.id === selectedSessionId)?.steps.some(s => s.id === step.id)) + : []; + + // Handle session selection + const handleSessionSelect = (sessionId: string) => { + setSelectedSessionId(sessionId); + setIsDrawerOpen(false); // Close drawer on selection (mobile) + }; + + // Toggle theme function + const toggleTheme = () => { + const newIsDark = !isDarkTheme; + setIsDarkTheme(newIsDark); + + // Update document element class + if (newIsDark) { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + + // Save to localStorage + localStorage.setItem('theme', newIsDark ? 'dark' : 'light'); + }; + + // Render header content + const headerContent = ( +
+

+ RA-Aid +

+ +
+ {/* Theme toggle button */} + +
+
+ ); + + // Render sidebar content + const sidebarContent = ( + + ); + + // Render drawer + const drawerContent = ( + setIsDrawerOpen(false)} + /> + ); + + // Render main content + const mainContent = ( + selectedSessionId ? ( + <> +

+ Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'} +

+ + + ) : ( +
+

Select a session to view details

+
+ ) + ); + + // Create floating action button for mobile sidebar toggle + const floatingAction = ( + + ); + + return ( + <> + + {mainContent} + +
+ {floatingAction} +
+ + ); +}; + +// Helper function for theme setup +const setupTheme = () => { + // Check if theme preference is stored in localStorage + const storedTheme = localStorage.getItem('theme'); + + // Default to dark mode unless explicitly set to light + const isDark = storedTheme ? storedTheme === 'dark' : true; + + // Apply theme to document + if (isDark) { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + + return isDark; +}; diff --git a/frontend/common/src/components/ui/floating-action-button.tsx b/frontend/common/src/components/ui/floating-action-button.tsx new file mode 100644 index 0000000..90ed420 --- /dev/null +++ b/frontend/common/src/components/ui/floating-action-button.tsx @@ -0,0 +1,36 @@ +import React, { ReactNode } from 'react'; +import { Button } from './button'; + +export interface FloatingActionButtonProps { + icon: ReactNode; + onClick: () => void; + ariaLabel?: string; + className?: string; + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'; +} + +/** + * FloatingActionButton component + * + * A button typically used for primary actions on mobile layouts + * Designed to be used with the Layout component's floatingAction prop + */ +export const FloatingActionButton: React.FC = ({ + icon, + onClick, + ariaLabel = 'Action button', + className = '', + variant = 'default' +}) => { + return ( + + ); +}; diff --git a/frontend/common/src/components/ui/index.ts b/frontend/common/src/components/ui/index.ts index 1b2cc61..b7dc02e 100644 --- a/frontend/common/src/components/ui/index.ts +++ b/frontend/common/src/components/ui/index.ts @@ -1,6 +1,7 @@ export * from './button'; export * from './card'; export * from './collapsible'; +export * from './floating-action-button'; export * from './input'; export * from './layout'; export * from './sheet'; diff --git a/frontend/common/src/components/ui/layout.tsx b/frontend/common/src/components/ui/layout.tsx index b2da5be..97fee7b 100644 --- a/frontend/common/src/components/ui/layout.tsx +++ b/frontend/common/src/components/ui/layout.tsx @@ -6,17 +6,25 @@ import React from 'react'; * - Sticky header at the top (z-index 30) * - Sidebar on desktop (hidden on mobile) * - Main content area with proper positioning + * - Optional floating action button for mobile navigation */ export interface LayoutProps { header: React.ReactNode; sidebar?: React.ReactNode; drawer?: React.ReactNode; children: React.ReactNode; + floatingAction?: React.ReactNode; } -export const Layout: React.FC = ({ header, sidebar, drawer, children }) => { +export const Layout: React.FC = ({ + header, + sidebar, + drawer, + children, + floatingAction +}) => { return ( -
+
{/* Header - always visible, spans full width */}
{header} @@ -36,6 +44,13 @@ export const Layout: React.FC = ({ header, sidebar, drawer, childre {/* Mobile drawer - rendered outside grid */} {drawer} + + {/* Floating action button for mobile */} + {floatingAction && ( +
+ {floatingAction} +
+ )}
); }; \ No newline at end of file diff --git a/frontend/common/src/index.ts b/frontend/common/src/index.ts index 6a63b0c..e47a3fc 100644 --- a/frontend/common/src/index.ts +++ b/frontend/common/src/index.ts @@ -18,6 +18,9 @@ export * from './components/TimelineFeed'; export * from './components/SessionDrawer'; export * from './components/SessionSidebar'; +// Export the main screen component +export * from './components/DefaultAgentScreen'; + // Export the hello function (temporary example) export const hello = (): void => { console.log("Hello from @ra-aid/common"); diff --git a/frontend/web/src/index.tsx b/frontend/web/src/index.tsx index f46832b..bb67e26 100644 --- a/frontend/web/src/index.tsx +++ b/frontend/web/src/index.tsx @@ -1,215 +1,16 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom/client'; -import { - Button, - Layout, - SessionDrawer, - SessionSidebar, - TimelineFeed, - getSampleAgentSessions, - getSampleAgentSteps -} from '@ra-aid/common'; -// The CSS import happens through the common package's index.ts - -// Theme management helper function -const setupTheme = () => { - // Check if theme preference is stored in localStorage - const storedTheme = localStorage.getItem('theme'); - - // Default to dark mode unless explicitly set to light - const isDark = storedTheme ? storedTheme === 'dark' : true; - - // Apply theme class to document element (html) for better CSS specificity - if (isDark) { - document.documentElement.classList.add('dark'); - } else { - document.documentElement.classList.remove('dark'); - } - - // Store the current theme preference - localStorage.setItem('theme', isDark ? 'dark' : 'light'); - - return isDark; -}; +import { DefaultAgentScreen } from '@ra-aid/common'; +/** + * Main application entry point + * Simply renders the DefaultAgentScreen component from the common package + */ const App = () => { - // State for drawer open/close - const [isDrawerOpen, setIsDrawerOpen] = useState(false); - - // State for selected session - const [selectedSessionId, setSelectedSessionId] = useState(null); - - // State for theme (dark is default) - const [isDarkTheme, setIsDarkTheme] = useState(true); - - // Get sample data - const sessions = getSampleAgentSessions(); - const allSteps = getSampleAgentSteps(); - - // Set up theme on component mount - useEffect(() => { - const isDark = setupTheme(); - setIsDarkTheme(isDark); - }, []); - - // Set initial selected session if none selected - useEffect(() => { - if (!selectedSessionId && sessions.length > 0) { - setSelectedSessionId(sessions[0].id); - } - }, [sessions, selectedSessionId]); - - // Filter steps for selected session - const selectedSessionSteps = selectedSessionId - ? allSteps.filter(step => sessions.find(s => s.id === selectedSessionId)?.steps.some(s => s.id === step.id)) - : []; - - // Handle session selection - const handleSessionSelect = (sessionId: string) => { - setSelectedSessionId(sessionId); - setIsDrawerOpen(false); // Close drawer on selection (mobile) - }; - - // Toggle theme function - const toggleTheme = () => { - const newIsDark = !isDarkTheme; - setIsDarkTheme(newIsDark); - - // Update document element class - if (newIsDark) { - document.documentElement.classList.add('dark'); - } else { - document.documentElement.classList.remove('dark'); - } - - // Save to localStorage - localStorage.setItem('theme', newIsDark ? 'dark' : 'light'); - }; - - // Render header content - const headerContent = ( -
-

- RA-Aid -

- -
- {/* Theme toggle button */} - - - {/* Mobile drawer toggle - show only on small screens */} -
- -
-
-
- ); - - // Render sidebar content - const sidebarContent = ( - - ); - - // Render drawer - const drawerContent = ( - setIsDrawerOpen(false)} - /> - ); - - // Render main content - const mainContent = ( - selectedSessionId ? ( - <> -

- Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'} -

- - - ) : ( -
-

Select a session to view details

-
- ) - ); - - return ( - - {mainContent} - - ); + return ; }; -// Initialize theme before rendering the app -setupTheme(); - +// Mount the app to the root element const root = ReactDOM.createRoot(document.getElementById('root')!); root.render( From 0a46e3c92b242d62753a6e306f5e9d981559581f Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Fri, 14 Mar 2025 10:11:42 -0400 Subject: [PATCH 4/7] FAB color --- frontend/common/dist/styles/global.css | 28 +++++++++++++------ .../src/components/DefaultAgentScreen.tsx | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/frontend/common/dist/styles/global.css b/frontend/common/dist/styles/global.css index d68f322..908bb9f 100644 --- a/frontend/common/dist/styles/global.css +++ b/frontend/common/dist/styles/global.css @@ -942,6 +942,10 @@ video { --tw-border-opacity: 1; border-color: rgb(255 255 255 / var(--tw-border-opacity, 1)); } +.border-zinc-700 { + --tw-border-opacity: 1; + border-color: rgb(63 63 70 / var(--tw-border-opacity, 1)); +} .border-l-transparent { border-left-color: transparent; } @@ -992,10 +996,6 @@ video { --tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)); } -.bg-red-600 { - --tw-bg-opacity: 1; - background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1)); -} .bg-secondary { background-color: hsl(var(--secondary)); } @@ -1006,6 +1006,10 @@ video { --tw-bg-opacity: 1; background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1)); } +.bg-zinc-800 { + --tw-bg-opacity: 1; + background-color: rgb(39 39 42 / var(--tw-bg-opacity, 1)); +} .bg-gradient-to-r { background-image: linear-gradient(to right, var(--tw-gradient-stops)); } @@ -1155,6 +1159,10 @@ video { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity, 1)); } +.text-zinc-100 { + --tw-text-opacity: 1; + color: rgb(244 244 245 / var(--tw-text-opacity, 1)); +} .underline-offset-4 { text-underline-offset: 4px; } @@ -1299,13 +1307,13 @@ video { .hover\:bg-primary\/90:hover { background-color: hsl(var(--primary) / 0.9); } -.hover\:bg-red-700:hover { - --tw-bg-opacity: 1; - background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1)); -} .hover\:bg-secondary\/80:hover { background-color: hsl(var(--secondary) / 0.8); } +.hover\:bg-zinc-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(63 63 70 / var(--tw-bg-opacity, 1)); +} .hover\:text-accent-foreground:hover { color: hsl(var(--accent-foreground)); } @@ -1479,6 +1487,10 @@ video { --tw-border-opacity: 1; border-color: rgb(31 41 55 / var(--tw-border-opacity, 1)); } +.dark\:border-zinc-600:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(82 82 91 / var(--tw-border-opacity, 1)); +} @media (min-width: 640px) { .sm\:max-w-md { diff --git a/frontend/common/src/components/DefaultAgentScreen.tsx b/frontend/common/src/components/DefaultAgentScreen.tsx index 916fe5b..8537e1b 100644 --- a/frontend/common/src/components/DefaultAgentScreen.tsx +++ b/frontend/common/src/components/DefaultAgentScreen.tsx @@ -174,7 +174,7 @@ export const DefaultAgentScreen: React.FC = () => { size="icon" onClick={() => setIsDrawerOpen(true)} aria-label="Toggle sessions panel" - className="h-14 w-14 rounded-full shadow-xl bg-red-600 hover:bg-red-700 text-white flex items-center justify-center border-2 border-white dark:border-gray-800" + className="h-14 w-14 rounded-full shadow-xl bg-zinc-800 hover:bg-zinc-700 text-zinc-100 flex items-center justify-center border-2 border-zinc-700 dark:border-zinc-600" > From fe3984329dcb04a810e2224257e1f979bfaab70b Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Fri, 14 Mar 2025 10:16:23 -0400 Subject: [PATCH 5/7] make sure session list hides when open and window expanded --- frontend/common/dist/styles/global.css | 6 +- .../src/components/DefaultAgentScreen.tsx | 16 ++++ .../common/src/components/SessionDrawer.tsx | 58 ++---------- .../common/src/components/SessionList.tsx | 91 +++++++++++++++++++ .../common/src/components/SessionSidebar.tsx | 56 ++---------- 5 files changed, 125 insertions(+), 102 deletions(-) create mode 100644 frontend/common/src/components/SessionList.tsx diff --git a/frontend/common/dist/styles/global.css b/frontend/common/dist/styles/global.css index 908bb9f..3b8e0f9 100644 --- a/frontend/common/dist/styles/global.css +++ b/frontend/common/dist/styles/global.css @@ -812,6 +812,9 @@ video { -moz-user-select: none; user-select: none; } +.resize { + resize: both; +} .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } @@ -1025,9 +1028,6 @@ video { -webkit-background-clip: text; background-clip: text; } -.p-2 { - padding: 0.5rem; -} .p-3 { padding: 0.75rem; } diff --git a/frontend/common/src/components/DefaultAgentScreen.tsx b/frontend/common/src/components/DefaultAgentScreen.tsx index 8537e1b..fb34934 100644 --- a/frontend/common/src/components/DefaultAgentScreen.tsx +++ b/frontend/common/src/components/DefaultAgentScreen.tsx @@ -42,6 +42,22 @@ export const DefaultAgentScreen: React.FC = () => { } }, [sessions, selectedSessionId]); + // Close drawer when window resizes to desktop width + useEffect(() => { + const handleResize = () => { + // Check if we're at desktop size (corresponds to md: breakpoint in Tailwind) + if (window.innerWidth >= 768 && isDrawerOpen) { + setIsDrawerOpen(false); + } + }; + + // Add event listener + window.addEventListener('resize', handleResize); + + // Clean up event listener on component unmount + return () => window.removeEventListener('resize', handleResize); + }, [isDrawerOpen]); + // Filter steps for selected session const selectedSessionSteps = selectedSessionId ? allSteps.filter(step => sessions.find(s => s.id === selectedSessionId)?.steps.some(s => s.id === step.id)) diff --git a/frontend/common/src/components/SessionDrawer.tsx b/frontend/common/src/components/SessionDrawer.tsx index 1343a63..2c2a1f6 100644 --- a/frontend/common/src/components/SessionDrawer.tsx +++ b/frontend/common/src/components/SessionDrawer.tsx @@ -6,9 +6,9 @@ import { SheetTitle, SheetClose } from './ui/sheet'; -import { ScrollArea } from './ui/scroll-area'; import { AgentSession } from '../utils/types'; import { getSampleAgentSessions } from '../utils/sample-data'; +import { SessionList } from './SessionList'; interface SessionDrawerProps { onSelectSession?: (sessionId: string) => void; @@ -25,30 +25,6 @@ export const SessionDrawer: React.FC = ({ isOpen = false, onClose }) => { - // Get status color - const getStatusColor = (status: string) => { - switch (status) { - case 'active': - return 'bg-blue-500'; - case 'completed': - return 'bg-green-500'; - case 'error': - return 'bg-red-500'; - default: - return 'bg-gray-500'; - } - }; - - // Format timestamp - const formatDate = (date: Date) => { - return date.toLocaleDateString([], { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - }; - return ( = ({ Sessions - -
- {sessions.map((session) => ( - - - - ))} -
-
+
); diff --git a/frontend/common/src/components/SessionList.tsx b/frontend/common/src/components/SessionList.tsx new file mode 100644 index 0000000..d517a23 --- /dev/null +++ b/frontend/common/src/components/SessionList.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { ScrollArea } from './ui/scroll-area'; +import { AgentSession } from '../utils/types'; +import { getSampleAgentSessions } from '../utils/sample-data'; + +interface SessionListProps { + onSelectSession?: (sessionId: string) => void; + currentSessionId?: string; + sessions?: AgentSession[]; + className?: string; + wrapperComponent?: React.ElementType; + closeAction?: React.ReactNode; +} + +export const SessionList: React.FC = ({ + onSelectSession, + currentSessionId, + sessions = getSampleAgentSessions(), + className = '', + wrapperComponent: WrapperComponent = 'button', + closeAction +}) => { + // Get status color + const getStatusColor = (status: string) => { + switch (status) { + case 'active': + return 'bg-blue-500'; + case 'completed': + return 'bg-green-500'; + case 'error': + return 'bg-red-500'; + default: + return 'bg-gray-500'; + } + }; + + // Format timestamp + const formatDate = (date: Date) => { + return date.toLocaleDateString([], { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }; + + return ( + +
+ {sessions.map((session) => { + const buttonContent = ( + <> +
+
+
{session.name}
+
+ {session.steps.length} steps • {formatDate(session.updated)} +
+
+ {session.status} +
+
+ + ); + + return React.createElement( + WrapperComponent, + { + key: session.id, + onClick: () => onSelectSession?.(session.id), + className: `w-full flex items-start p-3 text-left rounded-md transition-colors hover:bg-accent/50 ${ + currentSessionId === session.id ? 'bg-accent' : '' + }` + }, + closeAction ? ( + <> + {buttonContent} + {React.cloneElement(closeAction as React.ReactElement, { + onClick: (e: React.MouseEvent) => { + e.stopPropagation(); + onSelectSession?.(session.id); + } + })} + + ) : buttonContent + ); + })} +
+ + ); +}; diff --git a/frontend/common/src/components/SessionSidebar.tsx b/frontend/common/src/components/SessionSidebar.tsx index 0b52e8c..6ce00a1 100644 --- a/frontend/common/src/components/SessionSidebar.tsx +++ b/frontend/common/src/components/SessionSidebar.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { ScrollArea } from './ui/scroll-area'; import { AgentSession } from '../utils/types'; import { getSampleAgentSessions } from '../utils/sample-data'; +import { SessionList } from './SessionList'; interface SessionSidebarProps { onSelectSession?: (sessionId: string) => void; @@ -16,59 +16,17 @@ export const SessionSidebar: React.FC = ({ sessions = getSampleAgentSessions(), className = '' }) => { - // Get status color - const getStatusColor = (status: string) => { - switch (status) { - case 'active': - return 'bg-blue-500'; - case 'completed': - return 'bg-green-500'; - case 'error': - return 'bg-red-500'; - default: - return 'bg-gray-500'; - } - }; - - // Format timestamp - const formatDate = (date: Date) => { - return date.toLocaleDateString([], { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - }; - return (

Sessions

- -
- {sessions.map((session) => ( - - ))} -
-
+
); }; \ No newline at end of file From 07c6c2e5b5f5b6fd7ed752cf13cd24cd68bb9973 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Fri, 14 Mar 2025 10:25:22 -0400 Subject: [PATCH 6/7] fix hot reload on dev server --- frontend/common/package.json | 7 ++++--- frontend/common/src/index.ts | 3 --- frontend/web/vite.config.js | 33 +++++++++++++++++++++++++++------ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/frontend/common/package.json b/frontend/common/package.json index a876539..ff12aac 100644 --- a/frontend/common/package.json +++ b/frontend/common/package.json @@ -2,11 +2,12 @@ "name": "@ra-aid/common", "version": "1.0.0", "private": true, - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "src/index.ts", + "types": "src/index.ts", "scripts": { "build": "tsc && postcss src/styles/global.css -o dist/styles/global.css", - "dev": "concurrently \"tsc --watch\" \"postcss src/styles/global.css -o dist/styles/global.css --watch\"" + "dev": "concurrently \"tsc --watch\" \"postcss src/styles/global.css -o dist/styles/global.css --watch\"", + "prepare": "npm run build" }, "dependencies": { "@radix-ui/react-collapsible": "^1.1.3", diff --git a/frontend/common/src/index.ts b/frontend/common/src/index.ts index e47a3fc..6a63b0c 100644 --- a/frontend/common/src/index.ts +++ b/frontend/common/src/index.ts @@ -18,9 +18,6 @@ export * from './components/TimelineFeed'; export * from './components/SessionDrawer'; export * from './components/SessionSidebar'; -// Export the main screen component -export * from './components/DefaultAgentScreen'; - // Export the hello function (temporary example) export const hello = (): void => { console.log("Hello from @ra-aid/common"); diff --git a/frontend/web/vite.config.js b/frontend/web/vite.config.js index 6517ea5..082859f 100644 --- a/frontend/web/vite.config.js +++ b/frontend/web/vite.config.js @@ -3,23 +3,44 @@ import react from '@vitejs/plugin-react'; import path from 'path'; export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + ], resolve: { - // Ensure that Vite treats symlinked packages as local, so HMR works correctly. + // Point to the source files instead of dist for development alias: { - '@ra-aid/common': path.resolve(__dirname, '../common/dist') + '@ra-aid/common': path.resolve(__dirname, '../common/src') + } + }, + optimizeDeps: { + // Force Vite to include these dependencies in its optimization + include: ['@ra-aid/common'], + // Tell Vite to respect our aliased packages instead of using node_modules for them + esbuildOptions: { + preserveSymlinks: true, } }, server: { + hmr: { + // More verbose logging for HMR + overlay: true, + }, watch: { - // Watch for changes in the common package. - // This pattern forces Vite to notice file changes in the shared library. - paths: ['../common/src/**', '../common/dist/**'] + // Watch for changes in the common package + paths: ['../common/src/**'], + // Ensure changes in source files trigger a reload + usePolling: true, } }, css: { // PostCSS configuration is loaded from postcss.config.js // This ensures proper processing of Tailwind directives devSourcemap: true, + }, + build: { + // When building for production, we need to make sure the common package is built too + commonjsOptions: { + transformMixedEsModules: true, + }, } }); \ No newline at end of file From 0c40fa72c3c4c5b6640ea1cf085def171253b536 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Fri, 14 Mar 2025 15:09:22 -0400 Subject: [PATCH 7/7] style/hmr --- frontend/common/dist/index.js | 4 +- frontend/common/dist/styles/global.css | 27 +++++++++++ frontend/common/package.json | 4 +- .../src/components/DefaultAgentScreen.tsx | 2 +- frontend/common/src/index.ts | 5 +- frontend/web/vite.config.js | 46 ++++++++----------- 6 files changed, 57 insertions(+), 31 deletions(-) diff --git a/frontend/common/dist/index.js b/frontend/common/dist/index.js index 863663b..e06a60c 100644 --- a/frontend/common/dist/index.js +++ b/frontend/common/dist/index.js @@ -4,7 +4,7 @@ import './styles/global.css'; export * from './utils/types'; // Export utility functions export * from './utils'; -// Export all UI components +// Export UI components export * from './components/ui'; // Export timeline components export * from './components/TimelineStep'; @@ -12,7 +12,7 @@ export * from './components/TimelineFeed'; // Export session navigation components export * from './components/SessionDrawer'; export * from './components/SessionSidebar'; -// Export the main screen component +// Export main screens export * from './components/DefaultAgentScreen'; // Export the hello function (temporary example) export const hello = () => { diff --git a/frontend/common/dist/styles/global.css b/frontend/common/dist/styles/global.css index 3b8e0f9..836541c 100644 --- a/frontend/common/dist/styles/global.css +++ b/frontend/common/dist/styles/global.css @@ -917,6 +917,9 @@ video { .border-b { border-bottom-width: 1px; } +.border-b-2 { + border-bottom-width: 2px; +} .border-l { border-left-width: 1px; } @@ -929,6 +932,10 @@ video { .border-dashed { border-style: dashed; } +.border-blue-300 { + --tw-border-opacity: 1; + border-color: rgb(147 197 253 / var(--tw-border-opacity, 1)); +} .border-border { border-color: hsl(var(--border)); } @@ -1021,6 +1028,14 @@ video { --tw-gradient-to: rgb(96 165 250 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); } +.from-purple-100 { + --tw-gradient-from: #f3e8ff var(--tw-gradient-from-position); + --tw-gradient-to: rgb(243 232 255 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} +.to-blue-100 { + --tw-gradient-to: #dbeafe var(--tw-gradient-to-position); +} .to-purple-500 { --tw-gradient-to: #a855f7 var(--tw-gradient-to-position); } @@ -1483,6 +1498,10 @@ video { .data-\[state\=open\]\:duration-500[data-state="open"] { animation-duration: 500ms; } +.dark\:border-blue-700:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(29 78 216 / var(--tw-border-opacity, 1)); +} .dark\:border-gray-800:is(.dark *) { --tw-border-opacity: 1; border-color: rgb(31 41 55 / var(--tw-border-opacity, 1)); @@ -1491,6 +1510,14 @@ video { --tw-border-opacity: 1; border-color: rgb(82 82 91 / var(--tw-border-opacity, 1)); } +.dark\:from-purple-900:is(.dark *) { + --tw-gradient-from: #581c87 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(88 28 135 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} +.dark\:to-blue-900:is(.dark *) { + --tw-gradient-to: #1e3a8a var(--tw-gradient-to-position); +} @media (min-width: 640px) { .sm\:max-w-md { diff --git a/frontend/common/package.json b/frontend/common/package.json index ff12aac..fb6e158 100644 --- a/frontend/common/package.json +++ b/frontend/common/package.json @@ -6,7 +6,9 @@ "types": "src/index.ts", "scripts": { "build": "tsc && postcss src/styles/global.css -o dist/styles/global.css", - "dev": "concurrently \"tsc --watch\" \"postcss src/styles/global.css -o dist/styles/global.css --watch\"", + "dev": "tsc --watch", + "watch:css": "postcss src/styles/global.css -o dist/styles/global.css --watch", + "watch": "concurrently \"npm run dev\" \"npm run watch:css\"", "prepare": "npm run build" }, "dependencies": { diff --git a/frontend/common/src/components/DefaultAgentScreen.tsx b/frontend/common/src/components/DefaultAgentScreen.tsx index fb34934..4123d03 100644 --- a/frontend/common/src/components/DefaultAgentScreen.tsx +++ b/frontend/common/src/components/DefaultAgentScreen.tsx @@ -89,7 +89,7 @@ export const DefaultAgentScreen: React.FC = () => { const headerContent = (

- RA-Aid + RA.Aid

diff --git a/frontend/common/src/index.ts b/frontend/common/src/index.ts index 6a63b0c..19ff8ab 100644 --- a/frontend/common/src/index.ts +++ b/frontend/common/src/index.ts @@ -7,7 +7,7 @@ export * from './utils/types'; // Export utility functions export * from './utils'; -// Export all UI components +// Export UI components export * from './components/ui'; // Export timeline components @@ -18,6 +18,9 @@ export * from './components/TimelineFeed'; export * from './components/SessionDrawer'; export * from './components/SessionSidebar'; +// Export main screens +export * from './components/DefaultAgentScreen'; + // Export the hello function (temporary example) export const hello = (): void => { console.log("Hello from @ra-aid/common"); diff --git a/frontend/web/vite.config.js b/frontend/web/vite.config.js index 082859f..dc00518 100644 --- a/frontend/web/vite.config.js +++ b/frontend/web/vite.config.js @@ -1,46 +1,40 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; +import fs from 'fs'; + +// Get all component files from common package +const commonSrcDir = path.resolve(__dirname, '../common/src'); export default defineConfig({ - plugins: [ - react(), - ], + plugins: [react()], resolve: { - // Point to the source files instead of dist for development alias: { + // Direct alias to the source directory '@ra-aid/common': path.resolve(__dirname, '../common/src') - } + }, + preserveSymlinks: true }, optimizeDeps: { - // Force Vite to include these dependencies in its optimization - include: ['@ra-aid/common'], - // Tell Vite to respect our aliased packages instead of using node_modules for them - esbuildOptions: { - preserveSymlinks: true, - } + // Exclude the common package from optimization so it can trigger hot reload + exclude: ['@ra-aid/common'] }, server: { - hmr: { - // More verbose logging for HMR - overlay: true, - }, + hmr: true, watch: { - // Watch for changes in the common package - paths: ['../common/src/**'], - // Ensure changes in source files trigger a reload usePolling: true, + interval: 100, + // Make sure to explicitly NOT ignore the common package + ignored: [ + '**/node_modules/**', + '**/dist/**', + '!**/common/src/**' + ] } }, - css: { - // PostCSS configuration is loaded from postcss.config.js - // This ensures proper processing of Tailwind directives - devSourcemap: true, - }, build: { - // When building for production, we need to make sure the common package is built too commonjsOptions: { - transformMixedEsModules: true, - }, + transformMixedEsModules: true + } } }); \ No newline at end of file