Normalise styles
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 */}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user