mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-30 22:47:13 +02:00
refactor: polish MCP/LSP settings and activity panel UI
- Use indigo accent consistently instead of blue for toggle states - Replace bulky toggle cards with compact toggle switches in Activity panel - Fix input focus states to use app-standard indigo glow - Use semantic nav icons: Server for MCP, Code for LSP - Style Save button with indigo primary, Delete with subtle destructive hover - Add Info icon to callout hints - Center empty states, clean up list item detail text - Fix indentation in App.tsx settings overlay wiring Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -269,11 +269,11 @@ export default function App() {
|
|||||||
onNewPattern={() => {
|
onNewPattern={() => {
|
||||||
const defaultModel = availableModels[0] ?? findModel('gpt-5.4', availableModels) ?? findModel('gpt-5.4');
|
const defaultModel = availableModels[0] ?? findModel('gpt-5.4', availableModels) ?? findModel('gpt-5.4');
|
||||||
|
|
||||||
return createDraftPattern(
|
return createDraftPattern(
|
||||||
defaultModel?.id ?? 'gpt-5.4',
|
defaultModel?.id ?? 'gpt-5.4',
|
||||||
resolveReasoningEffort(defaultModel, 'high'),
|
resolveReasoningEffort(defaultModel, 'high'),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onRefreshCapabilities={refreshCapabilities}
|
onRefreshCapabilities={refreshCapabilities}
|
||||||
onSaveLspProfile={async (profile) => {
|
onSaveLspProfile={async (profile) => {
|
||||||
await api.saveLspProfile({ profile });
|
await api.saveLspProfile({ profile });
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, type ReactNode } from 'react';
|
||||||
import { Activity, Bot, Sparkles } from 'lucide-react';
|
import { Activity, Bot, Server, Code, Sparkles } from 'lucide-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildAgentActivityRows,
|
buildAgentActivityRows,
|
||||||
@@ -80,66 +80,60 @@ export function ActivityPanel({
|
|||||||
{/* Agent cards */}
|
{/* Agent cards */}
|
||||||
<div className="flex-1 overflow-y-auto px-3 py-3">
|
<div className="flex-1 overflow-y-auto px-3 py-3">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40 px-3 py-3">
|
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40 px-3 py-2.5">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div>
|
<h3 className="text-[11px] font-semibold uppercase tracking-[0.12em] text-zinc-500">
|
||||||
<h3 className="text-[12px] font-semibold text-zinc-200">Session tools</h3>
|
Session tools
|
||||||
<p className="mt-0.5 text-[11px] text-zinc-500">
|
</h3>
|
||||||
Enable globally configured MCPs and LSPs for this session.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{toolsDisabled && (
|
{toolsDisabled && (
|
||||||
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium text-zinc-400">
|
<span className="text-[10px] text-zinc-600">
|
||||||
{projectIsScratchpad ? 'Scratchpad disabled' : 'Locked while running'}
|
{projectIsScratchpad ? 'Scratchpad' : 'Running'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{projectIsScratchpad ? (
|
{projectIsScratchpad ? (
|
||||||
<p className="mt-3 text-[11px] leading-relaxed text-zinc-500">
|
<p className="mt-2 text-[11px] leading-relaxed text-zinc-600">
|
||||||
Scratchpad stays tool-free. Start a project-backed session to use MCPs or LSPs.
|
Start a project-backed session to use MCPs or LSPs.
|
||||||
|
</p>
|
||||||
|
) : mcpServers.length === 0 && lspProfiles.length === 0 ? (
|
||||||
|
<p className="mt-2 text-[11px] leading-relaxed text-zinc-600">
|
||||||
|
Add MCP servers or LSP profiles in Settings to enable them here.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-3 space-y-3">
|
<div className="mt-2 space-y-0.5">
|
||||||
<ToolToggleGroup
|
{mcpServers.map((server) => (
|
||||||
description="Globally configured MCP servers"
|
<ToolToggleRow
|
||||||
emptyMessage="No MCP servers configured in Settings."
|
detail={server.transport === 'local' ? server.command : server.url}
|
||||||
enabledIds={selection.enabledMcpServerIds}
|
disabled={toolsDisabled}
|
||||||
items={mcpServers.map((server) => ({
|
enabled={selection.enabledMcpServerIds.includes(server.id)}
|
||||||
id: server.id,
|
icon={<Server className="size-3 text-zinc-600" />}
|
||||||
label: server.name,
|
key={server.id}
|
||||||
detail:
|
label={server.name}
|
||||||
server.transport === 'local'
|
onToggle={() =>
|
||||||
? server.command
|
onUpdateSessionTooling({
|
||||||
: server.url,
|
...selection,
|
||||||
}))}
|
enabledMcpServerIds: toggleId(selection.enabledMcpServerIds, server.id),
|
||||||
onToggle={(id) =>
|
})
|
||||||
onUpdateSessionTooling({
|
}
|
||||||
...selection,
|
/>
|
||||||
enabledMcpServerIds: toggleId(selection.enabledMcpServerIds, id),
|
))}
|
||||||
})
|
{lspProfiles.map((profile) => (
|
||||||
}
|
<ToolToggleRow
|
||||||
title="MCP servers"
|
detail={profile.command}
|
||||||
disabled={toolsDisabled}
|
disabled={toolsDisabled}
|
||||||
/>
|
enabled={selection.enabledLspProfileIds.includes(profile.id)}
|
||||||
<ToolToggleGroup
|
icon={<Code className="size-3 text-zinc-600" />}
|
||||||
description="Globally configured LSP profiles"
|
key={profile.id}
|
||||||
emptyMessage="No LSP profiles configured in Settings."
|
label={profile.name}
|
||||||
enabledIds={selection.enabledLspProfileIds}
|
onToggle={() =>
|
||||||
items={lspProfiles.map((profile) => ({
|
onUpdateSessionTooling({
|
||||||
id: profile.id,
|
...selection,
|
||||||
label: profile.name,
|
enabledLspProfileIds: toggleId(selection.enabledLspProfileIds, profile.id),
|
||||||
detail: `${profile.languageId} · ${profile.command}`,
|
})
|
||||||
}))}
|
}
|
||||||
onToggle={(id) =>
|
/>
|
||||||
onUpdateSessionTooling({
|
))}
|
||||||
...selection,
|
|
||||||
enabledLspProfileIds: toggleId(selection.enabledLspProfileIds, id),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
title="LSP profiles"
|
|
||||||
disabled={toolsDisabled}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -235,71 +229,55 @@ export function ActivityPanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolToggleGroup({
|
function ToolToggleRow({
|
||||||
title,
|
label,
|
||||||
description,
|
detail,
|
||||||
items,
|
icon,
|
||||||
enabledIds,
|
enabled,
|
||||||
onToggle,
|
|
||||||
emptyMessage,
|
|
||||||
disabled,
|
disabled,
|
||||||
|
onToggle,
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
label: string;
|
||||||
description: string;
|
detail?: string;
|
||||||
items: Array<{ id: string; label: string; detail?: string }>;
|
icon: ReactNode;
|
||||||
enabledIds: string[];
|
enabled: boolean;
|
||||||
onToggle: (id: string) => void;
|
|
||||||
emptyMessage: string;
|
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
onToggle: () => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<button
|
||||||
<div className="mb-2">
|
className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition ${
|
||||||
<h4 className="text-[11px] font-semibold uppercase tracking-[0.12em] text-zinc-500">
|
disabled ? 'cursor-not-allowed opacity-50' : 'hover:bg-zinc-800/60'
|
||||||
{title}
|
}`}
|
||||||
</h4>
|
disabled={disabled}
|
||||||
<p className="mt-0.5 text-[11px] text-zinc-600">{description}</p>
|
onClick={onToggle}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="truncate text-[12px] font-medium text-zinc-300">{label}</div>
|
||||||
|
{detail && (
|
||||||
|
<div className="truncate text-[10px] text-zinc-600">{detail}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<ToggleSwitch enabled={enabled} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{items.length === 0 ? (
|
function ToggleSwitch({ enabled }: { enabled: boolean }) {
|
||||||
<p className="text-[11px] text-zinc-600">{emptyMessage}</p>
|
return (
|
||||||
) : (
|
<span
|
||||||
<div className="space-y-1.5">
|
className={`relative inline-flex h-[16px] w-[28px] shrink-0 items-center rounded-full transition-colors ${
|
||||||
{items.map((item) => {
|
enabled ? 'bg-indigo-500' : 'bg-zinc-700'
|
||||||
const enabled = enabledIds.includes(item.id);
|
}`}
|
||||||
return (
|
>
|
||||||
<button
|
<span
|
||||||
className={`flex w-full items-center justify-between gap-3 rounded-lg border px-3 py-2 text-left transition ${
|
className={`inline-block size-[12px] rounded-full bg-white shadow-sm transition-transform ${
|
||||||
enabled
|
enabled ? 'translate-x-[14px]' : 'translate-x-[2px]'
|
||||||
? 'border-blue-500/30 bg-blue-500/5'
|
}`}
|
||||||
: 'border-zinc-800 bg-zinc-900/30'
|
/>
|
||||||
} ${disabled ? 'cursor-not-allowed opacity-60' : 'hover:border-zinc-700 hover:bg-zinc-900/60'}`}
|
</span>
|
||||||
disabled={disabled}
|
|
||||||
key={item.id}
|
|
||||||
onClick={() => onToggle(item.id)}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<div className="min-w-0">
|
|
||||||
<div className="text-[12px] font-medium text-zinc-200">{item.label}</div>
|
|
||||||
{item.detail && (
|
|
||||||
<div className="truncate text-[11px] text-zinc-500">{item.detail}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${
|
|
||||||
enabled
|
|
||||||
? 'bg-blue-500/10 text-blue-300'
|
|
||||||
: 'bg-zinc-800 text-zinc-500'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{enabled ? 'Enabled' : 'Disabled'}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, type HTMLAttributes, type ReactNode } from 'react';
|
import { useState, type HTMLAttributes, type ReactNode } from 'react';
|
||||||
import { ChevronLeft, ChevronRight, Cpu, Layers, Plus, Workflow } from 'lucide-react';
|
import { ChevronLeft, ChevronRight, Code, Cpu, Info, Plus, Server, Workflow } from 'lucide-react';
|
||||||
|
|
||||||
import { CopilotStatusCard } from '@renderer/components/CopilotStatusCard';
|
import { CopilotStatusCard } from '@renderer/components/CopilotStatusCard';
|
||||||
import { PatternEditor } from '@renderer/components/PatternEditor';
|
import { PatternEditor } from '@renderer/components/PatternEditor';
|
||||||
@@ -65,8 +65,8 @@ const navGroups: NavGroup[] = [
|
|||||||
{
|
{
|
||||||
label: 'Tooling',
|
label: 'Tooling',
|
||||||
items: [
|
items: [
|
||||||
{ id: 'mcp-servers', label: 'MCP Servers', icon: <Layers className="size-3.5" /> },
|
{ id: 'mcp-servers', label: 'MCP Servers', icon: <Server className="size-3.5" /> },
|
||||||
{ id: 'lsp-profiles', label: 'LSP Profiles', icon: <Cpu className="size-3.5" /> },
|
{ id: 'lsp-profiles', label: 'LSP Profiles', icon: <Code className="size-3.5" /> },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -367,8 +367,8 @@ function McpServersSection({
|
|||||||
<ToolingListButton
|
<ToolingListButton
|
||||||
detail={
|
detail={
|
||||||
server.transport === 'local'
|
server.transport === 'local'
|
||||||
? `${server.command || 'No command'} · ${server.tools.length} tool filter${server.tools.length === 1 ? '' : 's'}`
|
? server.command || 'No command set'
|
||||||
: `${server.url} · ${server.transport.toUpperCase()}`
|
: server.url || 'No URL set'
|
||||||
}
|
}
|
||||||
key={server.id}
|
key={server.id}
|
||||||
label={server.name}
|
label={server.name}
|
||||||
@@ -407,10 +407,10 @@ function LspProfilesSection({
|
|||||||
)}
|
)}
|
||||||
{profiles.map((profile) => (
|
{profiles.map((profile) => (
|
||||||
<ToolingListButton
|
<ToolingListButton
|
||||||
detail={`${profile.languageId} · ${profile.command || 'No command'}`}
|
detail={profile.command || 'No command set'}
|
||||||
key={profile.id}
|
key={profile.id}
|
||||||
label={profile.name}
|
label={profile.name}
|
||||||
meta={profile.fileExtensions.join(', ')}
|
meta={profile.languageId}
|
||||||
onClick={() => onEditProfile(profile)}
|
onClick={() => onEditProfile(profile)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -672,7 +672,7 @@ function ToolingListButton({
|
|||||||
|
|
||||||
function EmptyState({ children }: { children: ReactNode }) {
|
function EmptyState({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-dashed border-zinc-800 bg-zinc-900/30 px-4 py-6 text-[12px] leading-relaxed text-zinc-500">
|
<div className="rounded-xl border border-dashed border-zinc-800 bg-zinc-900/20 px-5 py-8 text-center text-[12px] leading-relaxed text-zinc-500">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -716,7 +716,7 @@ function ToolingEditorShell({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{onDelete && (
|
{onDelete && (
|
||||||
<button
|
<button
|
||||||
className="rounded-lg border border-zinc-700 px-3 py-1.5 text-[13px] font-medium text-zinc-300 transition hover:border-red-500/40 hover:bg-red-500/10 hover:text-red-300"
|
className="rounded-lg border border-zinc-800 px-3 py-1.5 text-[13px] font-medium text-zinc-400 transition hover:border-red-500/30 hover:bg-red-500/10 hover:text-red-300"
|
||||||
onClick={() => void onDelete()}
|
onClick={() => void onDelete()}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -724,7 +724,7 @@ function ToolingEditorShell({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="rounded-lg bg-zinc-100 px-3 py-1.5 text-[13px] font-semibold text-zinc-900 transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-50"
|
className="rounded-lg bg-indigo-600 px-3 py-1.5 text-[13px] font-semibold text-white transition hover:bg-indigo-500 disabled:cursor-not-allowed disabled:opacity-40"
|
||||||
disabled={disableSave}
|
disabled={disableSave}
|
||||||
onClick={() => void onSave()}
|
onClick={() => void onSave()}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -783,7 +783,7 @@ function TextInput({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
className="w-full rounded-lg border border-zinc-800 bg-zinc-950 px-3 py-2 text-[13px] text-zinc-100 outline-none transition placeholder:text-zinc-600 focus:border-zinc-600"
|
className="w-full rounded-lg border border-zinc-800 bg-zinc-950 px-3 py-2 text-[13px] text-zinc-100 outline-none transition placeholder:text-zinc-600 focus:border-indigo-500/50"
|
||||||
inputMode={inputMode}
|
inputMode={inputMode}
|
||||||
onChange={(event) => onChange(event.target.value)}
|
onChange={(event) => onChange(event.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
@@ -805,7 +805,7 @@ function TextareaInput({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full rounded-lg border border-zinc-800 bg-zinc-950 px-3 py-2 text-[13px] text-zinc-100 outline-none transition placeholder:text-zinc-600 focus:border-zinc-600"
|
className="w-full rounded-lg border border-zinc-800 bg-zinc-950 px-3 py-2 text-[13px] text-zinc-100 outline-none transition placeholder:text-zinc-600 focus:border-indigo-500/50"
|
||||||
onChange={(event) => onChange(event.target.value)}
|
onChange={(event) => onChange(event.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
@@ -825,7 +825,7 @@ function SelectInput({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
className="w-full rounded-lg border border-zinc-800 bg-zinc-950 px-3 py-2 text-[13px] text-zinc-100 outline-none transition focus:border-zinc-600"
|
className="w-full rounded-lg border border-zinc-800 bg-zinc-950 px-3 py-2 text-[13px] text-zinc-100 outline-none transition focus:border-indigo-500/50"
|
||||||
onChange={(event) => onChange(event.target.value)}
|
onChange={(event) => onChange(event.target.value)}
|
||||||
value={value}
|
value={value}
|
||||||
>
|
>
|
||||||
@@ -840,8 +840,9 @@ function SelectInput({
|
|||||||
|
|
||||||
function InfoCallout({ children }: { children: ReactNode }) {
|
function InfoCallout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-zinc-800 bg-zinc-900/30 px-4 py-3 text-[12px] leading-relaxed text-zinc-500">
|
<div className="flex items-start gap-2.5 rounded-xl border border-zinc-800 bg-zinc-900/30 px-4 py-3 text-[12px] leading-relaxed text-zinc-500">
|
||||||
{children}
|
<Info className="mt-0.5 size-3.5 shrink-0 text-zinc-600" />
|
||||||
|
<span>{children}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user