diff --git a/src/renderer/components/SettingsPanel.tsx b/src/renderer/components/SettingsPanel.tsx index 2716d23..c6b4144 100644 --- a/src/renderer/components/SettingsPanel.tsx +++ b/src/renderer/components/SettingsPanel.tsx @@ -1,5 +1,5 @@ import { useState, type HTMLAttributes, type ReactNode } from 'react'; -import { ChevronLeft, ChevronRight, Code, Cpu, Info, Plus, Server, Workflow } from 'lucide-react'; +import { AlertCircle, ChevronLeft, ChevronRight, Code, Cpu, Info, Plus, Server, Trash, Workflow } from 'lucide-react'; import { CopilotStatusCard } from '@renderer/components/CopilotStatusCard'; import { PatternEditor } from '@renderer/components/PatternEditor'; @@ -436,93 +436,108 @@ function McpServerEditor({ return ( -
- - onChange(updateMcpServer(server, { name: value }))} - value={server.name} - /> - - - onChange(changeMcpTransport(server, value as McpServerDefinition['transport']))} - options={[ - { value: 'local', label: 'Local process' }, - { value: 'http', label: 'HTTP' }, - { value: 'sse', label: 'SSE' }, - ]} - value={server.transport} - /> - -
- - {server.transport === 'local' ? ( -
- +
+

+ General +

+
+ onChange(updateMcpServer(server, { command: value }))} - placeholder="node" - value={server.command} + onChange={(value) => onChange(updateMcpServer(server, { name: value }))} + value={server.name} /> - - onChange(updateMcpServer(server, { args: splitMultiline(value) }))} - placeholder="One argument per line" - rows={4} - value={joinMultiline(server.args)} - /> - - - onChange(updateMcpServer(server, { cwd: value || undefined }))} - placeholder="Optional" - value={server.cwd ?? ''} + + onChange(changeMcpTransport(server, value as McpServerDefinition['transport']))} + options={[ + { value: 'local', label: 'Local process' }, + { value: 'http', label: 'HTTP' }, + { value: 'sse', label: 'SSE' }, + ]} + value={server.transport} />
- ) : ( - - onChange(updateMcpServer(server, { url: value }))} - placeholder="https://example.com/mcp" - value={server.url} - /> - - )} +
-
- - onChange(updateMcpServer(server, { tools: splitTokens(value) }))} - placeholder="Use * for all tools, or list one tool per line" - rows={4} - value={joinMultiline(server.tools)} - /> - - - - onChange( - updateMcpServer(server, { - timeoutMs: value.trim() ? Number(value) : undefined, - }), - ) - } - placeholder="Optional" - value={server.timeoutMs?.toString() ?? ''} - /> - -
+
+

+ {server.transport === 'local' ? 'Process' : 'Endpoint'} +

+ {server.transport === 'local' ? ( + <> + + onChange(updateMcpServer(server, { command: value }))} + placeholder="node" + value={server.command} + /> + + + onChange(updateMcpServer(server, { args: splitMultiline(value) }))} + placeholder="One argument per line" + rows={3} + value={joinMultiline(server.args)} + /> + + + onChange(updateMcpServer(server, { cwd: value || undefined }))} + placeholder="Optional — defaults to project root" + value={server.cwd ?? ''} + /> + + + ) : ( + + onChange(updateMcpServer(server, { url: value }))} + placeholder="https://example.com/mcp" + value={server.url} + /> + + )} +
+ +
+

+ Advanced +

+
+ + onChange(updateMcpServer(server, { tools: splitTokens(value) }))} + placeholder="* for all, or one per line" + rows={3} + value={joinMultiline(server.tools)} + /> + + + + onChange( + updateMcpServer(server, { + timeoutMs: value.trim() ? Number(value) : undefined, + }), + ) + } + placeholder="Optional" + value={server.timeoutMs?.toString() ?? ''} + /> + +
+
Keep secrets out of this form. Use commands or endpoints that authenticate through the OS or external tooling. @@ -548,59 +563,72 @@ function LspProfileEditor({ return ( -
- +
+

+ General +

+
+ + onChange(updateLspProfile(profile, { name: value }))} + value={profile.name} + /> + + + onChange(updateLspProfile(profile, { languageId: value }))} + placeholder="typescript" + value={profile.languageId} + /> + +
+
+ +
+

+ Server +

+ onChange(updateLspProfile(profile, { name: value }))} - value={profile.name} + onChange={(value) => onChange(updateLspProfile(profile, { command: value }))} + placeholder="typescript-language-server" + value={profile.command} /> - - onChange(updateLspProfile(profile, { languageId: value }))} - placeholder="typescript" - value={profile.languageId} - /> - -
- - - onChange(updateLspProfile(profile, { command: value }))} - placeholder="typescript-language-server" - value={profile.command} - /> - - -
onChange(updateLspProfile(profile, { args: splitMultiline(value) }))} placeholder="One argument per line" - rows={4} + rows={3} value={joinMultiline(profile.args)} /> + + +
+

+ File matching +

onChange(updateLspProfile(profile, { fileExtensions: splitTokens(value) }))} placeholder={'.ts\n.tsx'} - rows={4} + rows={3} value={joinMultiline(profile.fileExtensions)} /> -
+ - Profiles are global definitions only. Project root resolution still comes from the active session's project. + Project root resolution comes from the active session's project, not from this definition.
); @@ -680,7 +708,7 @@ function EmptyState({ children }: { children: ReactNode }) { function ToolingEditorShell({ title, - description, + subtitle, error, disableSave, onBack, @@ -689,7 +717,7 @@ function ToolingEditorShell({ children, }: { title: string; - description: string; + subtitle: string; error?: string; disableSave: boolean; onBack: () => void; @@ -699,7 +727,7 @@ function ToolingEditorShell({ }) { return (
-
+
{onDelete && ( )}
-
-
+
+
{error && ( -
+
+ {error}
)} @@ -751,19 +781,17 @@ function ToolingEditorShell({ function FormField({ label, required, - className, children, }: { label: string; required?: boolean; - className?: string; children: ReactNode; }) { return ( -