mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-03-16 18:33:08 +00:00
ui: restructure header navbar into two-row responsive layout
Redesign the header from a single overflowing row into a clean two-row layout that prevents content from overlapping the logo and bleeding outside the navbar on smaller screens. Row 1: Logo + project selector + spacer + mode badges + utility icons Row 2: Agent controls + dev server + spacer + settings + reset (only rendered when a project is selected, with a subtle border divider) Changes: - App.tsx: Split header into two logical rows with flex spacers for right-alignment; hide title text below md breakpoint; move mode badges (Ollama/GLM) to row 1 with sm:hidden for small screens - ProjectSelector: Responsive min-width (140px mobile, 200px desktop); truncate long project names instead of pushing icons off-screen - AgentControl: Responsive gap (gap-2 mobile, gap-4 desktop) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
149
ui/src/App.tsx
149
ui/src/App.tsx
@@ -261,19 +261,19 @@ function App() {
|
|||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="sticky top-0 z-50 bg-card/80 backdrop-blur-md text-foreground border-b-2 border-border">
|
<header className="sticky top-0 z-50 bg-card/80 backdrop-blur-md text-foreground border-b-2 border-border">
|
||||||
<div className="max-w-7xl mx-auto px-4 py-4">
|
<div className="max-w-7xl mx-auto px-4 py-3">
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<div className="flex items-center justify-between">
|
{/* Row 1: Branding + Project + Utility icons */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
{/* Logo and Title */}
|
{/* Logo and Title */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
<img src="/logo.png" alt="AutoForge" className="h-9 w-9 rounded-full" />
|
<img src="/logo.png" alt="AutoForge" className="h-9 w-9 rounded-full" />
|
||||||
<h1 className="font-display text-2xl font-bold tracking-tight uppercase">
|
<h1 className="font-display text-2xl font-bold tracking-tight uppercase hidden md:block">
|
||||||
AutoForge
|
AutoForge
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Controls */}
|
{/* Project selector */}
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<ProjectSelector
|
<ProjectSelector
|
||||||
projects={projects ?? []}
|
projects={projects ?? []}
|
||||||
selectedProject={selectedProject}
|
selectedProject={selectedProject}
|
||||||
@@ -282,73 +282,31 @@ function App() {
|
|||||||
onSpecCreatingChange={setIsSpecCreating}
|
onSpecCreatingChange={setIsSpecCreating}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{selectedProject && (
|
{/* Spacer */}
|
||||||
<>
|
<div className="flex-1" />
|
||||||
<AgentControl
|
|
||||||
projectName={selectedProject}
|
|
||||||
status={wsState.agentStatus}
|
|
||||||
defaultConcurrency={selectedProjectData?.default_concurrency}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DevServerControl
|
{/* Ollama Mode Indicator */}
|
||||||
projectName={selectedProject}
|
{selectedProject && settings?.ollama_mode && (
|
||||||
status={wsState.devServerStatus}
|
<div
|
||||||
url={wsState.devServerUrl}
|
className="hidden sm:flex items-center gap-1.5 px-2 py-1 bg-card rounded border-2 border-border shadow-sm"
|
||||||
/>
|
title="Using Ollama local models"
|
||||||
|
>
|
||||||
<Tooltip>
|
<img src="/ollama.png" alt="Ollama" className="w-5 h-5" />
|
||||||
<TooltipTrigger asChild>
|
<span className="text-xs font-bold text-foreground">Ollama</span>
|
||||||
<Button
|
</div>
|
||||||
onClick={() => setShowSettings(true)}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
aria-label="Open Settings"
|
|
||||||
>
|
|
||||||
<Settings size={18} />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>Settings (,)</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
onClick={() => setShowResetModal(true)}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
aria-label="Reset Project"
|
|
||||||
disabled={wsState.agentStatus === 'running'}
|
|
||||||
>
|
|
||||||
<RotateCcw size={18} />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>Reset (R)</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* Ollama Mode Indicator */}
|
|
||||||
{settings?.ollama_mode && (
|
|
||||||
<div
|
|
||||||
className="flex items-center gap-1.5 px-2 py-1 bg-card rounded border-2 border-border shadow-sm"
|
|
||||||
title="Using Ollama local models"
|
|
||||||
>
|
|
||||||
<img src="/ollama.png" alt="Ollama" className="w-5 h-5" />
|
|
||||||
<span className="text-xs font-bold text-foreground">Ollama</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* GLM Mode Badge */}
|
|
||||||
{settings?.glm_mode && (
|
|
||||||
<Badge
|
|
||||||
className="bg-purple-500 text-white hover:bg-purple-600"
|
|
||||||
title="Using GLM API"
|
|
||||||
>
|
|
||||||
GLM
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Docs link */}
|
{/* GLM Mode Badge */}
|
||||||
|
{selectedProject && settings?.glm_mode && (
|
||||||
|
<Badge
|
||||||
|
className="hidden sm:inline-flex bg-purple-500 text-white hover:bg-purple-600"
|
||||||
|
title="Using GLM API"
|
||||||
|
>
|
||||||
|
GLM
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Utility icons - always visible */}
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -363,14 +321,12 @@ function App() {
|
|||||||
<TooltipContent>Docs</TooltipContent>
|
<TooltipContent>Docs</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* Theme selector */}
|
|
||||||
<ThemeSelector
|
<ThemeSelector
|
||||||
themes={themes}
|
themes={themes}
|
||||||
currentTheme={theme}
|
currentTheme={theme}
|
||||||
onThemeChange={setTheme}
|
onThemeChange={setTheme}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Dark mode toggle - always visible */}
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -384,8 +340,55 @@ function App() {
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Toggle theme</TooltipContent>
|
<TooltipContent>Toggle theme</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Row 2: Project controls - only when a project is selected */}
|
||||||
|
{selectedProject && (
|
||||||
|
<div className="flex items-center gap-3 mt-2 pt-2 border-t border-border/50">
|
||||||
|
<AgentControl
|
||||||
|
projectName={selectedProject}
|
||||||
|
status={wsState.agentStatus}
|
||||||
|
defaultConcurrency={selectedProjectData?.default_concurrency}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DevServerControl
|
||||||
|
projectName={selectedProject}
|
||||||
|
status={wsState.devServerStatus}
|
||||||
|
url={wsState.devServerUrl}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex-1" />
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowSettings(true)}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Open Settings"
|
||||||
|
>
|
||||||
|
<Settings size={18} />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Settings (,)</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowResetModal(true)}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Reset Project"
|
||||||
|
disabled={wsState.agentStatus === 'running'}
|
||||||
|
>
|
||||||
|
<RotateCcw size={18} />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Reset (R)</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function AgentControl({ projectName, status, defaultConcurrency = 3 }: Ag
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-2 sm:gap-4">
|
||||||
{/* Concurrency slider - visible when stopped */}
|
{/* Concurrency slider - visible when stopped */}
|
||||||
{isStopped && (
|
{isStopped && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -73,16 +73,16 @@ export function ProjectSelector({
|
|||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="min-w-[200px] justify-between"
|
className="min-w-[140px] sm:min-w-[200px] justify-between"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Loader2 size={18} className="animate-spin" />
|
<Loader2 size={18} className="animate-spin" />
|
||||||
) : selectedProject ? (
|
) : selectedProject ? (
|
||||||
<>
|
<>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2 truncate">
|
||||||
<FolderOpen size={18} />
|
<FolderOpen size={18} className="shrink-0" />
|
||||||
{selectedProject}
|
<span className="truncate">{selectedProject}</span>
|
||||||
</span>
|
</span>
|
||||||
{selectedProjectData && selectedProjectData.stats.total > 0 && (
|
{selectedProjectData && selectedProjectData.stats.total > 0 && (
|
||||||
<Badge className="ml-2">{selectedProjectData.stats.percentage}%</Badge>
|
<Badge className="ml-2">{selectedProjectData.stats.percentage}%</Badge>
|
||||||
|
|||||||
Reference in New Issue
Block a user