diff --git a/apps/x/apps/renderer/src/components/settings-dialog.tsx b/apps/x/apps/renderer/src/components/settings-dialog.tsx new file mode 100644 index 00000000..ac5a24bf --- /dev/null +++ b/apps/x/apps/renderer/src/components/settings-dialog.tsx @@ -0,0 +1,229 @@ +"use client" + +import * as React from "react" +import { useState, useEffect } from "react" +import { Server, Key, Shield } from "lucide-react" + +import { + Dialog, + DialogContent, + DialogTrigger, +} from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { cn } from "@/lib/utils" + +type ConfigTab = "models" | "mcp" | "security" + +interface TabConfig { + id: ConfigTab + label: string + icon: React.ElementType + path: string + description: string +} + +const tabs: TabConfig[] = [ + { + id: "models", + label: "Models", + icon: Key, + path: "config/models.json", + description: "Configure LLM providers and API keys", + }, + { + id: "mcp", + label: "MCP Servers", + icon: Server, + path: "config/mcp.json", + description: "Configure MCP server connections", + }, + { + id: "security", + label: "Security", + icon: Shield, + path: "config/security.json", + description: "Configure allowed shell commands", + }, +] + +interface SettingsDialogProps { + children: React.ReactNode +} + +export function SettingsDialog({ children }: SettingsDialogProps) { + const [open, setOpen] = useState(false) + const [activeTab, setActiveTab] = useState("models") + const [content, setContent] = useState("") + const [originalContent, setOriginalContent] = useState("") + const [loading, setLoading] = useState(false) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + + const activeTabConfig = tabs.find((t) => t.id === activeTab)! + + const loadConfig = async (tab: ConfigTab) => { + const tabConfig = tabs.find((t) => t.id === tab)! + setLoading(true) + setError(null) + try { + const result = await window.ipc.invoke("workspace:readFile", { + path: tabConfig.path, + }) + const formattedContent = formatJson(result.data) + setContent(formattedContent) + setOriginalContent(formattedContent) + } catch (err) { + setError(`Failed to load ${tabConfig.label} config`) + setContent("") + setOriginalContent("") + } finally { + setLoading(false) + } + } + + const saveConfig = async () => { + setSaving(true) + setError(null) + try { + // Validate JSON before saving + JSON.parse(content) + await window.ipc.invoke("workspace:writeFile", { + path: activeTabConfig.path, + data: content, + }) + setOriginalContent(content) + } catch (err) { + if (err instanceof SyntaxError) { + setError("Invalid JSON syntax") + } else { + setError(`Failed to save ${activeTabConfig.label} config`) + } + } finally { + setSaving(false) + } + } + + const formatJson = (jsonString: string): string => { + try { + return JSON.stringify(JSON.parse(jsonString), null, 2) + } catch { + return jsonString + } + } + + const handleFormat = () => { + setContent(formatJson(content)) + } + + const hasChanges = content !== originalContent + + useEffect(() => { + if (open) { + loadConfig(activeTab) + } + }, [open, activeTab]) + + const handleTabChange = (tab: ConfigTab) => { + if (hasChanges) { + if (!confirm("You have unsaved changes. Discard them?")) { + return + } + } + setActiveTab(tab) + } + + return ( + + {children} + +
+ {/* Sidebar */} +
+
+

Settings

+
+ +
+ + {/* Main content */} +
+ {/* Header */} +
+

{activeTabConfig.label}

+

+ {activeTabConfig.description} +

+
+ + {/* Editor */} +
+ {loading ? ( +
+ Loading... +
+ ) : ( +