Normalise styles

This commit is contained in:
Louis Knight-Webb
2025-06-21 22:33:33 +01:00
parent 290d128220
commit 6031904a07
7 changed files with 63 additions and 128 deletions

View File

@@ -8,6 +8,7 @@ import { TaskAttemptComparePage } from "@/pages/task-attempt-compare";
import { Settings } from "@/pages/Settings"; import { Settings } from "@/pages/Settings";
import { DisclaimerDialog } from "@/components/DisclaimerDialog"; import { DisclaimerDialog } from "@/components/DisclaimerDialog";
import { ConfigProvider, useConfig } from "@/components/config-provider"; import { ConfigProvider, useConfig } from "@/components/config-provider";
import { ThemeProvider } from "@/components/theme-provider";
import type { Config, ApiResponse } from "shared/types"; import type { Config, ApiResponse } from "shared/types";
function AppContent() { function AppContent() {
@@ -57,30 +58,32 @@ function AppContent() {
} }
return ( return (
<div className="h-screen flex flex-col bg-background"> <ThemeProvider initialTheme={config?.theme || "system"}>
<DisclaimerDialog <div className="h-screen flex flex-col bg-background">
open={showDisclaimer} <DisclaimerDialog
onAccept={handleDisclaimerAccept} open={showDisclaimer}
/> onAccept={handleDisclaimerAccept}
{showNavbar && <Navbar />} />
<div className="flex-1 overflow-y-scroll"> {showNavbar && <Navbar />}
<Routes> <div className="flex-1 overflow-y-scroll">
<Route path="/" element={<Projects />} /> <Routes>
<Route path="/projects" element={<Projects />} /> <Route path="/" element={<Projects />} />
<Route path="/projects/:projectId" element={<Projects />} /> <Route path="/projects" element={<Projects />} />
<Route path="/projects/:projectId/tasks" element={<ProjectTasks />} /> <Route path="/projects/:projectId" element={<Projects />} />
<Route <Route path="/projects/:projectId/tasks" element={<ProjectTasks />} />
path="/projects/:projectId/tasks/:taskId" <Route
element={<TaskDetailsPage />} path="/projects/:projectId/tasks/:taskId"
/> element={<TaskDetailsPage />}
<Route />
path="/projects/:projectId/tasks/:taskId/attempts/:attemptId/compare" <Route
element={<TaskAttemptComparePage />} path="/projects/:projectId/tasks/:taskId/attempts/:attemptId/compare"
/> element={<TaskAttemptComparePage />}
<Route path="/settings" element={<Settings />} /> />
</Routes> <Route path="/settings" element={<Settings />} />
</Routes>
</div>
</div> </div>
</div> </ThemeProvider>
); );
} }

View File

@@ -4,7 +4,6 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { import {
PersonStanding,
Brain, Brain,
Wrench as Tool, Wrench as Tool,
ChevronDown, ChevronDown,
@@ -408,11 +407,11 @@ export function ConversationViewer({ jsonlOutput }: ConversationViewerProps) {
return ( return (
<Card <Card
key={`error-${index}`} key={`error-${index}`}
className="bg-yellow-50 border-yellow-200" className="bg-yellow-100/50 dark:bg-yellow-900/20 border"
> >
<CardContent className="p-3"> <CardContent className="p-3">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<AlertTriangle className="h-4 w-4 text-yellow-600" /> <AlertTriangle className="h-4 w-4 text-yellow-600 dark:text-yellow-400" />
<Badge variant="secondary" className="text-xs"> <Badge variant="secondary" className="text-xs">
Parse Error Parse Error
</Badge> </Badge>
@@ -421,7 +420,7 @@ export function ConversationViewer({ jsonlOutput }: ConversationViewerProps) {
<p className="text-xs text-muted-foreground mb-1"> <p className="text-xs text-muted-foreground mb-1">
Raw JSONL: Raw JSONL:
</p> </p>
<pre className="text-xs bg-white p-2 rounded border overflow-x-auto whitespace-pre-wrap"> <pre className="text-xs bg-background p-2 rounded border overflow-x-auto whitespace-pre-wrap">
{safeRenderString(item.rawLine)} {safeRenderString(item.rawLine)}
</pre> </pre>
</div> </div>
@@ -443,20 +442,17 @@ export function ConversationViewer({ jsonlOutput }: ConversationViewerProps) {
} }
return ( return (
<Card <Card key={`unknown-${index}`} className="bg-muted/30 border">
key={`unknown-${index}`}
className="bg-gray-50 border-gray-200"
>
<CardContent className="p-3"> <CardContent className="p-3">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<FileText className="h-4 w-4 text-gray-600" /> <FileText className="h-4 w-4 text-muted-foreground" />
<Badge variant="secondary" className="text-xs"> <Badge variant="secondary" className="text-xs">
Unknown Unknown
</Badge> </Badge>
</div> </div>
<div> <div>
<p className="text-xs text-muted-foreground mb-1">JSONL:</p> <p className="text-xs text-muted-foreground mb-1">JSONL:</p>
<pre className="text-xs bg-white p-2 rounded border overflow-x-auto whitespace-pre-wrap"> <pre className="text-xs bg-background p-2 rounded border overflow-x-auto whitespace-pre-wrap">
{safeRenderString(prettyJson)} {safeRenderString(prettyJson)}
</pre> </pre>
</div> </div>
@@ -469,11 +465,11 @@ export function ConversationViewer({ jsonlOutput }: ConversationViewerProps) {
return ( return (
<Card <Card
key={`rejection-${index}`} key={`rejection-${index}`}
className="bg-red-50 border-red-200" className="bg-red-100/50 dark:bg-red-900/20 border"
> >
<CardContent className="p-3"> <CardContent className="p-3">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<AlertTriangle className="h-4 w-4 text-red-600" /> <AlertTriangle className="h-4 w-4 text-red-600 dark:text-red-400" />
<Badge variant="secondary" className="text-xs"> <Badge variant="secondary" className="text-xs">
Tool Rejected Tool Rejected
</Badge> </Badge>
@@ -486,7 +482,7 @@ export function ConversationViewer({ jsonlOutput }: ConversationViewerProps) {
<p className="text-xs text-muted-foreground mb-1"> <p className="text-xs text-muted-foreground mb-1">
Command: Command:
</p> </p>
<pre className="text-xs bg-white p-2 rounded border overflow-x-auto"> <pre className="text-xs bg-background p-2 rounded border overflow-x-auto">
{safeRenderString(item.command)} {safeRenderString(item.command)}
</pre> </pre>
</div> </div>
@@ -494,7 +490,7 @@ export function ConversationViewer({ jsonlOutput }: ConversationViewerProps) {
<p className="text-xs text-muted-foreground mb-1"> <p className="text-xs text-muted-foreground mb-1">
Message: Message:
</p> </p>
<p className="text-xs bg-white p-2 rounded border"> <p className="text-xs bg-background p-2 rounded border">
{safeRenderString(item.message)} {safeRenderString(item.message)}
</p> </p>
</div> </div>
@@ -516,8 +512,8 @@ export function ConversationViewer({ jsonlOutput }: ConversationViewerProps) {
key={messageId} key={messageId}
className={`${ className={`${
item.role === "user" item.role === "user"
? "bg-blue-50 border-blue-200 ml-12" ? "bg-blue-100/50 dark:bg-blue-900/20 border ml-12"
: "bg-gray-50 border-gray-200 mr-12" : "bg-muted/50 border mr-12"
}`} }`}
> >
<CardContent className="p-4"> <CardContent className="p-4">
@@ -587,7 +583,7 @@ export function ConversationViewer({ jsonlOutput }: ConversationViewerProps) {
return ( return (
<div key={contentIndex} className="mt-3"> <div key={contentIndex} className="mt-3">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<Tool className="h-4 w-4 text-green-600" /> <Tool className="h-4 w-4 text-green-600 dark:text-green-400" />
<span className="text-sm font-medium"> <span className="text-sm font-medium">
{safeRenderString(content.name)} {safeRenderString(content.name)}
</span> </span>

View File

@@ -62,7 +62,7 @@ export function ExecutionOutputViewer({
} }
return ( return (
<Card className="bg-muted border-none"> <Card className="">
<CardContent className="p-3"> <CardContent className="p-3">
<div className="space-y-3"> <div className="space-y-3">
{/* View mode toggle for Amp executor with valid JSONL */} {/* View mode toggle for Amp executor with valid JSONL */}

View File

@@ -1,45 +1,30 @@
import React, { createContext, useContext, useEffect, useState } from "react"; import React, { createContext, useContext, useEffect, useState } from "react";
import type { Config, ThemeMode, ApiResponse } from "shared/types"; import type { ThemeMode } from "shared/types";
type ThemeProviderProps = { type ThemeProviderProps = {
children: React.ReactNode; children: React.ReactNode;
initialTheme?: ThemeMode;
}; };
type ThemeProviderState = { type ThemeProviderState = {
theme: ThemeMode; theme: ThemeMode;
setTheme: (theme: ThemeMode) => void; setTheme: (theme: ThemeMode) => void;
loadThemeFromConfig: () => Promise<void>;
}; };
const initialState: ThemeProviderState = { const initialState: ThemeProviderState = {
theme: "system", theme: "system",
setTheme: () => null, setTheme: () => null,
loadThemeFromConfig: async () => {},
}; };
const ThemeProviderContext = createContext<ThemeProviderState>(initialState); const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
export function ThemeProvider({ children, ...props }: ThemeProviderProps) { export function ThemeProvider({ children, initialTheme = "system", ...props }: ThemeProviderProps) {
const [theme, setThemeState] = useState<ThemeMode>("system"); const [theme, setThemeState] = useState<ThemeMode>(initialTheme);
// Load theme from backend config // Update theme when initialTheme changes
const loadThemeFromConfig = async () => {
try {
const response = await fetch("/api/config");
const data: ApiResponse<Config> = await response.json();
if (data.success && data.data) {
setThemeState(data.data.theme);
}
} catch (err) {
console.error("Error loading theme from config:", err);
}
};
// Load theme on mount
useEffect(() => { useEffect(() => {
loadThemeFromConfig(); setThemeState(initialTheme);
}, []); }, [initialTheme]);
useEffect(() => { useEffect(() => {
const root = window.document.documentElement; const root = window.document.documentElement;
@@ -66,7 +51,6 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
const value = { const value = {
theme, theme,
setTheme, setTheme,
loadThemeFromConfig,
}; };
return ( return (

View File

@@ -3,13 +3,10 @@ import ReactDOM from "react-dom/client";
import App from "./App.tsx"; import App from "./App.tsx";
import "./index.css"; import "./index.css";
import { ClickToComponent } from "click-to-react-component"; import { ClickToComponent } from "click-to-react-component";
import { ThemeProvider } from "@/components/theme-provider";
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<ThemeProvider> <ClickToComponent />
<ClickToComponent /> <App />
<App />
</ThemeProvider>
</React.StrictMode> </React.StrictMode>
); );

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react"; import { useState } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -7,40 +7,17 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import type { Config, ThemeMode, EditorType, ApiResponse } from "shared/types"; import type { ThemeMode, EditorType } from "shared/types";
import { useTheme } from "@/components/theme-provider"; import { useTheme } from "@/components/theme-provider";
import { useConfig } from "@/components/config-provider";
export function Settings() { export function Settings() {
const [config, setConfig] = useState<Config | null>(null); const { config, updateConfig, saveConfig, loading } = useConfig();
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false); const [success, setSuccess] = useState(false);
const { setTheme } = useTheme(); const { setTheme } = useTheme();
// Load initial config
useEffect(() => {
const loadConfig = async () => {
try {
const response = await fetch("/api/config");
const data: ApiResponse<Config> = await response.json();
if (data.success && data.data) {
setConfig(data.data);
} else {
setError(data.message || "Failed to load configuration");
}
} catch (err) {
setError("Failed to load configuration");
console.error("Error loading config:", err);
} finally {
setLoading(false);
}
};
loadConfig();
}, []);
const handleSave = async () => { const handleSave = async () => {
if (!config) return; if (!config) return;
@@ -49,24 +26,16 @@ export function Settings() {
setSuccess(false); setSuccess(false);
try { try {
const response = await fetch("/api/config", { const success = await saveConfig();
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(config),
});
const data: ApiResponse<Config> = await response.json(); if (success) {
if (data.success) {
setSuccess(true); setSuccess(true);
// Update theme provider to reflect the saved theme // Update theme provider to reflect the saved theme
setTheme(config.theme); setTheme(config.theme);
setTimeout(() => setSuccess(false), 3000); setTimeout(() => setSuccess(false), 3000);
} else { } else {
setError(data.message || "Failed to save configuration"); setError("Failed to save configuration");
} }
} catch (err) { } catch (err) {
setError("Failed to save configuration"); setError("Failed to save configuration");
@@ -76,10 +45,6 @@ export function Settings() {
} }
}; };
const updateConfig = (updates: Partial<Config>) => {
setConfig((prev: Config | null) => prev ? { ...prev, ...updates } : null);
};
const resetDisclaimer = async () => { const resetDisclaimer = async () => {
if (!config) return; if (!config) return;

View File

@@ -15,6 +15,7 @@ import { ArrowLeft, FileText, Code /* , Monitor, Braces */ } from "lucide-react"
import { makeRequest } from "@/lib/api"; import { makeRequest } from "@/lib/api";
import { TaskFormDialog } from "@/components/tasks/TaskFormDialog"; import { TaskFormDialog } from "@/components/tasks/TaskFormDialog";
import { useKeyboardShortcuts } from "@/lib/keyboard-shortcuts"; import { useKeyboardShortcuts } from "@/lib/keyboard-shortcuts";
import { useConfig } from "@/components/config-provider";
import type { import type {
TaskStatus, TaskStatus,
TaskAttempt, TaskAttempt,
@@ -23,7 +24,6 @@ import type {
ExecutionProcess, ExecutionProcess,
ExecutionProcessStatus, ExecutionProcessStatus,
ExecutionProcessType, ExecutionProcessType,
Config,
ApiResponse, ApiResponse,
} from "shared/types"; } from "shared/types";
@@ -118,6 +118,7 @@ export function TaskDetailsPage() {
>([]); >([]);
const [executionProcessesLoading, setExecutionProcessesLoading] = useState(false); const [executionProcessesLoading, setExecutionProcessesLoading] = useState(false);
const [selectedExecutor, setSelectedExecutor] = useState<string>("claude"); const [selectedExecutor, setSelectedExecutor] = useState<string>("claude");
const { config } = useConfig();
const [creatingAttempt, setCreatingAttempt] = useState(false); const [creatingAttempt, setCreatingAttempt] = useState(false);
const [stoppingProcess, setStoppingProcess] = useState<string | null>(null); const [stoppingProcess, setStoppingProcess] = useState<string | null>(null);
const [openingEditor, setOpeningEditor] = useState(false); const [openingEditor, setOpeningEditor] = useState(false);
@@ -164,23 +165,12 @@ export function TaskDetailsPage() {
} }
}, [projectId, taskId]); }, [projectId, taskId]);
// Load config to get default executor // Set default executor from config
useEffect(() => { useEffect(() => {
const loadConfig = async () => { if (config) {
try { setSelectedExecutor(config.executor.type);
const response = await makeRequest("/api/config"); }
if (response.ok) { }, [config]);
const result: ApiResponse<Config> = await response.json();
if (result.success && result.data) {
setSelectedExecutor(result.data.executor.type);
}
}
} catch (err) {
console.error("Failed to load config:", err);
}
};
loadConfig();
}, []);
useEffect(() => { useEffect(() => {
if (task) { if (task) {