diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5708f659..d9834355 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,52 +7,37 @@ import { TaskDetailsPage } from "@/pages/task-details"; import { TaskAttemptComparePage } from "@/pages/task-attempt-compare"; import { Settings } from "@/pages/Settings"; import { DisclaimerDialog } from "@/components/DisclaimerDialog"; +import { ConfigProvider, useConfig } from "@/components/config-provider"; import type { Config, ApiResponse } from "shared/types"; function AppContent() { - const [config, setConfig] = useState(null); - const [loading, setLoading] = useState(true); + const { config, updateConfig, loading } = useConfig(); const [showDisclaimer, setShowDisclaimer] = useState(false); const showNavbar = true; useEffect(() => { - const loadConfig = async () => { - try { - const response = await fetch("/api/config"); - const data: ApiResponse = await response.json(); - - if (data.success && data.data) { - setConfig(data.data); - setShowDisclaimer(!data.data.disclaimer_acknowledged); - } - } catch (err) { - console.error("Error loading config:", err); - } finally { - setLoading(false); - } - }; - - loadConfig(); - }, []); + if (config) { + setShowDisclaimer(!config.disclaimer_acknowledged); + } + }, [config]); const handleDisclaimerAccept = async () => { if (!config) return; - const updatedConfig = { ...config, disclaimer_acknowledged: true }; - + updateConfig({ disclaimer_acknowledged: true }); + try { const response = await fetch("/api/config", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify(updatedConfig), + body: JSON.stringify({ ...config, disclaimer_acknowledged: true }), }); const data: ApiResponse = await response.json(); if (data.success) { - setConfig(updatedConfig); setShowDisclaimer(false); } } catch (err) { @@ -102,7 +87,9 @@ function AppContent() { function App() { return ( - + + + ); } diff --git a/frontend/src/components/config-provider.tsx b/frontend/src/components/config-provider.tsx new file mode 100644 index 00000000..f66b5341 --- /dev/null +++ b/frontend/src/components/config-provider.tsx @@ -0,0 +1,77 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from "react"; +import type { Config, ApiResponse } from "shared/types"; + +interface ConfigContextType { + config: Config | null; + updateConfig: (updates: Partial) => void; + saveConfig: () => Promise; + loading: boolean; +} + +const ConfigContext = createContext(undefined); + +interface ConfigProviderProps { + children: ReactNode; +} + +export function ConfigProvider({ children }: ConfigProviderProps) { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const loadConfig = async () => { + try { + const response = await fetch("/api/config"); + const data: ApiResponse = await response.json(); + + if (data.success && data.data) { + setConfig(data.data); + } + } catch (err) { + console.error("Error loading config:", err); + } finally { + setLoading(false); + } + }; + + loadConfig(); + }, []); + + const updateConfig = (updates: Partial) => { + setConfig((prev) => prev ? { ...prev, ...updates } : null); + }; + + const saveConfig = async (): Promise => { + if (!config) return false; + + try { + const response = await fetch("/api/config", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(config), + }); + + const data: ApiResponse = await response.json(); + return data.success; + } catch (err) { + console.error("Error saving config:", err); + return false; + } + }; + + return ( + + {children} + + ); +} + +export function useConfig() { + const context = useContext(ConfigContext); + if (context === undefined) { + throw new Error("useConfig must be used within a ConfigProvider"); + } + return context; +} diff --git a/frontend/src/components/tasks/TaskDetailsPanel.tsx b/frontend/src/components/tasks/TaskDetailsPanel.tsx index fbf8ff2e..c2de47d5 100644 --- a/frontend/src/components/tasks/TaskDetailsPanel.tsx +++ b/frontend/src/components/tasks/TaskDetailsPanel.tsx @@ -29,6 +29,7 @@ import { getTaskPanelClasses, getBackdropClasses, } from "@/lib/responsive-config"; +import { useConfig } from "@/components/config-provider"; import type { TaskStatus, TaskAttempt, @@ -132,7 +133,8 @@ export function TaskDetailsPanel({ const [loading, setLoading] = useState(false); const [followUpMessage, setFollowUpMessage] = useState(""); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); - const [selectedExecutor, setSelectedExecutor] = useState("claude"); + const [selectedExecutor, setSelectedExecutor] = useState("claude"); + const { config } = useConfig(); // Available executors const availableExecutors = [ @@ -162,6 +164,13 @@ export function TaskDetailsPanel({ return () => clearInterval(interval); }, [isAttemptRunning, task?.id, selectedAttempt?.id]); + // Set default executor from config + useEffect(() => { + if (config) { + setSelectedExecutor(config.executor.type); + } + }, [config]); + useEffect(() => { if (task && isOpen) { fetchTaskAttempts();