mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-28 23:48:39 +02:00
feat: Luminous Depth UI redesign
Complete visual overhaul of the Aryx application with the Luminous Depth design language — blue-tinted dark surfaces, brand gradient accents, glass-morphism effects, and refined typography. Design Foundation: - New color system with CSS custom properties for all surfaces, borders, text, accents, and status colors - Typography: Outfit (display), DM Sans (body), JetBrains Mono (code) - Animations: accent-flow, thinking-wave, ambient-glow - Utility classes: glass-surface, glow-border, brand-gradient-bg/text - Light theme with cool-tinted whites and blue-tinted shadows Components Updated: - UI primitives (TextInput, SelectInput, ToggleSwitch, FormField, etc.) - AppShell with ambient glow background - Sidebar with brand gradient selection and accent-flow running bars - ChatPane with semantic phase tinting and gradient user avatars - WelcomePane with nebula background and motion entrance animations - All chat banners (Approval, UserInput, PlanReview, MCP Auth, etc.) - ActivityPanel and RunTimeline with glass-surface cards - Settings panels, modals, and editor shells - TerminalPanel with harmonized ANSI palette - PatternGraph nodes with glass-surface styling - MarkdownComposer toolbar Zero hardcoded zinc/indigo color classes remain in renderer components. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,11 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "kopaya",
|
"name": "kopaya",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource-variable/dm-sans": "^5.2.8",
|
||||||
|
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
||||||
|
"@fontsource-variable/outfit": "^5.2.8",
|
||||||
"keytar": "^7.9.0",
|
"keytar": "^7.9.0",
|
||||||
|
"motion": "^12.38.0",
|
||||||
"node-pty": "^1.1.0",
|
"node-pty": "^1.1.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -157,6 +161,12 @@
|
|||||||
|
|
||||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
|
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
|
||||||
|
|
||||||
|
"@fontsource-variable/dm-sans": ["@fontsource-variable/dm-sans@5.2.8", "", {}, "sha512-AxkvMTvNWgfrmlyjiV05vlHYJa+nRQCf1EfvIrQAPBpFJW0O9VTz7oAFr9S3lvbWdmnFoBk7yFqQL86u64nl2g=="],
|
||||||
|
|
||||||
|
"@fontsource-variable/jetbrains-mono": ["@fontsource-variable/jetbrains-mono@5.2.8", "", {}, "sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q=="],
|
||||||
|
|
||||||
|
"@fontsource-variable/outfit": ["@fontsource-variable/outfit@5.2.8", "", {}, "sha512-4oUDCZx/Tcz6HZP423w/niqEH31Gks5IsqHV2ZZz1qKHaVIZdj2f0/S1IK2n8jl6Xo0o3N+3RjNHlV9R73ozQA=="],
|
||||||
|
|
||||||
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
|
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
@@ -569,6 +579,8 @@
|
|||||||
|
|
||||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||||
|
|
||||||
|
"framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
|
||||||
|
|
||||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||||
|
|
||||||
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||||
@@ -831,6 +843,12 @@
|
|||||||
|
|
||||||
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
|
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
|
||||||
|
|
||||||
|
"motion": ["motion@12.38.0", "", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="],
|
||||||
|
|
||||||
|
"motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="],
|
||||||
|
|
||||||
|
"motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
@@ -1011,6 +1029,8 @@
|
|||||||
|
|
||||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||||
|
|
||||||
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
||||||
|
|
||||||
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||||
|
|||||||
@@ -68,7 +68,11 @@
|
|||||||
"yaml": "^2.8.3"
|
"yaml": "^2.8.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource-variable/dm-sans": "^5.2.8",
|
||||||
|
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
||||||
|
"@fontsource-variable/outfit": "^5.2.8",
|
||||||
"keytar": "^7.9.0",
|
"keytar": "^7.9.0",
|
||||||
|
"motion": "^12.38.0",
|
||||||
"node-pty": "^1.1.0"
|
"node-pty": "^1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ import { ProviderIcon } from './ProviderIcons';
|
|||||||
/* ── Mode accent colours ───────────────────────────────────── */
|
/* ── Mode accent colours ───────────────────────────────────── */
|
||||||
|
|
||||||
const modeAccent: Record<OrchestrationMode, { dot: string; bar: string; label: string }> = {
|
const modeAccent: Record<OrchestrationMode, { dot: string; bar: string; label: string }> = {
|
||||||
single: { dot: 'bg-indigo-400', bar: 'bg-indigo-500/60', label: 'text-indigo-400' },
|
single: { dot: 'bg-[#245CF9]', bar: 'bg-[#245CF9] opacity-60', label: 'text-[#245CF9]' },
|
||||||
sequential: { dot: 'bg-amber-400', bar: 'bg-amber-500/60', label: 'text-amber-400' },
|
sequential: { dot: 'bg-[var(--color-status-warning)]', bar: 'bg-[var(--color-status-warning)] opacity-60', label: 'text-[var(--color-status-warning)]' },
|
||||||
concurrent: { dot: 'bg-emerald-400', bar: 'bg-emerald-500/60', label: 'text-emerald-400' },
|
concurrent: { dot: 'bg-[var(--color-status-success)]', bar: 'bg-[var(--color-status-success)] opacity-60', label: 'text-[var(--color-status-success)]' },
|
||||||
handoff: { dot: 'bg-sky-400', bar: 'bg-sky-500/60', label: 'text-sky-400' },
|
handoff: { dot: 'bg-[var(--color-accent-sky)]', bar: 'bg-[var(--color-accent-sky)] opacity-60', label: 'text-[var(--color-accent-sky)]' },
|
||||||
'group-chat': { dot: 'bg-violet-400', bar: 'bg-violet-500/60', label: 'text-violet-400' },
|
'group-chat': { dot: 'bg-[var(--color-accent-purple)]', bar: 'bg-[var(--color-accent-purple)] opacity-60', label: 'text-[var(--color-accent-purple)]' },
|
||||||
magentic: { dot: 'bg-zinc-500', bar: 'bg-zinc-600/60', label: 'text-zinc-500' },
|
magentic: { dot: 'bg-[var(--color-text-muted)]', bar: 'bg-[var(--color-text-muted)] opacity-60', label: 'text-[var(--color-text-muted)]' },
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ── Helpers ───────────────────────────────────────────────── */
|
/* ── Helpers ───────────────────────────────────────────────── */
|
||||||
@@ -62,7 +62,7 @@ const modeLabels: Record<OrchestrationMode, string> = {
|
|||||||
|
|
||||||
function SectionHeader({ children }: { children: ReactNode }) {
|
function SectionHeader({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<h3 className="mb-2 flex items-center gap-2 text-[10px] font-semibold uppercase tracking-wider text-zinc-600">
|
<h3 className="font-display mb-2 flex items-center gap-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
{children}
|
{children}
|
||||||
</h3>
|
</h3>
|
||||||
);
|
);
|
||||||
@@ -87,7 +87,7 @@ function AgentRow({
|
|||||||
const isCompleted = isAgentActivityCompleted(row.activity);
|
const isCompleted = isAgentActivityCompleted(row.activity);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative flex gap-2.5 py-2.5 ${isLast ? '' : 'border-b border-zinc-800/50'}`}>
|
<div className={`relative flex gap-2.5 py-2.5 ${isLast ? '' : 'border-b border-[var(--color-border-subtle)]'}`}>
|
||||||
{/* Left accent bar — visible only when this agent is actively working */}
|
{/* Left accent bar — visible only when this agent is actively working */}
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<div className={`absolute -left-3 bottom-2 top-2 w-[3px] rounded-full ${accent.bar}`} />
|
<div className={`absolute -left-3 bottom-2 top-2 w-[3px] rounded-full ${accent.bar}`} />
|
||||||
@@ -96,12 +96,12 @@ function AgentRow({
|
|||||||
{/* Status dot */}
|
{/* Status dot */}
|
||||||
<div className="flex shrink-0 pt-0.5">
|
<div className="flex shrink-0 pt-0.5">
|
||||||
<span
|
<span
|
||||||
className={`size-2 rounded-full ${
|
className={`size-2 rounded-full transition-all duration-200 ${
|
||||||
isActive
|
isActive
|
||||||
? `animate-pulse ${accent.dot}`
|
? `animate-pulse ${accent.dot} ring-2 ring-[var(--color-border-glow)]`
|
||||||
: isCompleted
|
: isCompleted
|
||||||
? 'bg-emerald-400'
|
? 'bg-[var(--color-status-success)]'
|
||||||
: 'bg-zinc-700'
|
: 'bg-[var(--color-surface-3)]'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,13 +109,13 @@ function AgentRow({
|
|||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="truncate text-[12px] font-medium text-zinc-200">{row.agentName}</span>
|
<span className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">{row.agentName}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Model + effort inline */}
|
{/* Model + effort inline */}
|
||||||
{agent && (
|
{agent && (
|
||||||
<div className="mt-1 flex flex-wrap items-center gap-1">
|
<div className="mt-1 flex flex-wrap items-center gap-1">
|
||||||
<span className="inline-flex items-center gap-1 text-[10px] text-zinc-500">
|
<span className="inline-flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]">
|
||||||
{(() => {
|
{(() => {
|
||||||
const prov = inferProvider(agent.model);
|
const prov = inferProvider(agent.model);
|
||||||
return prov ? <ProviderIcon provider={prov} className="size-2.5" /> : null;
|
return prov ? <ProviderIcon provider={prov} className="size-2.5" /> : null;
|
||||||
@@ -124,8 +124,8 @@ function AgentRow({
|
|||||||
</span>
|
</span>
|
||||||
{agent.reasoningEffort && (
|
{agent.reasoningEffort && (
|
||||||
<>
|
<>
|
||||||
<span className="text-[10px] text-zinc-700">·</span>
|
<span className="text-[10px] text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="inline-flex items-center gap-0.5 text-[10px] text-zinc-500">
|
<span className="inline-flex items-center gap-0.5 text-[10px] text-[var(--color-text-muted)]">
|
||||||
<Sparkles className="size-2" />
|
<Sparkles className="size-2" />
|
||||||
{formatEffort(agent.reasoningEffort)}
|
{formatEffort(agent.reasoningEffort)}
|
||||||
</span>
|
</span>
|
||||||
@@ -141,8 +141,8 @@ function AgentRow({
|
|||||||
isActive
|
isActive
|
||||||
? accent.label
|
? accent.label
|
||||||
: isCompleted
|
: isCompleted
|
||||||
? 'text-emerald-400'
|
? 'text-[var(--color-status-success)]'
|
||||||
: 'text-zinc-600'
|
: 'text-[var(--color-text-muted)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{formatAgentActivityLabel(row.activity)}
|
{formatAgentActivityLabel(row.activity)}
|
||||||
@@ -151,20 +151,20 @@ function AgentRow({
|
|||||||
|
|
||||||
{/* Per-agent usage summary */}
|
{/* Per-agent usage summary */}
|
||||||
{agentUsage && agentUsage.requestCount > 0 && (
|
{agentUsage && agentUsage.requestCount > 0 && (
|
||||||
<div className="mt-0.5 flex items-center gap-1 text-[10px] text-zinc-600">
|
<div className="mt-0.5 flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]">
|
||||||
<span className="tabular-nums">{formatTokenCount(agentUsage.inputTokens)} in</span>
|
<span className="font-mono tabular-nums">{formatTokenCount(agentUsage.inputTokens)} in</span>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="tabular-nums">{formatTokenCount(agentUsage.outputTokens)} out</span>
|
<span className="font-mono tabular-nums">{formatTokenCount(agentUsage.outputTokens)} out</span>
|
||||||
{agentUsage.cost > 0 && (
|
{agentUsage.cost > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="tabular-nums">{agentUsage.cost.toFixed(2)} cost</span>
|
<span className="font-mono tabular-nums">{agentUsage.cost.toFixed(2)} cost</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{agentUsage.durationMs > 0 && (
|
{agentUsage.durationMs > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="tabular-nums">{formatDuration(agentUsage.durationMs)}</span>
|
<span className="font-mono tabular-nums">{formatDuration(agentUsage.durationMs)}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -182,15 +182,15 @@ function TurnEventIcon({ kind, phase, success }: { kind: SessionEventKind; phase
|
|||||||
const base = 'size-3';
|
const base = 'size-3';
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case 'subagent':
|
case 'subagent':
|
||||||
return <ArrowRight className={`${base} ${success === false ? 'text-red-400' : 'text-sky-400'}`} />;
|
return <ArrowRight className={`${base} ${success === false ? 'text-[var(--color-status-error)]' : 'text-[var(--color-accent-sky)]'}`} />;
|
||||||
case 'hook-lifecycle':
|
case 'hook-lifecycle':
|
||||||
return <Cog className={`${base} ${phase === 'start' ? 'animate-spin text-amber-400' : success === false ? 'text-red-400' : 'text-emerald-400'}`} />;
|
return <Cog className={`${base} ${phase === 'start' ? 'animate-spin text-[var(--color-status-warning)]' : success === false ? 'text-[var(--color-status-error)]' : 'text-[var(--color-status-success)]'}`} />;
|
||||||
case 'skill-invoked':
|
case 'skill-invoked':
|
||||||
return <Sparkles className={`${base} text-violet-400`} />;
|
return <Sparkles className={`${base} text-[var(--color-accent-purple)]`} />;
|
||||||
case 'session-compaction':
|
case 'session-compaction':
|
||||||
return <CheckCircle2 className={`${base} ${phase === 'start' ? 'animate-pulse text-amber-400' : 'text-emerald-400'}`} />;
|
return <CheckCircle2 className={`${base} ${phase === 'start' ? 'animate-pulse text-[var(--color-status-warning)]' : 'text-[var(--color-status-success)]'}`} />;
|
||||||
default:
|
default:
|
||||||
return <Zap className={`${base} text-zinc-500`} />;
|
return <Zap className={`${base} text-[var(--color-text-muted)]`} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,19 +238,19 @@ export function ActivityPanel({
|
|||||||
{/* Header — top padding clears the title bar overlay zone */}
|
{/* Header — top padding clears the title bar overlay zone */}
|
||||||
<div className="drag-region border-b border-[var(--color-border)] px-4 pb-3 pt-3">
|
<div className="drag-region border-b border-[var(--color-border)] px-4 pb-3 pt-3">
|
||||||
<div className="flex min-h-8 items-center gap-2">
|
<div className="flex min-h-8 items-center gap-2">
|
||||||
<Activity className="size-4 text-zinc-500" />
|
<Activity className="size-4 text-[var(--color-text-muted)]" />
|
||||||
<span className="text-[12px] font-semibold uppercase tracking-[0.12em] text-zinc-400">
|
<span className="font-display text-[12px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-secondary)]">
|
||||||
Activity
|
Activity
|
||||||
</span>
|
</span>
|
||||||
{hasPendingApproval ? (
|
{hasPendingApproval ? (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<ShieldAlert className="size-3 text-amber-400" />
|
<ShieldAlert className="size-3 text-[var(--color-status-warning)]" />
|
||||||
<span className="text-[9px] font-semibold uppercase tracking-wider text-amber-400">
|
<span className="text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-warning)]">
|
||||||
Approval{totalApprovalCount > 1 ? `s (${totalApprovalCount})` : ''}
|
Approval{totalApprovalCount > 1 ? `s (${totalApprovalCount})` : ''}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
) : isBusy ? (
|
) : isBusy ? (
|
||||||
<span className="size-1.5 animate-pulse rounded-full bg-blue-400" />
|
<span className="size-1.5 animate-pulse rounded-full bg-[var(--color-status-info)]" />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -261,7 +261,7 @@ export function ActivityPanel({
|
|||||||
<SectionHeader>
|
<SectionHeader>
|
||||||
<Users className="size-3" />
|
<Users className="size-3" />
|
||||||
<span>Agents</span>
|
<span>Agents</span>
|
||||||
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[9px] tabular-nums text-zinc-500">
|
<span className="font-mono rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[9px] tabular-nums text-[var(--color-text-muted)]">
|
||||||
{activityRows.length}
|
{activityRows.length}
|
||||||
</span>
|
</span>
|
||||||
<span className={`ml-auto text-[9px] font-medium normal-case tracking-normal ${accent.label}`}>
|
<span className={`ml-auto text-[9px] font-medium normal-case tracking-normal ${accent.label}`}>
|
||||||
@@ -270,7 +270,7 @@ export function ActivityPanel({
|
|||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
|
|
||||||
{activityRows.length > 0 ? (
|
{activityRows.length > 0 ? (
|
||||||
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40 px-3">
|
<div className="glass-surface rounded-lg px-3">
|
||||||
{activityRows.map((row, index) => {
|
{activityRows.map((row, index) => {
|
||||||
const agentKey = row.activity?.agentId ?? row.key;
|
const agentKey = row.activity?.agentId ?? row.key;
|
||||||
const agentUsage = sessionRequestUsage?.perAgent[agentKey]
|
const agentUsage = sessionRequestUsage?.perAgent[agentKey]
|
||||||
@@ -288,7 +288,7 @@ export function ActivityPanel({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="py-4 text-center text-[11px] text-zinc-600">No agents configured</p>
|
<p className="py-4 text-center text-[11px] text-[var(--color-text-muted)]">No agents configured</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -300,32 +300,32 @@ export function ActivityPanel({
|
|||||||
<span>Session Usage</span>
|
<span>Session Usage</span>
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
|
|
||||||
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40 px-3 py-2.5">
|
<div className="glass-surface rounded-lg px-3 py-2.5">
|
||||||
<div className="flex flex-wrap items-center gap-1.5 text-[11px] text-zinc-400">
|
<div className="flex flex-wrap items-center gap-1.5 text-[11px] text-[var(--color-text-secondary)]">
|
||||||
<span className="font-medium tabular-nums">
|
<span className="font-mono font-medium tabular-nums">
|
||||||
{sessionRequestUsage.requestCount} premium request{sessionRequestUsage.requestCount === 1 ? '' : 's'}
|
{sessionRequestUsage.requestCount} premium request{sessionRequestUsage.requestCount === 1 ? '' : 's'}
|
||||||
</span>
|
</span>
|
||||||
{sessionRequestUsage.totalNanoAiu > 0 && (
|
{sessionRequestUsage.totalNanoAiu > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="tabular-nums">{formatNanoAiu(sessionRequestUsage.totalNanoAiu)} AIU</span>
|
<span className="font-mono tabular-nums">{formatNanoAiu(sessionRequestUsage.totalNanoAiu)} AIU</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 flex flex-wrap items-center gap-1.5 text-[10px] text-zinc-500">
|
<div className="mt-1 flex flex-wrap items-center gap-1.5 text-[10px] text-[var(--color-text-muted)]">
|
||||||
<span className="tabular-nums">{formatTokenCount(sessionRequestUsage.totalInputTokens)} in</span>
|
<span className="font-mono tabular-nums">{formatTokenCount(sessionRequestUsage.totalInputTokens)} in</span>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="tabular-nums">{formatTokenCount(sessionRequestUsage.totalOutputTokens)} out</span>
|
<span className="font-mono tabular-nums">{formatTokenCount(sessionRequestUsage.totalOutputTokens)} out</span>
|
||||||
{sessionRequestUsage.totalCost > 0 && (
|
{sessionRequestUsage.totalCost > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="tabular-nums">{sessionRequestUsage.totalCost.toFixed(2)} cost</span>
|
<span className="font-mono tabular-nums">{sessionRequestUsage.totalCost.toFixed(2)} cost</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{sessionRequestUsage.totalDurationMs > 0 && (
|
{sessionRequestUsage.totalDurationMs > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="tabular-nums">{formatDuration(sessionRequestUsage.totalDurationMs)} total</span>
|
<span className="font-mono tabular-nums">{formatDuration(sessionRequestUsage.totalDurationMs)} total</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -339,7 +339,7 @@ export function ActivityPanel({
|
|||||||
<Clock className="size-3" />
|
<Clock className="size-3" />
|
||||||
<span>Timeline</span>
|
<span>Timeline</span>
|
||||||
{session.runs.length > 0 && (
|
{session.runs.length > 0 && (
|
||||||
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[9px] tabular-nums text-zinc-500">
|
<span className="font-mono rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[9px] tabular-nums text-[var(--color-text-muted)]">
|
||||||
{session.runs.length}
|
{session.runs.length}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -354,12 +354,12 @@ export function ActivityPanel({
|
|||||||
<SectionHeader>
|
<SectionHeader>
|
||||||
<Zap className="size-3" />
|
<Zap className="size-3" />
|
||||||
<span>Events</span>
|
<span>Events</span>
|
||||||
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[9px] tabular-nums text-zinc-500">
|
<span className="font-mono rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[9px] tabular-nums text-[var(--color-text-muted)]">
|
||||||
{turnEvents.length}
|
{turnEvents.length}
|
||||||
</span>
|
</span>
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
|
|
||||||
<div className="space-y-0.5 rounded-lg border border-zinc-800 bg-zinc-900/40 px-3 py-2">
|
<div className="glass-surface space-y-0.5 rounded-lg px-3 py-2">
|
||||||
{turnEvents.slice().reverse().map((entry, index) => (
|
{turnEvents.slice().reverse().map((entry, index) => (
|
||||||
<div key={index} className="flex items-start gap-2 py-1">
|
<div key={index} className="flex items-start gap-2 py-1">
|
||||||
<div className="mt-0.5 shrink-0">
|
<div className="mt-0.5 shrink-0">
|
||||||
@@ -367,13 +367,13 @@ export function ActivityPanel({
|
|||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-[11px] font-medium text-zinc-300">{entry.label}</span>
|
<span className="text-[11px] font-medium text-[var(--color-text-secondary)]">{entry.label}</span>
|
||||||
<span className="ml-auto shrink-0 text-[9px] tabular-nums text-zinc-700">
|
<span className="font-mono ml-auto shrink-0 text-[9px] tabular-nums text-[var(--color-text-muted)]">
|
||||||
{formatTurnEventTimestamp(entry.occurredAt)}
|
{formatTurnEventTimestamp(entry.occurredAt)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{entry.detail && (
|
{entry.detail && (
|
||||||
<p className="text-[10px] leading-snug text-zinc-600">{entry.detail}</p>
|
<p className="text-[10px] leading-snug text-[var(--color-text-muted)]">{entry.detail}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ function TierBadge({ tier }: { tier: ModelDefinition['tier'] }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
premium: 'bg-amber-500/10 text-amber-400',
|
premium: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]',
|
||||||
standard: 'bg-zinc-700/50 text-zinc-500',
|
standard: 'bg-[var(--color-surface-3)]/50 text-[var(--color-text-muted)]',
|
||||||
fast: 'bg-emerald-500/10 text-emerald-400',
|
fast: 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -75,10 +75,10 @@ export function ModelSelect({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<label className="block space-y-1.5">
|
<label className="block space-y-1.5">
|
||||||
<span className="text-[12px] font-medium text-zinc-400">{label}</span>
|
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
|
||||||
<div className="relative" ref={containerRef}>
|
<div className="relative" ref={containerRef}>
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center gap-2 rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-left text-[13px] text-zinc-100 outline-none transition hover:border-zinc-600 focus:border-indigo-500/50 disabled:cursor-not-allowed disabled:opacity-60"
|
className="flex w-full items-center gap-2 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 text-left text-[13px] text-[var(--color-text-primary)] outline-none transition-all duration-200 hover:border-[var(--color-border)] focus:border-[var(--color-accent)]/50 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => setOpen((current) => !current)}
|
onClick={() => setOpen((current) => !current)}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -86,25 +86,25 @@ export function ModelSelect({
|
|||||||
{provider && <ProviderIcon provider={provider} />}
|
{provider && <ProviderIcon provider={provider} />}
|
||||||
<span className="flex-1 truncate">{selected?.name ?? (value || 'Select model')}</span>
|
<span className="flex-1 truncate">{selected?.name ?? (value || 'Select model')}</span>
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`size-3.5 text-zinc-500 transition ${open ? 'rotate-180' : ''}`}
|
className={`size-3.5 text-[var(--color-text-muted)] transition ${open ? 'rotate-180' : ''}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{open && !disabled && (
|
{open && !disabled && (
|
||||||
<div className="absolute z-30 mt-1 max-h-72 w-full overflow-y-auto rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-2xl">
|
<div className="absolute z-30 mt-1 max-h-72 w-full overflow-y-auto rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
|
||||||
{groupedModels.map((providerGroup) => {
|
{groupedModels.map((providerGroup) => {
|
||||||
return (
|
return (
|
||||||
<div key={providerGroup.id}>
|
<div key={providerGroup.id}>
|
||||||
<div className="flex items-center gap-2 px-3 pb-1 pt-2.5">
|
<div className="flex items-center gap-2 px-3 pb-1 pt-2.5">
|
||||||
<ProviderIcon provider={providerGroup.id} />
|
<ProviderIcon provider={providerGroup.id} />
|
||||||
<span className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
|
<span className="text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
{providerGroup.label}
|
{providerGroup.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{providerGroup.models.map((model) => (
|
{providerGroup.models.map((model) => (
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
|
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
|
||||||
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
|
model.id === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
key={model.id}
|
key={model.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -122,13 +122,13 @@ export function ModelSelect({
|
|||||||
})}
|
})}
|
||||||
{otherModels.length > 0 && (
|
{otherModels.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="px-3 pb-1 pt-2.5 text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
|
<div className="px-3 pb-1 pt-2.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Other
|
Other
|
||||||
</div>
|
</div>
|
||||||
{otherModels.map((model) => (
|
{otherModels.map((model) => (
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
|
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
|
||||||
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
|
model.id === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
key={model.id}
|
key={model.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -173,15 +173,15 @@ export function ReasoningEffortSelect({
|
|||||||
if (supportedEfforts && supportedEfforts.length === 0) {
|
if (supportedEfforts && supportedEfforts.length === 0) {
|
||||||
return (
|
return (
|
||||||
<label className="block space-y-1.5">
|
<label className="block space-y-1.5">
|
||||||
<span className="text-[12px] font-medium text-zinc-400">{label}</span>
|
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 pr-9 text-[13px] text-zinc-500 outline-none"
|
className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 pr-9 text-[13px] text-[var(--color-text-muted)] outline-none"
|
||||||
disabled
|
disabled
|
||||||
readOnly
|
readOnly
|
||||||
value="Not supported for this model"
|
value="Not supported for this model"
|
||||||
/>
|
/>
|
||||||
<Sparkles className="pointer-events-none absolute right-3 top-1/2 size-3.5 -translate-y-1/2 text-zinc-600" />
|
<Sparkles className="pointer-events-none absolute right-3 top-1/2 size-3.5 -translate-y-1/2 text-[var(--color-text-muted)]" />
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
@@ -189,10 +189,10 @@ export function ReasoningEffortSelect({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<label className="block space-y-1.5">
|
<label className="block space-y-1.5">
|
||||||
<span className="text-[12px] font-medium text-zinc-400">{label}</span>
|
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<select
|
<select
|
||||||
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 pr-9 text-[13px] text-zinc-100 outline-none transition focus:border-indigo-500/50 disabled:cursor-not-allowed disabled:opacity-60"
|
className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 pr-9 text-[13px] text-[var(--color-text-primary)] outline-none transition focus:border-[var(--color-accent)]/50 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
disabled={disabled || !selectedValue}
|
disabled={disabled || !selectedValue}
|
||||||
onChange={(event) => onChange(event.target.value as ReasoningEffort)}
|
onChange={(event) => onChange(event.target.value as ReasoningEffort)}
|
||||||
value={selectedValue}
|
value={selectedValue}
|
||||||
@@ -203,7 +203,7 @@ export function ReasoningEffortSelect({
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<Sparkles className="pointer-events-none absolute right-3 top-1/2 size-3.5 -translate-y-1/2 text-zinc-500" />
|
<Sparkles className="pointer-events-none absolute right-3 top-1/2 size-3.5 -translate-y-1/2 text-[var(--color-text-muted)]" />
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,22 +10,33 @@ interface AppShellProps {
|
|||||||
|
|
||||||
export function AppShell({ sidebar, content, detailPanel, terminalPanel, overlay }: AppShellProps) {
|
export function AppShell({ sidebar, content, detailPanel, terminalPanel, overlay }: AppShellProps) {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen bg-[var(--color-surface-0)] text-zinc-100">
|
<div className="relative flex h-screen bg-[var(--color-surface-0)] text-[var(--color-text-primary)]">
|
||||||
{/* Full-width drag region matching the title bar overlay height */}
|
{/* Full-width drag region matching the title bar overlay height */}
|
||||||
<div className="drag-region absolute inset-x-0 top-0 z-10 h-3" />
|
<div className="drag-region absolute inset-x-0 top-0 z-10 h-3" />
|
||||||
|
|
||||||
<aside className="flex w-72 shrink-0 flex-col border-r border-[var(--color-border)] bg-[var(--color-surface-1)]">
|
{/* Sidebar */}
|
||||||
|
<aside className="flex w-72 shrink-0 flex-col border-r border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]">
|
||||||
{sidebar}
|
{sidebar}
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
{/* Main content + terminal */}
|
||||||
<main className="relative flex min-w-0 flex-1 flex-col">
|
<main className="relative flex min-w-0 flex-1 flex-col">
|
||||||
<div className="min-h-0 flex-1">{content}</div>
|
{/* Ambient glow behind active content area */}
|
||||||
|
<div
|
||||||
|
className="pointer-events-none absolute inset-0 opacity-30"
|
||||||
|
style={{ background: 'var(--gradient-glow)' }}
|
||||||
|
/>
|
||||||
|
<div className="relative min-h-0 flex-1">{content}</div>
|
||||||
{terminalPanel}
|
{terminalPanel}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* Detail panel */}
|
||||||
{detailPanel && (
|
{detailPanel && (
|
||||||
<aside className="flex w-64 shrink-0 flex-col border-l border-[var(--color-border)] bg-[var(--color-surface-1)]">
|
<aside className="flex w-64 shrink-0 flex-col border-l border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]">
|
||||||
{detailPanel}
|
{detailPanel}
|
||||||
</aside>
|
</aside>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{overlay}
|
{overlay}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -250,16 +250,16 @@ export function ChatPane({
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* Header — extra top padding clears the title bar overlay zone */}
|
{/* Header — extra top padding clears the title bar overlay zone */}
|
||||||
<header className="drag-region border-b border-[var(--color-border)] px-6 pb-3 pt-3">
|
<header className="drag-region border-b border-[var(--color-border-subtle)] px-6 pb-3 pt-3">
|
||||||
<div className="flex min-h-8 items-center justify-between">
|
<div className="flex min-h-8 items-center justify-between">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<h2 className="truncate text-[13px] font-semibold leading-tight text-zinc-100">{session.title}</h2>
|
<h2 className="font-display truncate text-[13px] font-semibold leading-tight text-[var(--color-text-primary)]">{session.title}</h2>
|
||||||
<p className="truncate text-[11px] leading-tight text-zinc-500">
|
<p className="truncate text-[11px] leading-tight text-[var(--color-text-muted)]">
|
||||||
{isScratchpad
|
{isScratchpad
|
||||||
? `Scratchpad · ${pattern.name}`
|
? `Scratchpad · ${pattern.name}`
|
||||||
: `${project.name} · ${pattern.name} · ${pattern.mode}`}
|
: `${project.name} · ${pattern.name} · ${pattern.mode}`}
|
||||||
{!isScratchpad && project.git?.status === 'ready' && (
|
{!isScratchpad && project.git?.status === 'ready' && (
|
||||||
<span className="ml-2 inline-flex items-center gap-1 text-zinc-600">
|
<span className="ml-2 inline-flex items-center gap-1 text-[var(--color-text-muted)]">
|
||||||
<GitBranch className="inline size-2.5" />
|
<GitBranch className="inline size-2.5" />
|
||||||
{project.git.branch ?? project.git.head?.shortHash ?? 'HEAD'}
|
{project.git.branch ?? project.git.head?.shortHash ?? 'HEAD'}
|
||||||
{project.git.isDirty && (
|
{project.git.isDirty && (
|
||||||
@@ -273,31 +273,31 @@ export function ChatPane({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{pendingApproval && (
|
{pendingApproval && (
|
||||||
<div className="flex items-center gap-1.5 text-[12px] font-medium text-amber-400">
|
<div className="flex items-center gap-1.5 text-[12px] font-medium text-[var(--color-status-warning)]">
|
||||||
<ShieldAlert className="size-3.5" />
|
<ShieldAlert className="size-3.5" />
|
||||||
Awaiting approval
|
Awaiting approval
|
||||||
{queuedApprovals.length > 0 && (
|
{queuedApprovals.length > 0 && (
|
||||||
<span className="rounded-full bg-amber-500/15 px-1.5 py-0.5 text-[10px] tabular-nums">
|
<span className="rounded-full bg-[var(--color-status-warning)]/15 px-1.5 py-0.5 text-[10px] tabular-nums">
|
||||||
+{queuedApprovals.length} queued
|
+{queuedApprovals.length} queued
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{pendingUserInput && !pendingApproval && (
|
{pendingUserInput && !pendingApproval && (
|
||||||
<div className="flex items-center gap-1.5 text-[12px] font-medium text-blue-400">
|
<div className="flex items-center gap-1.5 text-[12px] font-medium text-[var(--color-accent-sky)]">
|
||||||
<MessageCircleQuestion className="size-3.5" />
|
<MessageCircleQuestion className="size-3.5" />
|
||||||
Awaiting your input
|
Awaiting your input
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isSessionBusy && !pendingApproval && !pendingUserInput && <span className="size-2 animate-pulse rounded-full bg-blue-400" />}
|
{isSessionBusy && !pendingApproval && !pendingUserInput && <span className="size-2 animate-pulse rounded-full bg-[var(--color-accent-sky)]" />}
|
||||||
{session.status === 'error' && (
|
{session.status === 'error' && (
|
||||||
<div className="flex items-center gap-1.5 text-[12px] text-red-400">
|
<div className="flex items-center gap-1.5 text-[12px] text-[var(--color-status-error)]">
|
||||||
<AlertCircle className="size-3.5" />
|
<AlertCircle className="size-3.5" />
|
||||||
Error
|
Error
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{session.status === 'idle' && !pendingApproval && !pendingUserInput && session.messages.length > 0 && (
|
{session.status === 'idle' && !pendingApproval && !pendingUserInput && session.messages.length > 0 && (
|
||||||
<span className="text-[12px] text-zinc-600">
|
<span className="text-[12px] text-[var(--color-text-muted)]">
|
||||||
{session.messages.length} message{session.messages.length === 1 ? '' : 's'}
|
{session.messages.length} message{session.messages.length === 1 ? '' : 's'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -309,18 +309,18 @@ export function ChatPane({
|
|||||||
<div className="flex-1 overflow-y-auto" ref={transcriptRef}>
|
<div className="flex-1 overflow-y-auto" ref={transcriptRef}>
|
||||||
{session.messages.length === 0 ? (
|
{session.messages.length === 0 ? (
|
||||||
<div className="flex h-full flex-col items-center justify-center gap-2 px-6 text-center">
|
<div className="flex h-full flex-col items-center justify-center gap-2 px-6 text-center">
|
||||||
<Bot className="size-10 text-zinc-800" />
|
<Bot className="size-10 text-[var(--color-surface-3)]" />
|
||||||
<p className="text-[13px] text-zinc-500">Send a message to start the conversation</p>
|
<p className="text-[13px] text-[var(--color-text-muted)]">Send a message to start the conversation</p>
|
||||||
<p className="text-[12px] text-zinc-700">
|
<p className="text-[12px] text-[var(--color-text-muted)]">
|
||||||
{isScratchpad ? (
|
{isScratchpad ? (
|
||||||
<>
|
<>
|
||||||
Scratchpad is ready for ad-hoc questions using{' '}
|
Scratchpad is ready for ad-hoc questions using{' '}
|
||||||
<span className="text-zinc-500">{pattern.name}</span>
|
<span className="text-[var(--color-text-secondary)]">{pattern.name}</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
Using <span className="text-zinc-500">{pattern.name}</span> in{' '}
|
Using <span className="text-[var(--color-text-secondary)]">{pattern.name}</span> in{' '}
|
||||||
<span className="text-zinc-500">{project.name}</span>
|
<span className="text-[var(--color-text-secondary)]">{project.name}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
@@ -333,14 +333,14 @@ export function ChatPane({
|
|||||||
const phase = getAssistantMessagePhase(session, message, index);
|
const phase = getAssistantMessagePhase(session, message, index);
|
||||||
const assistantContainerClass =
|
const assistantContainerClass =
|
||||||
phase === 'thinking'
|
phase === 'thinking'
|
||||||
? 'border-sky-500/20 bg-sky-500/5'
|
? 'border-[var(--color-accent-sky)]/20 bg-[var(--color-accent-sky)]/5'
|
||||||
: phase === 'final'
|
: phase === 'final'
|
||||||
? 'border-emerald-500/20 bg-emerald-500/5'
|
? 'border-[var(--color-status-success)]/20 bg-[var(--color-status-success)]/5'
|
||||||
: 'border-zinc-800 bg-zinc-900/40';
|
: 'border-[var(--color-border)] bg-[var(--color-surface-1)]/40';
|
||||||
const assistantBadgeClass =
|
const assistantBadgeClass =
|
||||||
phase === 'thinking'
|
phase === 'thinking'
|
||||||
? 'border-sky-400/20 bg-sky-400/10 text-sky-300'
|
? 'border-[var(--color-accent-sky)]/20 bg-[var(--color-accent-sky)]/10 text-[var(--color-accent-sky)]'
|
||||||
: 'border-emerald-400/20 bg-emerald-400/10 text-emerald-300';
|
: 'border-[var(--color-status-success)]/20 bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]';
|
||||||
const phaseLabel =
|
const phaseLabel =
|
||||||
phase === 'thinking' ? 'Thinking' : phase === 'final' ? 'Final' : undefined;
|
phase === 'thinking' ? 'Thinking' : phase === 'final' ? 'Final' : undefined;
|
||||||
|
|
||||||
@@ -349,13 +349,13 @@ export function ChatPane({
|
|||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<div
|
<div
|
||||||
className={`mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full ${
|
className={`mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full ${
|
||||||
isUser ? 'bg-indigo-600 text-white' : 'bg-zinc-800 text-zinc-400'
|
isUser ? 'brand-gradient-bg text-white' : 'bg-[var(--color-surface-2)] text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isUser ? <User className="size-3.5" /> : <Bot className="size-3.5" />}
|
{isUser ? <User className="size-3.5" /> : <Bot className="size-3.5" />}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="mb-1 flex items-center gap-2 text-[12px] font-medium text-zinc-400">
|
<div className="mb-1 flex items-center gap-2 text-[12px] font-medium text-[var(--color-text-secondary)]">
|
||||||
<span>{message.authorName}</span>
|
<span>{message.authorName}</span>
|
||||||
{!isUser && phaseLabel && (
|
{!isUser && phaseLabel && (
|
||||||
<span
|
<span
|
||||||
@@ -368,8 +368,8 @@ export function ChatPane({
|
|||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
isUser
|
isUser
|
||||||
? 'text-[14px] leading-relaxed text-zinc-200'
|
? 'text-[14px] leading-relaxed text-[var(--color-text-primary)]'
|
||||||
: `rounded-xl border px-4 py-3 text-[14px] leading-relaxed text-zinc-200 ${assistantContainerClass}`
|
: `rounded-xl border px-4 py-3 text-[14px] leading-relaxed text-[var(--color-text-primary)] ${assistantContainerClass}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* Attachment thumbnails */}
|
{/* Attachment thumbnails */}
|
||||||
@@ -380,13 +380,13 @@ export function ChatPane({
|
|||||||
<img
|
<img
|
||||||
key={attIdx}
|
key={attIdx}
|
||||||
alt={getAttachmentDisplayName(att)}
|
alt={getAttachmentDisplayName(att)}
|
||||||
className="max-h-48 max-w-xs rounded-lg border border-zinc-700 object-cover"
|
className="max-h-48 max-w-xs rounded-lg border border-[var(--color-border)] object-cover"
|
||||||
src={`data:${att.mimeType};base64,${att.data}`}
|
src={`data:${att.mimeType};base64,${att.data}`}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
key={attIdx}
|
key={attIdx}
|
||||||
className="flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800 px-2 py-1 text-[11px] text-zinc-400"
|
className="flex items-center gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2 py-1 text-[11px] text-[var(--color-text-secondary)]"
|
||||||
>
|
>
|
||||||
<Paperclip className="size-3" />
|
<Paperclip className="size-3" />
|
||||||
{getAttachmentDisplayName(att)}
|
{getAttachmentDisplayName(att)}
|
||||||
@@ -396,14 +396,14 @@ export function ChatPane({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isUser && message.pending ? (
|
{!isUser && message.pending ? (
|
||||||
<div className="whitespace-pre-wrap break-words text-[14px] leading-relaxed text-zinc-200">
|
<div className="whitespace-pre-wrap break-words text-[14px] leading-relaxed text-[var(--color-text-primary)]">
|
||||||
{message.content}
|
{message.content}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<MarkdownContent content={message.content} />
|
<MarkdownContent content={message.content} />
|
||||||
)}
|
)}
|
||||||
{message.pending && message.content && (
|
{message.pending && message.content && (
|
||||||
<span className="mt-1 inline-block h-4 w-[2px] animate-pulse rounded-sm bg-zinc-400" />
|
<span className="mt-1 inline-block h-4 w-[2px] animate-pulse rounded-sm bg-[var(--color-accent)]" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{message.pending && !message.content && <ThinkingDots />}
|
{message.pending && !message.content && <ThinkingDots />}
|
||||||
@@ -423,24 +423,24 @@ export function ChatPane({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Input area */}
|
{/* Input area */}
|
||||||
<div className="border-t border-[var(--color-border)] px-6 py-4">
|
<div className="border-t border-[var(--color-border-subtle)] px-6 py-4">
|
||||||
{session.lastError && (
|
{session.lastError && (
|
||||||
<div className="mb-3 flex items-start gap-2 rounded-lg bg-red-500/10 px-3 py-2 text-[13px] text-red-300">
|
<div className="mb-3 flex items-start gap-2 rounded-lg bg-[var(--color-status-error)]/10 px-3 py-2 text-[13px] text-[var(--color-status-error)]">
|
||||||
<AlertCircle className="mt-0.5 size-4 shrink-0 text-red-400" />
|
<AlertCircle className="mt-0.5 size-4 shrink-0 text-[var(--color-status-error)]" />
|
||||||
<span>{session.lastError}</span>
|
<span>{session.lastError}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{configError && (
|
{configError && (
|
||||||
<div className="mb-3 flex items-start gap-2 rounded-lg bg-red-500/10 px-3 py-2 text-[13px] text-red-300">
|
<div className="mb-3 flex items-start gap-2 rounded-lg bg-[var(--color-status-error)]/10 px-3 py-2 text-[13px] text-[var(--color-status-error)]">
|
||||||
<AlertCircle className="mt-0.5 size-4 shrink-0 text-red-400" />
|
<AlertCircle className="mt-0.5 size-4 shrink-0 text-[var(--color-status-error)]" />
|
||||||
<span>{configError}</span>
|
<span>{configError}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{approvalError && (
|
{approvalError && (
|
||||||
<div className="mb-3 flex items-start gap-2 rounded-lg bg-red-500/10 px-3 py-2 text-[13px] text-red-300">
|
<div className="mb-3 flex items-start gap-2 rounded-lg bg-[var(--color-status-error)]/10 px-3 py-2 text-[13px] text-[var(--color-status-error)]">
|
||||||
<AlertCircle className="mt-0.5 size-4 shrink-0 text-red-400" />
|
<AlertCircle className="mt-0.5 size-4 shrink-0 text-[var(--color-status-error)]" />
|
||||||
<span>{approvalError}</span>
|
<span>{approvalError}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -544,7 +544,7 @@ export function ChatPane({
|
|||||||
value={sessionReasoningEffort}
|
value={sessionReasoningEffort}
|
||||||
/>
|
/>
|
||||||
{isUpdatingSessionModelConfig && (
|
{isUpdatingSessionModelConfig && (
|
||||||
<Loader2 className="size-3 animate-spin text-zinc-500" />
|
<Loader2 className="size-3 animate-spin text-[var(--color-text-muted)]" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -584,13 +584,13 @@ export function ChatPane({
|
|||||||
{pendingAttachments.map((attachment, index) => (
|
{pendingAttachments.map((attachment, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800 px-2.5 py-1.5 text-[11px] text-zinc-300"
|
className="flex items-center gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2.5 py-1.5 text-[11px] text-[var(--color-text-secondary)]"
|
||||||
>
|
>
|
||||||
<Paperclip className="size-3 text-zinc-500" />
|
<Paperclip className="size-3 text-[var(--color-text-muted)]" />
|
||||||
<span className="max-w-[160px] truncate">{getAttachmentDisplayName(attachment)}</span>
|
<span className="max-w-[160px] truncate">{getAttachmentDisplayName(attachment)}</span>
|
||||||
<button
|
<button
|
||||||
aria-label="Remove attachment"
|
aria-label="Remove attachment"
|
||||||
className="ml-1 rounded p-0.5 text-zinc-500 hover:bg-zinc-700 hover:text-zinc-300"
|
className="ml-1 rounded p-0.5 text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={() => setPendingAttachments((prev) => prev.filter((_, i) => i !== index))}
|
onClick={() => setPendingAttachments((prev) => prev.filter((_, i) => i !== index))}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -601,7 +601,7 @@ export function ChatPane({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="rounded-xl border border-zinc-700 bg-zinc-900 transition-colors focus-within:border-indigo-500/50">
|
<div className="rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-1)] transition-all duration-200 focus-within:border-[var(--color-border-glow)] focus-within:shadow-[0_0_16px_rgba(36,92,249,0.06)]">
|
||||||
<MarkdownComposer
|
<MarkdownComposer
|
||||||
ref={composerRef}
|
ref={composerRef}
|
||||||
disabled={isComposerDisabled}
|
disabled={isComposerDisabled}
|
||||||
@@ -651,7 +651,7 @@ export function ChatPane({
|
|||||||
{/* Attachment picker */}
|
{/* Attachment picker */}
|
||||||
<button
|
<button
|
||||||
aria-label="Attach image"
|
aria-label="Attach image"
|
||||||
className="flex size-8 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="flex size-8 items-center justify-center rounded-lg text-[var(--color-text-muted)] transition-all duration-150 hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text-primary)]"
|
||||||
disabled={isComposerDisabled}
|
disabled={isComposerDisabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
@@ -685,10 +685,10 @@ export function ChatPane({
|
|||||||
<button
|
<button
|
||||||
aria-label={isPlanMode ? 'Switch to interactive mode' : 'Switch to plan mode'}
|
aria-label={isPlanMode ? 'Switch to interactive mode' : 'Switch to plan mode'}
|
||||||
aria-pressed={isPlanMode}
|
aria-pressed={isPlanMode}
|
||||||
className={`flex size-8 items-center justify-center rounded-lg transition ${
|
className={`flex size-8 items-center justify-center rounded-lg transition-all duration-150 ${
|
||||||
isPlanMode
|
isPlanMode
|
||||||
? 'bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30'
|
? 'bg-[var(--color-status-success)]/20 text-[var(--color-status-success)] hover:bg-[var(--color-status-success)]/30'
|
||||||
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
|
: 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text-primary)]'
|
||||||
}`}
|
}`}
|
||||||
disabled={isComposerDisabled}
|
disabled={isComposerDisabled}
|
||||||
onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')}
|
onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')}
|
||||||
@@ -700,16 +700,16 @@ export function ChatPane({
|
|||||||
|
|
||||||
{/* Send / Stop / Steer button */}
|
{/* Send / Stop / Steer button */}
|
||||||
<button
|
<button
|
||||||
className={`flex size-8 items-center justify-center rounded-lg transition ${
|
className={`flex size-8 items-center justify-center rounded-lg transition-all duration-150 ${
|
||||||
isSessionBusy && !hasComposerContent && pendingAttachments.length === 0
|
isSessionBusy && !hasComposerContent && pendingAttachments.length === 0
|
||||||
? 'bg-red-600/80 text-white hover:bg-red-500'
|
? 'bg-[var(--color-status-error)]/80 text-white hover:bg-[var(--color-status-error)]'
|
||||||
: canSubmitInput || pendingAttachments.length > 0
|
: canSubmitInput || pendingAttachments.length > 0
|
||||||
? isSessionBusy
|
? isSessionBusy
|
||||||
? 'bg-amber-600 text-white hover:bg-amber-500'
|
? 'bg-[var(--color-status-warning)] text-white hover:brightness-110'
|
||||||
: isPlanMode
|
: isPlanMode
|
||||||
? 'bg-emerald-600 text-white hover:bg-emerald-500'
|
? 'bg-[var(--color-status-success)] text-white hover:brightness-110'
|
||||||
: 'bg-indigo-600 text-white hover:bg-indigo-500'
|
: 'brand-gradient-bg text-white shadow-[0_2px_12px_rgba(36,92,249,0.25)] hover:shadow-[0_4px_20px_rgba(36,92,249,0.35)]'
|
||||||
: 'bg-zinc-800 text-zinc-600'
|
: 'bg-[var(--color-surface-2)] text-[var(--color-text-muted)]'
|
||||||
}`}
|
}`}
|
||||||
disabled={!canSubmitInput && !isSessionBusy && pendingAttachments.length === 0}
|
disabled={!canSubmitInput && !isSessionBusy && pendingAttachments.length === 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -741,16 +741,16 @@ export function ChatPane({
|
|||||||
</MarkdownComposer>
|
</MarkdownComposer>
|
||||||
{isPlanMode && !isSessionBusy && (
|
{isPlanMode && !isSessionBusy && (
|
||||||
<div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5">
|
<div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5">
|
||||||
<div className="size-1.5 rounded-full bg-emerald-500" />
|
<div className="size-1.5 rounded-full bg-[var(--color-status-success)]" />
|
||||||
<span className="text-[10px] font-medium text-emerald-400/80">
|
<span className="text-[10px] font-medium text-[var(--color-status-success)]/80">
|
||||||
Plan mode — the agent will propose a plan instead of implementing
|
Plan mode — the agent will propose a plan instead of implementing
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isSessionBusy && (hasComposerContent || pendingAttachments.length > 0) && (
|
{isSessionBusy && (hasComposerContent || pendingAttachments.length > 0) && (
|
||||||
<div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5">
|
<div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5">
|
||||||
<div className="size-1.5 rounded-full bg-amber-500" />
|
<div className="size-1.5 rounded-full bg-[var(--color-status-warning)]" />
|
||||||
<span className="text-[10px] font-medium text-amber-400/80">
|
<span className="text-[10px] font-medium text-[var(--color-status-warning)]/80">
|
||||||
Steering — your message will be injected into the current turn
|
Steering — your message will be injected into the current turn
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -760,15 +760,15 @@ export function ChatPane({
|
|||||||
{/* Session usage bar */}
|
{/* Session usage bar */}
|
||||||
{sessionUsage && sessionUsage.tokenLimit > 0 && (
|
{sessionUsage && sessionUsage.tokenLimit > 0 && (
|
||||||
<div className="px-1 pt-1.5">
|
<div className="px-1 pt-1.5">
|
||||||
<div className="flex items-center gap-2 text-[10px] text-zinc-500">
|
<div className="flex items-center gap-2 text-[10px] text-[var(--color-text-muted)]">
|
||||||
<div className="h-1 flex-1 overflow-hidden rounded-full bg-zinc-800">
|
<div className="h-1 flex-1 overflow-hidden rounded-full bg-[var(--color-surface-2)]">
|
||||||
<div
|
<div
|
||||||
className={`h-full rounded-full transition-all ${
|
className={`h-full rounded-full transition-all ${
|
||||||
sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.9
|
sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.9
|
||||||
? 'bg-red-500'
|
? 'bg-[var(--color-status-error)]'
|
||||||
: sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.7
|
: sessionUsage.currentTokens / sessionUsage.tokenLimit > 0.7
|
||||||
? 'bg-amber-500'
|
? 'bg-[var(--color-status-warning)]'
|
||||||
: 'bg-indigo-500/60'
|
: 'bg-[var(--color-accent)]/60'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${Math.min(100, (sessionUsage.currentTokens / sessionUsage.tokenLimit) * 100)}%` }}
|
style={{ width: `${Math.min(100, (sessionUsage.currentTokens / sessionUsage.tokenLimit) * 100)}%` }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -44,35 +44,35 @@ function getStatusConfig(status: SidecarConnectionStatus): StatusConfig {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'ready':
|
case 'ready':
|
||||||
return {
|
return {
|
||||||
icon: <CheckCircle2 className="size-4 text-emerald-400" />,
|
icon: <CheckCircle2 className="size-4 text-[var(--color-status-success)]" />,
|
||||||
label: 'Connected to GitHub Copilot',
|
label: 'Connected to GitHub Copilot',
|
||||||
accentClasses: 'text-emerald-400',
|
accentClasses: 'text-[var(--color-status-success)]',
|
||||||
dotClasses: 'bg-emerald-400',
|
dotClasses: 'bg-[var(--color-status-success)]',
|
||||||
};
|
};
|
||||||
case 'copilot-cli-missing':
|
case 'copilot-cli-missing':
|
||||||
return {
|
return {
|
||||||
icon: <Download className="size-4 text-amber-400" />,
|
icon: <Download className="size-4 text-[var(--color-status-warning)]" />,
|
||||||
label: 'Copilot CLI not found',
|
label: 'Copilot CLI not found',
|
||||||
accentClasses: 'text-amber-400',
|
accentClasses: 'text-[var(--color-status-warning)]',
|
||||||
dotClasses: 'bg-amber-400',
|
dotClasses: 'bg-[var(--color-status-warning)]',
|
||||||
actionIcon: <Terminal className="size-3" />,
|
actionIcon: <Terminal className="size-3" />,
|
||||||
actionLabel: 'Install the copilot CLI and ensure it is on your PATH',
|
actionLabel: 'Install the copilot CLI and ensure it is on your PATH',
|
||||||
};
|
};
|
||||||
case 'copilot-auth-required':
|
case 'copilot-auth-required':
|
||||||
return {
|
return {
|
||||||
icon: <LogIn className="size-4 text-blue-400" />,
|
icon: <LogIn className="size-4 text-[var(--color-status-info)]" />,
|
||||||
label: 'Sign-in required',
|
label: 'Sign-in required',
|
||||||
accentClasses: 'text-blue-400',
|
accentClasses: 'text-[var(--color-status-info)]',
|
||||||
dotClasses: 'bg-blue-400',
|
dotClasses: 'bg-[var(--color-status-info)]',
|
||||||
actionIcon: <Terminal className="size-3" />,
|
actionIcon: <Terminal className="size-3" />,
|
||||||
actionLabel: 'Run copilot auth login in your terminal, then refresh',
|
actionLabel: 'Run copilot auth login in your terminal, then refresh',
|
||||||
};
|
};
|
||||||
case 'copilot-error':
|
case 'copilot-error':
|
||||||
return {
|
return {
|
||||||
icon: <XCircle className="size-4 text-red-400" />,
|
icon: <XCircle className="size-4 text-[var(--color-status-error)]" />,
|
||||||
label: 'Connection error',
|
label: 'Connection error',
|
||||||
accentClasses: 'text-red-400',
|
accentClasses: 'text-[var(--color-status-error)]',
|
||||||
dotClasses: 'bg-red-400',
|
dotClasses: 'bg-[var(--color-status-error)]',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,21 +108,21 @@ function VersionBadge({ status, installedVersion }: { status: SidecarCopilotCliV
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'latest':
|
case 'latest':
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-400">
|
<span className="inline-flex items-center gap-1 rounded-full bg-[var(--color-status-success)]/10 px-2 py-0.5 text-[10px] font-medium text-[var(--color-status-success)]">
|
||||||
<CheckCircle2 className="size-2.5" />
|
<CheckCircle2 className="size-2.5" />
|
||||||
{versionLabel ?? 'Up to date'}
|
{versionLabel ?? 'Up to date'}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
case 'outdated':
|
case 'outdated':
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-amber-500/10 px-2 py-0.5 text-[10px] font-medium text-amber-400">
|
<span className="inline-flex items-center gap-1 rounded-full bg-[var(--color-status-warning)]/10 px-2 py-0.5 text-[10px] font-medium text-[var(--color-status-warning)]">
|
||||||
<ArrowUpCircle className="size-2.5" />
|
<ArrowUpCircle className="size-2.5" />
|
||||||
Update available
|
Update available
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
case 'unknown':
|
case 'unknown':
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium text-zinc-500">
|
<span className="inline-flex items-center gap-1 rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
|
||||||
{versionLabel ?? 'Version unknown'}
|
{versionLabel ?? 'Version unknown'}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -183,8 +183,8 @@ function QuotaSection({
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 py-2">
|
<div className="flex items-center gap-2 py-2">
|
||||||
<Loader2 className="size-3.5 animate-spin text-zinc-500" />
|
<Loader2 className="size-3.5 animate-spin text-[var(--color-text-muted)]" />
|
||||||
<span className="text-[12px] text-zinc-500">Loading quota…</span>
|
<span className="text-[12px] text-[var(--color-text-muted)]">Loading quota…</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -192,8 +192,8 @@ function QuotaSection({
|
|||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 py-2">
|
<div className="flex items-center gap-2 py-2">
|
||||||
<XCircle className="size-3.5 text-red-400" />
|
<XCircle className="size-3.5 text-[var(--color-status-error)]" />
|
||||||
<span className="text-[12px] text-zinc-500">Could not load quota</span>
|
<span className="text-[12px] text-[var(--color-text-muted)]">Could not load quota</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ function QuotaSection({
|
|||||||
if (!quotaData || Object.keys(quotaData).length === 0) {
|
if (!quotaData || Object.keys(quotaData).length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<span className="text-[12px] text-zinc-600">No quota data available</span>
|
<span className="text-[12px] text-[var(--color-text-muted)]">No quota data available</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -213,42 +213,42 @@ function QuotaSection({
|
|||||||
? (snapshot.usedRequests / snapshot.entitlementRequests) * 100
|
? (snapshot.usedRequests / snapshot.entitlementRequests) * 100
|
||||||
: 0;
|
: 0;
|
||||||
const barColor = usedPct > 90
|
const barColor = usedPct > 90
|
||||||
? 'bg-red-500'
|
? 'bg-[var(--color-status-error)]'
|
||||||
: usedPct > 70
|
: usedPct > 70
|
||||||
? 'bg-amber-500'
|
? 'bg-[var(--color-status-warning)]'
|
||||||
: 'bg-indigo-500/60';
|
: 'bg-[var(--color-accent)]/60';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={key} className="space-y-1.5">
|
<div key={key} className="space-y-1.5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-[12px] font-medium text-zinc-300">
|
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">
|
||||||
{formatQuotaTypeLabel(key)}
|
{formatQuotaTypeLabel(key)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[11px] tabular-nums text-zinc-500">
|
<span className="text-[11px] tabular-nums text-[var(--color-text-muted)]">
|
||||||
{Math.round(snapshot.remainingPercentage)}% remaining
|
{Math.round(snapshot.remainingPercentage)}% remaining
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-1.5 overflow-hidden rounded-full bg-zinc-800">
|
<div className="h-1.5 overflow-hidden rounded-full bg-[var(--color-surface-3)]">
|
||||||
<div
|
<div
|
||||||
className={`h-full rounded-full transition-all ${barColor}`}
|
className={`h-full rounded-full transition-all ${barColor}`}
|
||||||
style={{ width: `${Math.min(100, usedPct)}%` }}
|
style={{ width: `${Math.min(100, usedPct)}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5 text-[10px] text-zinc-500">
|
<div className="flex items-center gap-1.5 text-[10px] text-[var(--color-text-muted)]">
|
||||||
<span className="tabular-nums">
|
<span className="tabular-nums">
|
||||||
{Math.round(snapshot.usedRequests)} of {Math.round(snapshot.entitlementRequests)} used
|
{Math.round(snapshot.usedRequests)} of {Math.round(snapshot.entitlementRequests)} used
|
||||||
</span>
|
</span>
|
||||||
{snapshot.overage > 0 && (
|
{snapshot.overage > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="tabular-nums text-amber-400">
|
<span className="tabular-nums text-[var(--color-status-warning)]">
|
||||||
{Math.round(snapshot.overage)} overage
|
{Math.round(snapshot.overage)} overage
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{snapshot.resetDate && (
|
{snapshot.resetDate && (
|
||||||
<>
|
<>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span>Resets {formatResetDate(snapshot.resetDate)}</span>
|
<span>Resets {formatResetDate(snapshot.resetDate)}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -276,34 +276,34 @@ function AccountSection({ connection }: { connection: SidecarConnectionDiagnosti
|
|||||||
<div className="space-y-2.5">
|
<div className="space-y-2.5">
|
||||||
{/* Identity row */}
|
{/* Identity row */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<User className="size-3.5 text-zinc-500" />
|
<User className="size-3.5 text-[var(--color-text-muted)]" />
|
||||||
{hasLogin ? (
|
{hasLogin ? (
|
||||||
<div className="flex items-center gap-1.5 text-[12px]">
|
<div className="flex items-center gap-1.5 text-[12px]">
|
||||||
<span className="font-medium text-zinc-200">{account.login}</span>
|
<span className="font-medium text-[var(--color-text-primary)]">{account.login}</span>
|
||||||
{account.host && (
|
{account.host && (
|
||||||
<span className="text-zinc-500">· {account.host}</span>
|
<span className="text-[var(--color-text-muted)]">· {account.host}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-[12px] text-zinc-500">{account.statusMessage}</span>
|
<span className="text-[12px] text-[var(--color-text-muted)]">{account.statusMessage}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Organizations */}
|
{/* Organizations */}
|
||||||
{hasOrgs && (
|
{hasOrgs && (
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<Building2 className="mt-0.5 size-3.5 shrink-0 text-zinc-500" />
|
<Building2 className="mt-0.5 size-3.5 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
<div className="flex flex-wrap items-center gap-1">
|
<div className="flex flex-wrap items-center gap-1">
|
||||||
{visibleOrgs.map((org) => (
|
{visibleOrgs.map((org) => (
|
||||||
<span
|
<span
|
||||||
className="rounded-md bg-zinc-800 px-1.5 py-0.5 text-[10px] font-medium text-zinc-400"
|
className="rounded-md bg-[var(--color-surface-3)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-text-secondary)]"
|
||||||
key={org}
|
key={org}
|
||||||
>
|
>
|
||||||
{org}
|
{org}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{remainingOrgs > 0 && (
|
{remainingOrgs > 0 && (
|
||||||
<span className="text-[10px] text-zinc-600">+{remainingOrgs} more</span>
|
<span className="text-[10px] text-[var(--color-text-muted)]">+{remainingOrgs} more</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -323,10 +323,10 @@ export function CopilotStatusCard({
|
|||||||
|
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3 rounded-xl border border-[var(--color-border)] bg-zinc-900/40 px-4 py-3">
|
<div className="flex items-center gap-3 rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] px-4 py-3">
|
||||||
<Cpu className="size-4 text-zinc-600" />
|
<Cpu className="size-4 text-[var(--color-text-muted)]" />
|
||||||
<span className="text-[13px] text-zinc-500">Checking connection…</span>
|
<span className="text-[13px] text-[var(--color-text-muted)]">Checking connection…</span>
|
||||||
<RefreshCw className="ml-auto size-3.5 animate-spin text-zinc-600" />
|
<RefreshCw className="ml-auto size-3.5 animate-spin text-[var(--color-text-muted)]" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -347,7 +347,7 @@ export function CopilotStatusCard({
|
|||||||
{config.label}
|
{config.label}
|
||||||
</span>
|
</span>
|
||||||
{isHealthy && (
|
{isHealthy && (
|
||||||
<span className="text-[12px] text-zinc-500">
|
<span className="text-[12px] text-[var(--color-text-muted)]">
|
||||||
· {modelCount} model{modelCount === 1 ? '' : 's'} available
|
· {modelCount} model{modelCount === 1 ? '' : 's'} available
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -359,10 +359,10 @@ export function CopilotStatusCard({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{checkedLabel && (
|
{checkedLabel && (
|
||||||
<span className="text-[11px] text-zinc-600">{checkedLabel}</span>
|
<span className="text-[11px] text-[var(--color-text-muted)]">{checkedLabel}</span>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="flex size-6 items-center justify-center rounded-md text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300 disabled:opacity-50"
|
className="flex size-6 items-center justify-center rounded-md text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)] disabled:opacity-50"
|
||||||
disabled={isRefreshing}
|
disabled={isRefreshing}
|
||||||
onClick={onRefresh}
|
onClick={onRefresh}
|
||||||
title="Refresh connection status"
|
title="Refresh connection status"
|
||||||
@@ -381,11 +381,11 @@ export function CopilotStatusCard({
|
|||||||
{/* Usage & Quota (when healthy and callback provided) */}
|
{/* Usage & Quota (when healthy and callback provided) */}
|
||||||
{isHealthy && onGetQuota && (
|
{isHealthy && onGetQuota && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1.5 text-[11px] font-medium text-zinc-400">
|
<div className="flex items-center gap-1.5 text-[11px] font-medium text-[var(--color-text-secondary)]">
|
||||||
<BarChart3 className="size-3" />
|
<BarChart3 className="size-3" />
|
||||||
<span>Usage & Quota</span>
|
<span>Usage & Quota</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg border border-zinc-800/60 bg-zinc-900/40 px-3 py-2.5">
|
<div className="rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-glass)] px-3 py-2.5">
|
||||||
<QuotaSection onGetQuota={onGetQuota} />
|
<QuotaSection onGetQuota={onGetQuota} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -393,9 +393,9 @@ export function CopilotStatusCard({
|
|||||||
|
|
||||||
{/* Action hint for non-ready states */}
|
{/* Action hint for non-ready states */}
|
||||||
{!isHealthy && config.actionLabel && (
|
{!isHealthy && config.actionLabel && (
|
||||||
<div className="flex items-start gap-2 rounded-lg border border-zinc-800 bg-zinc-900/60 px-3 py-2.5">
|
<div className="flex items-start gap-2 rounded-lg border border-[var(--color-border)] bg-[var(--color-glass)] px-3 py-2.5">
|
||||||
<div className="mt-0.5 shrink-0">{config.actionIcon}</div>
|
<div className="mt-0.5 shrink-0">{config.actionIcon}</div>
|
||||||
<p className="text-[12px] leading-relaxed text-zinc-400">{config.actionLabel}</p>
|
<p className="text-[12px] leading-relaxed text-[var(--color-text-secondary)]">{config.actionLabel}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -403,7 +403,7 @@ export function CopilotStatusCard({
|
|||||||
{isHealthy && hasDetail && (
|
{isHealthy && hasDetail && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center gap-1.5 text-[11px] text-zinc-600 transition hover:text-zinc-400"
|
className="flex w-full items-center gap-1.5 text-[11px] text-[var(--color-text-muted)] transition-all duration-200 hover:text-[var(--color-text-secondary)]"
|
||||||
onClick={() => setShowDetails((prev) => !prev)}
|
onClick={() => setShowDetails((prev) => !prev)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -412,26 +412,26 @@ export function CopilotStatusCard({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showDetails && (
|
{showDetails && (
|
||||||
<div className="overflow-hidden rounded-lg border border-zinc-800/60 bg-zinc-900/40">
|
<div className="overflow-hidden rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-glass)]">
|
||||||
{connection.copilotCliPath && (
|
{connection.copilotCliPath && (
|
||||||
<div className="border-b border-zinc-800/40 px-3 py-2">
|
<div className="border-b border-[var(--color-border-subtle)] px-3 py-2">
|
||||||
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">CLI path</span>
|
<span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">CLI path</span>
|
||||||
<p className="mt-0.5 break-all font-mono text-[11px] text-zinc-400" title={connection.copilotCliPath}>
|
<p className="mt-0.5 break-all font-mono text-[11px] text-[var(--color-text-secondary)]" title={connection.copilotCliPath}>
|
||||||
{connection.copilotCliPath}
|
{connection.copilotCliPath}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasVersionInfo && connection.copilotCliVersion!.status === 'outdated' && connection.copilotCliVersion!.latestVersion && (
|
{hasVersionInfo && connection.copilotCliVersion!.status === 'outdated' && connection.copilotCliVersion!.latestVersion && (
|
||||||
<div className="border-b border-zinc-800/40 px-3 py-2">
|
<div className="border-b border-[var(--color-border-subtle)] px-3 py-2">
|
||||||
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Latest version</span>
|
<span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">Latest version</span>
|
||||||
<p className="mt-0.5 font-mono text-[11px] text-zinc-400">
|
<p className="mt-0.5 font-mono text-[11px] text-[var(--color-text-secondary)]">
|
||||||
{connection.copilotCliVersion!.latestVersion}
|
{connection.copilotCliVersion!.latestVersion}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="px-3 py-2">
|
<div className="px-3 py-2">
|
||||||
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Last checked</span>
|
<span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">Last checked</span>
|
||||||
<p className="mt-0.5 text-[11px] text-zinc-400">
|
<p className="mt-0.5 text-[11px] text-[var(--color-text-secondary)]">
|
||||||
{new Date(connection.checkedAt).toLocaleString()}
|
{new Date(connection.checkedAt).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -442,17 +442,17 @@ export function CopilotStatusCard({
|
|||||||
|
|
||||||
{/* Error detail for non-ready states */}
|
{/* Error detail for non-ready states */}
|
||||||
{!isHealthy && hasDetail && (
|
{!isHealthy && hasDetail && (
|
||||||
<div className="overflow-hidden rounded-lg border border-zinc-800/60 bg-zinc-900/40">
|
<div className="overflow-hidden rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-glass)]">
|
||||||
<div className="border-b border-zinc-800/40 px-3 py-2">
|
<div className="border-b border-[var(--color-border-subtle)] px-3 py-2">
|
||||||
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">CLI path</span>
|
<span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">CLI path</span>
|
||||||
<p className="mt-0.5 break-all font-mono text-[11px] text-zinc-400" title={connection.copilotCliPath}>
|
<p className="mt-0.5 break-all font-mono text-[11px] text-[var(--color-text-secondary)]" title={connection.copilotCliPath}>
|
||||||
{shortenPath(connection.copilotCliPath!)}
|
{shortenPath(connection.copilotCliPath!)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{connection.detail && (
|
{connection.detail && (
|
||||||
<div className="px-3 py-2">
|
<div className="px-3 py-2">
|
||||||
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Error detail</span>
|
<span className="text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">Error detail</span>
|
||||||
<p className="mt-0.5 break-words text-[11px] text-zinc-400">{connection.detail}</p>
|
<p className="mt-0.5 break-words text-[11px] text-[var(--color-text-secondary)]">{connection.detail}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,20 +81,20 @@ export function DiscoveredToolingModal({
|
|||||||
<div
|
<div
|
||||||
aria-labelledby="discovered-tooling-title"
|
aria-labelledby="discovered-tooling-title"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
className="fixed inset-0 z-50 flex items-center justify-center bg-[#07080e]/90 backdrop-blur-sm"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
>
|
>
|
||||||
<div className="flex max-h-[80vh] w-full max-w-lg flex-col rounded-xl border border-zinc-800 bg-zinc-900 shadow-2xl">
|
<div className="flex max-h-[80vh] w-full max-w-lg flex-col rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-zinc-800 px-5 py-4">
|
<div className="flex items-center justify-between border-b border-[var(--color-border)] px-5 py-4">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<FileSearch className="size-4 text-indigo-400" />
|
<FileSearch className="size-4 text-[var(--color-text-accent)]" />
|
||||||
<h2 id="discovered-tooling-title" className="text-[13px] font-semibold text-zinc-100">
|
<h2 id="discovered-tooling-title" className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">
|
||||||
MCP servers found in config files
|
MCP servers found in config files
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="flex size-7 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="flex size-7 items-center justify-center rounded-lg text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -104,7 +104,7 @@ export function DiscoveredToolingModal({
|
|||||||
|
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="flex-1 overflow-y-auto px-5 py-4">
|
<div className="flex-1 overflow-y-auto px-5 py-4">
|
||||||
<p className="mb-4 text-[12px] leading-relaxed text-zinc-500">
|
<p className="mb-4 text-[12px] leading-relaxed text-[var(--color-text-muted)]">
|
||||||
The following MCP servers were found in your config files. Accept the ones you want to
|
The following MCP servers were found in your config files. Accept the ones you want to
|
||||||
use, or dismiss those you don't need. Accepted servers become available for session tooling.
|
use, or dismiss those you don't need. Accepted servers become available for session tooling.
|
||||||
</p>
|
</p>
|
||||||
@@ -127,20 +127,20 @@ export function DiscoveredToolingModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-between border-t border-zinc-800 px-5 py-3">
|
<div className="flex items-center justify-between border-t border-[var(--color-border)] px-5 py-3">
|
||||||
<span className="text-[12px] text-zinc-600">
|
<span className="text-[12px] text-[var(--color-text-muted)]">
|
||||||
{totalPending} server{totalPending === 1 ? '' : 's'} pending review
|
{totalPending} server{totalPending === 1 ? '' : 's'} pending review
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="rounded-lg px-3 py-1.5 text-[13px] text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200"
|
className="rounded-lg px-3 py-1.5 text-[13px] text-[var(--color-text-secondary)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={handleDismissAll}
|
onClick={handleDismissAll}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Dismiss All
|
Dismiss All
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="rounded-lg bg-indigo-600 px-3 py-1.5 text-[13px] font-medium text-white transition hover:bg-indigo-500"
|
className="rounded-lg bg-[var(--color-accent)] px-3 py-1.5 text-[13px] font-medium text-white transition hover:bg-[var(--color-accent-sky)]"
|
||||||
onClick={handleAcceptAll}
|
onClick={handleAcceptAll}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -166,15 +166,15 @@ function DiscoveredGroup({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="mb-2 text-[10px] font-semibold uppercase tracking-wider text-zinc-600">
|
<div className="mb-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
{scopeLabel}
|
{scopeLabel}
|
||||||
</div>
|
</div>
|
||||||
{groups.map((group) => (
|
{groups.map((group) => (
|
||||||
<div className="mb-3" key={group.sourceLabel}>
|
<div className="mb-3" key={group.sourceLabel}>
|
||||||
<div className="mb-1.5 flex items-center gap-1.5 text-[11px] text-zinc-500">
|
<div className="mb-1.5 flex items-center gap-1.5 text-[11px] text-[var(--color-text-muted)]">
|
||||||
<span className="truncate font-medium">{group.sourceLabel}</span>
|
<span className="truncate font-medium">{group.sourceLabel}</span>
|
||||||
<span className="text-zinc-700">·</span>
|
<span className="text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="text-zinc-600">
|
<span className="text-[var(--color-text-muted)]">
|
||||||
{group.servers.length} server{group.servers.length === 1 ? '' : 's'}
|
{group.servers.length} server{group.servers.length === 1 ? '' : 's'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -211,22 +211,22 @@ function ServerRow({
|
|||||||
: server.url || 'No URL';
|
: server.url || 'No URL';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group flex items-center gap-3 rounded-lg border border-zinc-800/60 bg-zinc-800/20 px-3 py-2.5">
|
<div className="group flex items-center gap-3 rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-glass)] px-3 py-2.5">
|
||||||
<Server className="size-3.5 shrink-0 text-zinc-600" />
|
<Server className="size-3.5 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="truncate text-[13px] font-medium text-zinc-200">
|
<span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]">
|
||||||
{server.name}
|
{server.name}
|
||||||
</span>
|
</span>
|
||||||
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-zinc-500">
|
<span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-muted)]">
|
||||||
{server.transport}
|
{server.transport}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 truncate text-[12px] text-zinc-500">{detail}</p>
|
<p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">{detail}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
className="flex size-7 items-center justify-center rounded-md text-zinc-600 transition hover:bg-red-500/10 hover:text-red-400"
|
className="flex size-7 items-center justify-center rounded-md text-[var(--color-text-muted)] transition hover:bg-[var(--color-status-error)]/10 hover:text-[var(--color-status-error)]"
|
||||||
onClick={onDismiss}
|
onClick={onDismiss}
|
||||||
title="Dismiss"
|
title="Dismiss"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -234,7 +234,7 @@ function ServerRow({
|
|||||||
<XCircle className="size-3.5" />
|
<XCircle className="size-3.5" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="flex size-7 items-center justify-center rounded-md text-zinc-600 transition hover:bg-emerald-500/10 hover:text-emerald-400"
|
className="flex size-7 items-center justify-center rounded-md text-[var(--color-text-muted)] transition hover:bg-[var(--color-status-success)]/10 hover:text-[var(--color-status-success)]"
|
||||||
onClick={onAccept}
|
onClick={onAccept}
|
||||||
title="Accept"
|
title="Accept"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -677,11 +677,11 @@ function ToolbarPlugin({ disabled }: { disabled: boolean }) {
|
|||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-0.5 border-b border-zinc-700/50 px-2 py-1">
|
<div className="flex items-center gap-0.5 border-b border-[var(--color-border)]/50 px-2 py-1">
|
||||||
<ToolbarButton active={state.isBold} disabled={disabled} icon={<Bold className="size-3.5" />} onClick={formatBold} onMouseDown={preventFocus} title="Bold (Ctrl+B)" />
|
<ToolbarButton active={state.isBold} disabled={disabled} icon={<Bold className="size-3.5" />} onClick={formatBold} onMouseDown={preventFocus} title="Bold (Ctrl+B)" />
|
||||||
<ToolbarButton active={state.isItalic} disabled={disabled} icon={<Italic className="size-3.5" />} onClick={formatItalic} onMouseDown={preventFocus} title="Italic (Ctrl+I)" />
|
<ToolbarButton active={state.isItalic} disabled={disabled} icon={<Italic className="size-3.5" />} onClick={formatItalic} onMouseDown={preventFocus} title="Italic (Ctrl+I)" />
|
||||||
<ToolbarButton active={state.isCode} disabled={disabled} icon={<Code className="size-3.5" />} onClick={formatInlineCode} onMouseDown={preventFocus} title="Inline Code" />
|
<ToolbarButton active={state.isCode} disabled={disabled} icon={<Code className="size-3.5" />} onClick={formatInlineCode} onMouseDown={preventFocus} title="Inline Code" />
|
||||||
<div className="mx-1 h-4 w-px bg-zinc-700/50" />
|
<div className="mx-1 h-4 w-px bg-[var(--color-border)]/50" />
|
||||||
<ToolbarButton active={state.blockType === 'ul'} disabled={disabled} icon={<List className="size-3.5" />} onClick={toggleBulletList} onMouseDown={preventFocus} title="Bullet List" />
|
<ToolbarButton active={state.blockType === 'ul'} disabled={disabled} icon={<List className="size-3.5" />} onClick={toggleBulletList} onMouseDown={preventFocus} title="Bullet List" />
|
||||||
<ToolbarButton active={state.blockType === 'ol'} disabled={disabled} icon={<ListOrdered className="size-3.5" />} onClick={toggleNumberedList} onMouseDown={preventFocus} title="Numbered List" />
|
<ToolbarButton active={state.blockType === 'ol'} disabled={disabled} icon={<ListOrdered className="size-3.5" />} onClick={toggleNumberedList} onMouseDown={preventFocus} title="Numbered List" />
|
||||||
<ToolbarButton active={state.blockType === 'code'} disabled={disabled} icon={<Braces className="size-3.5" />} onClick={toggleCodeBlock} onMouseDown={preventFocus} title="Code Block" />
|
<ToolbarButton active={state.blockType === 'code'} disabled={disabled} icon={<Braces className="size-3.5" />} onClick={toggleCodeBlock} onMouseDown={preventFocus} title="Code Block" />
|
||||||
@@ -709,8 +709,8 @@ function ToolbarButton({
|
|||||||
aria-pressed={active}
|
aria-pressed={active}
|
||||||
className={`flex size-7 items-center justify-center rounded transition ${
|
className={`flex size-7 items-center justify-center rounded transition ${
|
||||||
active
|
active
|
||||||
? 'bg-indigo-600/30 text-indigo-300'
|
? 'bg-[var(--color-accent)]/30 text-[var(--color-accent-sky)]'
|
||||||
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
|
: 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]'
|
||||||
} ${disabled ? 'pointer-events-none opacity-50' : ''}`}
|
} ${disabled ? 'pointer-events-none opacity-50' : ''}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ export function NewSessionModal({
|
|||||||
const canCreate = projectId && patternId;
|
const canCreate = projectId && patternId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" role="dialog" aria-modal="true" aria-labelledby="new-session-title">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[#07080e]/90 backdrop-blur-sm" role="dialog" aria-modal="true" aria-labelledby="new-session-title">
|
||||||
<div className="w-full max-w-md rounded-xl border border-zinc-800 bg-zinc-900 shadow-2xl">
|
<div className="w-full max-w-md rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-zinc-800 px-5 py-4">
|
<div className="flex items-center justify-between border-b border-[var(--color-border)] px-5 py-4">
|
||||||
<h2 id="new-session-title" className="text-[13px] font-semibold text-zinc-100">New Session</h2>
|
<h2 id="new-session-title" className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">New Session</h2>
|
||||||
<button
|
<button
|
||||||
className="flex size-7 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="flex size-7 items-center justify-center rounded-lg text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -74,9 +74,9 @@ export function NewSessionModal({
|
|||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="space-y-4 px-5 py-5">
|
<div className="space-y-4 px-5 py-5">
|
||||||
<label className="block space-y-1.5">
|
<label className="block space-y-1.5">
|
||||||
<span className="text-[12px] font-medium text-zinc-400">Project</span>
|
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">Project</span>
|
||||||
<select
|
<select
|
||||||
className="w-full rounded-lg border border-zinc-700 bg-zinc-950 px-3 py-2 text-[13px] text-zinc-100 outline-none transition focus:border-indigo-500/50"
|
className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-0)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] outline-none transition focus:border-[var(--color-accent)]/50"
|
||||||
onChange={(e) => setProjectId(e.target.value)}
|
onChange={(e) => setProjectId(e.target.value)}
|
||||||
value={projectId}
|
value={projectId}
|
||||||
>
|
>
|
||||||
@@ -89,28 +89,28 @@ export function NewSessionModal({
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label className="block space-y-1.5">
|
<label className="block space-y-1.5">
|
||||||
<span className="text-[12px] font-medium text-zinc-400">Pattern</span>
|
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">Pattern</span>
|
||||||
<div className="space-y-1 rounded-lg border border-zinc-700 bg-zinc-950 p-1.5">
|
<div className="space-y-1 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-0)] p-1.5">
|
||||||
{availablePatterns.map((p) => (
|
{availablePatterns.map((p) => (
|
||||||
<div
|
<div
|
||||||
key={p.id}
|
key={p.id}
|
||||||
className={`flex cursor-pointer items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] transition ${
|
className={`flex cursor-pointer items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] transition ${
|
||||||
patternId === p.id
|
patternId === p.id
|
||||||
? 'bg-indigo-500/15 text-zinc-100 ring-1 ring-indigo-500/25'
|
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-primary)] ring-1 ring-[var(--color-border-glow)]'
|
||||||
: 'text-zinc-300 hover:bg-zinc-800/60'
|
: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-glass-hover)]'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setPatternId(p.id)}
|
onClick={() => setPatternId(p.id)}
|
||||||
>
|
>
|
||||||
<span className="flex-1 truncate">
|
<span className="flex-1 truncate">
|
||||||
{p.name}
|
{p.name}
|
||||||
<span className="ml-1.5 text-[11px] text-zinc-500">({p.mode})</span>
|
<span className="ml-1.5 text-[11px] text-[var(--color-text-muted)]">({p.mode})</span>
|
||||||
</span>
|
</span>
|
||||||
{onTogglePatternFavorite && (
|
{onTogglePatternFavorite && (
|
||||||
<button
|
<button
|
||||||
className={`shrink-0 transition ${
|
className={`shrink-0 transition ${
|
||||||
p.isFavorite
|
p.isFavorite
|
||||||
? 'text-amber-400 hover:text-amber-300'
|
? 'text-[var(--color-status-warning)] hover:text-[var(--color-status-warning)]'
|
||||||
: 'text-zinc-700 hover:text-zinc-400'
|
: 'text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -126,7 +126,7 @@ export function NewSessionModal({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{patternId && (
|
{patternId && (
|
||||||
<p className="text-[12px] text-zinc-600">
|
<p className="text-[12px] text-[var(--color-text-muted)]">
|
||||||
{availablePatterns.find((p) => p.id === patternId)?.description}
|
{availablePatterns.find((p) => p.id === patternId)?.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -134,16 +134,16 @@ export function NewSessionModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-end gap-2 border-t border-zinc-800 px-5 py-3">
|
<div className="flex items-center justify-end gap-2 border-t border-[var(--color-border)] px-5 py-3">
|
||||||
<button
|
<button
|
||||||
className="rounded-lg px-4 py-1.5 text-[13px] text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200"
|
className="rounded-lg px-4 py-1.5 text-[13px] text-[var(--color-text-secondary)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="rounded-lg bg-indigo-600 px-4 py-1.5 text-[13px] font-medium text-white transition hover:bg-indigo-500 disabled:cursor-not-allowed disabled:opacity-40"
|
className="rounded-lg bg-[var(--color-accent)] px-4 py-1.5 text-[13px] font-medium text-white transition hover:bg-[var(--color-accent-sky)] disabled:cursor-not-allowed disabled:opacity-40"
|
||||||
disabled={!canCreate}
|
disabled={!canCreate}
|
||||||
onClick={() => canCreate && onCreate(projectId, patternId)}
|
onClick={() => canCreate && onCreate(projectId, patternId)}
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -105,10 +105,10 @@ function InputField({
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}) {
|
}) {
|
||||||
const baseClasses =
|
const baseClasses =
|
||||||
'w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 placeholder-zinc-600 outline-none transition focus:border-indigo-500/50';
|
'w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none transition-all duration-200 focus:border-[var(--color-accent)]/50';
|
||||||
return (
|
return (
|
||||||
<label className="block space-y-1.5">
|
<label className="block space-y-1.5">
|
||||||
<span className="text-[12px] font-medium text-zinc-400">{label}</span>
|
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
|
||||||
{multiline ? (
|
{multiline ? (
|
||||||
<textarea
|
<textarea
|
||||||
className={`${baseClasses} min-h-20 resize-y`}
|
className={`${baseClasses} min-h-20 resize-y`}
|
||||||
@@ -242,17 +242,17 @@ export function PatternEditor({
|
|||||||
<div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] pb-3 pl-5 pr-36 pt-3">
|
<div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] pb-3 pl-5 pr-36 pt-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200"
|
className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={onBack}
|
onClick={onBack}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="size-4" />
|
<ChevronLeft className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-[13px] font-semibold text-zinc-100">
|
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">
|
||||||
{pattern.name || 'Untitled pattern'}
|
{pattern.name || 'Untitled pattern'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-[12px] text-zinc-500">
|
<p className="text-[12px] text-[var(--color-text-muted)]">
|
||||||
{isBuiltin ? 'Built-in pattern' : 'Custom pattern'}
|
{isBuiltin ? 'Built-in pattern' : 'Custom pattern'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -260,7 +260,7 @@ export function PatternEditor({
|
|||||||
<div className="no-drag flex items-center gap-2">
|
<div className="no-drag flex items-center gap-2">
|
||||||
{!isBuiltin && onDelete && (
|
{!isBuiltin && onDelete && (
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] text-red-400 transition hover:bg-red-500/10"
|
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] text-[var(--color-status-error)] transition-all duration-200 hover:bg-[var(--color-status-error)]/10"
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -269,7 +269,7 @@ export function PatternEditor({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="rounded-lg bg-indigo-600 px-4 py-1.5 text-[13px] font-medium text-white transition hover:bg-indigo-500"
|
className="rounded-lg bg-[var(--color-accent)] px-4 py-1.5 text-[13px] font-medium text-white transition-all duration-200 hover:bg-[var(--color-accent-sky)]"
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -290,8 +290,8 @@ export function PatternEditor({
|
|||||||
<div
|
<div
|
||||||
className={`flex items-start gap-2 rounded-lg px-3 py-2 text-[12px] ${
|
className={`flex items-start gap-2 rounded-lg px-3 py-2 text-[12px] ${
|
||||||
issue.level === 'error'
|
issue.level === 'error'
|
||||||
? 'bg-red-500/10 text-red-300'
|
? 'bg-[var(--color-status-error)]/10 text-[var(--color-status-error)]'
|
||||||
: 'bg-amber-500/10 text-amber-300'
|
: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]'
|
||||||
}`}
|
}`}
|
||||||
key={`${issue.field ?? 'v'}-${i}`}
|
key={`${issue.field ?? 'v'}-${i}`}
|
||||||
>
|
>
|
||||||
@@ -301,7 +301,7 @@ export function PatternEditor({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center gap-2 rounded-lg bg-emerald-500/10 px-3 py-2 text-[12px] text-emerald-300">
|
<div className="flex items-center gap-2 rounded-lg bg-[var(--color-status-success)]/10 px-3 py-2 text-[12px] text-[var(--color-status-success)]">
|
||||||
<CheckCircle className="size-3.5" />
|
<CheckCircle className="size-3.5" />
|
||||||
Pattern is valid
|
Pattern is valid
|
||||||
</div>
|
</div>
|
||||||
@@ -310,11 +310,11 @@ export function PatternEditor({
|
|||||||
|
|
||||||
{/* Graph canvas */}
|
{/* Graph canvas */}
|
||||||
<div className="flex items-center justify-between px-5 pt-4">
|
<div className="flex items-center justify-between px-5 pt-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Topology
|
Topology
|
||||||
</h4>
|
</h4>
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1.5 rounded-lg px-2.5 py-1 text-[12px] font-medium text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200"
|
className="flex items-center gap-1.5 rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={addAgent}
|
onClick={addAgent}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -335,11 +335,11 @@ export function PatternEditor({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scrollable settings below graph */}
|
{/* Scrollable settings below graph */}
|
||||||
<div className="max-h-[45%] overflow-y-auto border-t border-zinc-800/50 px-5 py-5">
|
<div className="max-h-[45%] overflow-y-auto border-t border-[var(--color-border-subtle)] px-5 py-5">
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{/* General */}
|
{/* General */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
General
|
General
|
||||||
</h4>
|
</h4>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
@@ -360,7 +360,7 @@ export function PatternEditor({
|
|||||||
|
|
||||||
{/* Mode selector */}
|
{/* Mode selector */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Orchestration Mode
|
Orchestration Mode
|
||||||
</h4>
|
</h4>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
@@ -372,12 +372,12 @@ export function PatternEditor({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`flex flex-col rounded-xl border p-2.5 text-left transition ${
|
className={`flex flex-col rounded-xl border p-2.5 text-left transition-all duration-200 ${
|
||||||
selected
|
selected
|
||||||
? 'border-indigo-500/40 bg-indigo-500/5 ring-1 ring-indigo-500/20'
|
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] ring-1 ring-[var(--color-border-glow)]'
|
||||||
: disabled
|
: disabled
|
||||||
? 'cursor-not-allowed border-zinc-800/50 opacity-40'
|
? 'cursor-not-allowed border-[var(--color-border-subtle)] opacity-40'
|
||||||
: 'border-zinc-800 hover:border-zinc-700 hover:bg-zinc-900/60'
|
: 'border-[var(--color-border)] hover:border-[var(--color-border)] hover:bg-[var(--color-glass)]'
|
||||||
}`}
|
}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
key={mode}
|
key={mode}
|
||||||
@@ -386,17 +386,17 @@ export function PatternEditor({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Icon
|
<Icon
|
||||||
className={`size-3.5 ${selected ? 'text-indigo-400' : 'text-zinc-500'}`}
|
className={`size-3.5 ${selected ? 'text-[var(--color-text-accent)]' : 'text-[var(--color-text-muted)]'}`}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={`text-[11px] font-semibold ${
|
className={`text-[11px] font-semibold ${
|
||||||
selected ? 'text-indigo-200' : 'text-zinc-300'
|
selected ? 'text-[var(--color-text-accent)]' : 'text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{info.label}
|
{info.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-[10px] leading-snug text-zinc-500">
|
<p className="mt-1 text-[10px] leading-snug text-[var(--color-text-muted)]">
|
||||||
{info.description}
|
{info.description}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
@@ -407,12 +407,11 @@ export function PatternEditor({
|
|||||||
|
|
||||||
{/* Approval checkpoints */}
|
{/* Approval checkpoints */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Approval Checkpoints
|
Approval Checkpoints
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<p className="text-[11px] leading-relaxed text-zinc-600">
|
<p className="text-[11px] leading-relaxed text-[var(--color-text-muted)]">
|
||||||
Pause the run for human review before risky actions or publishing responses.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -426,15 +425,15 @@ export function PatternEditor({
|
|||||||
scopedAgentIds={checkpointAgentIds('tool-call')}
|
scopedAgentIds={checkpointAgentIds('tool-call')}
|
||||||
onScopeChange={(agentIds) => setCheckpointAgentScope('tool-call', agentIds)}
|
onScopeChange={(agentIds) => setCheckpointAgentScope('tool-call', agentIds)}
|
||||||
>
|
>
|
||||||
<div className="mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-zinc-500">
|
<div className="mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Auto-approved tools
|
Auto-approved tools
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-3 text-[11px] leading-relaxed text-zinc-600">
|
<p className="mb-3 text-[11px] leading-relaxed text-[var(--color-text-muted)]">
|
||||||
Tools marked as auto-approved will skip manual review.
|
Tools marked as auto-approved will skip manual review.
|
||||||
Sessions can override these defaults from the Activity panel.
|
Sessions can override these defaults from the Activity panel.
|
||||||
</p>
|
</p>
|
||||||
{approvalTools.length === 0 ? (
|
{approvalTools.length === 0 ? (
|
||||||
<p className="py-2 text-center text-[11px] text-zinc-600">
|
<p className="py-2 text-center text-[11px] text-[var(--color-text-muted)]">
|
||||||
No tools available yet. Connect MCP servers or wait for runtime capabilities to load.
|
No tools available yet. Connect MCP servers or wait for runtime capabilities to load.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@@ -462,9 +461,9 @@ export function PatternEditor({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right column: node inspector */}
|
{/* Right column: node inspector */}
|
||||||
<div className="w-[320px] shrink-0 overflow-y-auto border-l border-zinc-800/50 bg-zinc-900/30">
|
<div className="w-[320px] shrink-0 overflow-y-auto border-l border-[var(--color-border-subtle)] bg-[var(--color-glass)]">
|
||||||
<div className="border-b border-zinc-800/50 px-4 py-3">
|
<div className="border-b border-[var(--color-border-subtle)] px-4 py-3">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Inspector
|
Inspector
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
@@ -518,26 +517,26 @@ function ApprovalCheckpointRow({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-zinc-800 bg-zinc-900/50 p-4">
|
<div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] p-4">
|
||||||
<button className="flex w-full items-center gap-3 text-left" onClick={() => onToggle(!enabled)} type="button">
|
<button className="flex w-full items-center gap-3 text-left" onClick={() => onToggle(!enabled)} type="button">
|
||||||
<ShieldCheck className={`size-4 shrink-0 ${enabled ? 'text-indigo-400' : 'text-zinc-600'}`} />
|
<ShieldCheck className={`size-4 shrink-0 ${enabled ? 'text-[var(--color-text-accent)]' : 'text-[var(--color-text-muted)]'}`} />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<span className="text-[12px] font-medium text-zinc-200">{label}</span>
|
<span className="text-[12px] font-medium text-[var(--color-text-primary)]">{label}</span>
|
||||||
<p className="text-[11px] text-zinc-500">{description}</p>
|
<p className="text-[11px] text-[var(--color-text-muted)]">{description}</p>
|
||||||
</div>
|
</div>
|
||||||
<ToggleSwitch enabled={enabled} />
|
<ToggleSwitch enabled={enabled} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Agent scope selector */}
|
{/* Agent scope selector */}
|
||||||
{enabled && agents.length > 1 && (
|
{enabled && agents.length > 1 && (
|
||||||
<div className="mt-3 border-t border-zinc-800/50 pt-3">
|
<div className="mt-3 border-t border-[var(--color-border-subtle)] pt-3">
|
||||||
<div className="mb-2 flex items-center gap-2">
|
<div className="mb-2 flex items-center gap-2">
|
||||||
<span className="text-[11px] font-medium text-zinc-400">Scope</span>
|
<span className="text-[11px] font-medium text-[var(--color-text-secondary)]">Scope</span>
|
||||||
<button
|
<button
|
||||||
className={`rounded-full px-2 py-0.5 text-[10px] font-medium transition ${
|
className={`rounded-full px-2 py-0.5 text-[10px] font-medium transition-all duration-200 ${
|
||||||
isAllAgents
|
isAllAgents
|
||||||
? 'bg-indigo-500/15 text-indigo-300'
|
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
|
||||||
: 'bg-zinc-800 text-zinc-500 hover:text-zinc-400'
|
: 'bg-[var(--color-surface-3)] text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onScopeChange(undefined)}
|
onClick={() => onScopeChange(undefined)}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -545,10 +544,10 @@ function ApprovalCheckpointRow({
|
|||||||
All agents
|
All agents
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`rounded-full px-2 py-0.5 text-[10px] font-medium transition ${
|
className={`rounded-full px-2 py-0.5 text-[10px] font-medium transition-all duration-200 ${
|
||||||
!isAllAgents
|
!isAllAgents
|
||||||
? 'bg-indigo-500/15 text-indigo-300'
|
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
|
||||||
: 'bg-zinc-800 text-zinc-500 hover:text-zinc-400'
|
: 'bg-[var(--color-surface-3)] text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onScopeChange([])}
|
onClick={() => onScopeChange([])}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -563,10 +562,10 @@ function ApprovalCheckpointRow({
|
|||||||
const isSelected = scopedAgentIds?.includes(agent.id) ?? false;
|
const isSelected = scopedAgentIds?.includes(agent.id) ?? false;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`rounded-full px-2.5 py-1 text-[11px] font-medium transition ${
|
className={`rounded-full px-2.5 py-1 text-[11px] font-medium transition-all duration-200 ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'bg-indigo-500/20 text-indigo-300 ring-1 ring-indigo-500/30'
|
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)] ring-1 ring-[var(--color-border-glow)]'
|
||||||
: 'bg-zinc-800 text-zinc-500 hover:bg-zinc-700 hover:text-zinc-400'
|
: 'bg-[var(--color-surface-3)] text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
key={agent.id}
|
key={agent.id}
|
||||||
onClick={() => toggleAgentScope(agent.id)}
|
onClick={() => toggleAgentScope(agent.id)}
|
||||||
@@ -583,7 +582,7 @@ function ApprovalCheckpointRow({
|
|||||||
|
|
||||||
{/* Optional additional content (e.g. tool auto-approval list) */}
|
{/* Optional additional content (e.g. tool auto-approval list) */}
|
||||||
{enabled && children && (
|
{enabled && children && (
|
||||||
<div className="mt-3 border-t border-zinc-800/50 pt-3">
|
<div className="mt-3 border-t border-[var(--color-border-subtle)] pt-3">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -620,7 +619,7 @@ function ToolApprovalGroupedList({
|
|||||||
{groups.map((group, i) => (
|
{groups.map((group, i) => (
|
||||||
<div key={group.kind}>
|
<div key={group.kind}>
|
||||||
{showHeaders && (
|
{showHeaders && (
|
||||||
<div className={`text-[9px] font-semibold uppercase tracking-wider text-zinc-600 ${i > 0 ? 'mt-3' : ''} mb-1`}>
|
<div className={`text-[9px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)] ${i > 0 ? 'mt-3' : ''} mb-1`}>
|
||||||
{approvalKindLabels[group.kind]}
|
{approvalKindLabels[group.kind]}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -650,13 +649,13 @@ function ToolApprovalToggleRow({
|
|||||||
const detail = tool.description || (tool.providerNames.length > 0 ? tool.providerNames.join(', ') : undefined);
|
const detail = tool.description || (tool.providerNames.length > 0 ? tool.providerNames.join(', ') : undefined);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center gap-2.5 rounded-lg px-2 py-1.5 text-left transition hover:bg-zinc-800/60"
|
className="flex w-full items-center gap-2.5 rounded-lg px-2 py-1.5 text-left transition-all duration-200 hover:bg-[var(--color-surface-3)]/60"
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<span className="truncate text-[12px] font-medium text-zinc-300">{tool.label}</span>
|
<span className="truncate text-[12px] font-medium text-[var(--color-text-secondary)]">{tool.label}</span>
|
||||||
{detail && <div className="truncate text-[10px] text-zinc-600">{detail}</div>}
|
{detail && <div className="truncate text-[10px] text-[var(--color-text-muted)]">{detail}</div>}
|
||||||
</div>
|
</div>
|
||||||
<ToggleSwitch enabled={enabled} />
|
<ToggleSwitch enabled={enabled} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -115,16 +115,16 @@ export function ProjectSettingsPanel({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
|
<div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
|
||||||
<button
|
<button
|
||||||
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200"
|
className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="size-4" />
|
<ChevronLeft className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
<h2 className="text-[13px] font-semibold text-zinc-100">
|
<h2 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">
|
||||||
Project Settings
|
Project Settings
|
||||||
<span className="ml-2 font-normal text-zinc-500">·</span>
|
<span className="ml-2 font-normal text-[var(--color-text-muted)]">·</span>
|
||||||
<span className="ml-2 font-normal text-zinc-400">{project.name}</span>
|
<span className="ml-2 font-normal text-[var(--color-text-secondary)]">{project.name}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ export function ProjectSettingsPanel({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{navGroups.map((group) => (
|
{navGroups.map((group) => (
|
||||||
<div key={group.label}>
|
<div key={group.label}>
|
||||||
<span className="mb-1 block px-3 text-[10px] font-semibold uppercase tracking-wider text-zinc-600">
|
<span className="mb-1 block px-3 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
{group.label}
|
{group.label}
|
||||||
</span>
|
</span>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
@@ -144,10 +144,10 @@ export function ProjectSettingsPanel({
|
|||||||
const badge = sectionBadge(item.id);
|
const badge = sectionBadge(item.id);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition ${
|
className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition-all duration-200 ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-zinc-800 font-medium text-zinc-100'
|
? 'bg-[var(--color-surface-3)] font-medium text-[var(--color-text-primary)]'
|
||||||
: 'text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-300'
|
: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -156,7 +156,7 @@ export function ProjectSettingsPanel({
|
|||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span className={isActive ? 'text-zinc-300' : 'text-zinc-500'}>{item.icon}</span>
|
<span className={isActive ? 'text-[var(--color-text-secondary)]' : 'text-[var(--color-text-muted)]'}>{item.icon}</span>
|
||||||
<span className="flex-1 truncate">{item.label}</span>
|
<span className="flex-1 truncate">{item.label}</span>
|
||||||
{badge}
|
{badge}
|
||||||
</button>
|
</button>
|
||||||
@@ -167,13 +167,13 @@ export function ProjectSettingsPanel({
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Danger zone at the bottom */}
|
{/* Danger zone at the bottom */}
|
||||||
<div className="border-t border-zinc-800 pt-3">
|
<div className="border-t border-[var(--color-border)] pt-3">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition ${
|
className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition-all duration-200 ${
|
||||||
activeSection === 'danger-zone'
|
activeSection === 'danger-zone'
|
||||||
? 'bg-zinc-800 font-medium text-red-400'
|
? 'bg-[var(--color-surface-3)] font-medium text-[var(--color-status-error)]'
|
||||||
: 'text-zinc-500 hover:bg-zinc-800/50 hover:text-red-400'
|
: 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-status-error)]'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveSection('danger-zone');
|
setActiveSection('danger-zone');
|
||||||
@@ -240,7 +240,7 @@ export function ProjectSettingsPanel({
|
|||||||
|
|
||||||
function CountBadge({ count, total }: { count: number; total?: number }) {
|
function CountBadge({ count, total }: { count: number; total?: number }) {
|
||||||
return (
|
return (
|
||||||
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[10px] font-medium text-zinc-500">
|
<span className="rounded-full bg-[var(--color-surface-3)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
|
||||||
{total !== undefined ? `${count}/${total}` : count}
|
{total !== undefined ? `${count}/${total}` : count}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -248,7 +248,7 @@ function CountBadge({ count, total }: { count: number; total?: number }) {
|
|||||||
|
|
||||||
function PendingBadge({ count }: { count: number }) {
|
function PendingBadge({ count }: { count: number }) {
|
||||||
return (
|
return (
|
||||||
<span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-medium text-amber-400">
|
<span className="rounded-full bg-[var(--color-status-warning)]/10 px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-status-warning)]">
|
||||||
{count} new
|
{count} new
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -263,12 +263,12 @@ function OverviewContent({ project }: { project: ProjectRecord }) {
|
|||||||
description="Project details and git status."
|
description="Project details and git status."
|
||||||
title="Overview"
|
title="Overview"
|
||||||
/>
|
/>
|
||||||
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 px-5 py-4 space-y-3">
|
<div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] px-5 py-4 space-y-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<FolderOpen className="size-5 shrink-0 text-indigo-400" />
|
<FolderOpen className="size-5 shrink-0 text-[var(--color-text-accent)]" />
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="text-[13px] font-medium text-zinc-200">{project.name}</div>
|
<div className="text-[13px] font-medium text-[var(--color-text-primary)]">{project.name}</div>
|
||||||
<div className="mt-0.5 truncate text-[12px] text-zinc-500">{project.path}</div>
|
<div className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">{project.path}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{project.git && <ProjectGitInfo git={project.git} />}
|
{project.git && <ProjectGitInfo git={project.git} />}
|
||||||
@@ -280,7 +280,7 @@ function OverviewContent({ project }: { project: ProjectRecord }) {
|
|||||||
function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
|
function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
|
||||||
if (git.status === 'not-repository') {
|
if (git.status === 'not-repository') {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 text-[12px] text-zinc-600">
|
<div className="flex items-center gap-2 text-[12px] text-[var(--color-text-muted)]">
|
||||||
<GitBranch className="size-3.5" />
|
<GitBranch className="size-3.5" />
|
||||||
Not a git repository
|
Not a git repository
|
||||||
</div>
|
</div>
|
||||||
@@ -289,7 +289,7 @@ function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
|
|||||||
|
|
||||||
if (git.status === 'git-missing') {
|
if (git.status === 'git-missing') {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 text-[12px] text-amber-500/70">
|
<div className="flex items-center gap-2 text-[12px] text-[var(--color-status-warning)]">
|
||||||
<AlertTriangle className="size-3.5" />
|
<AlertTriangle className="size-3.5" />
|
||||||
Git is not installed
|
Git is not installed
|
||||||
</div>
|
</div>
|
||||||
@@ -298,7 +298,7 @@ function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
|
|||||||
|
|
||||||
if (git.status === 'error') {
|
if (git.status === 'error') {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 text-[12px] text-red-400/70">
|
<div className="flex items-center gap-2 text-[12px] text-[var(--color-status-error)]">
|
||||||
<AlertTriangle className="size-3.5" />
|
<AlertTriangle className="size-3.5" />
|
||||||
{git.errorMessage ?? 'Git error'}
|
{git.errorMessage ?? 'Git error'}
|
||||||
</div>
|
</div>
|
||||||
@@ -312,12 +312,12 @@ function ProjectGitInfo({ git }: { git: ProjectGitContext }) {
|
|||||||
if (git.behind) parts.push(`${git.behind} behind`);
|
if (git.behind) parts.push(`${git.behind} behind`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 text-[12px] text-zinc-400">
|
<div className="flex items-center gap-2 text-[12px] text-[var(--color-text-secondary)]">
|
||||||
<GitBranch className="size-3.5 shrink-0" />
|
<GitBranch className="size-3.5 shrink-0" />
|
||||||
<span>{branchLabel}</span>
|
<span>{branchLabel}</span>
|
||||||
{git.isDirty && <Circle className="size-1.5 shrink-0 fill-amber-500 text-amber-500" />}
|
{git.isDirty && <Circle className="size-1.5 shrink-0 fill-[var(--color-status-warning)] text-[var(--color-status-warning)]" />}
|
||||||
{parts.length > 0 && (
|
{parts.length > 0 && (
|
||||||
<span className="text-zinc-600">· {parts.join(' · ')}</span>
|
<span className="text-[var(--color-text-muted)]">· {parts.join(' · ')}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -343,7 +343,7 @@ function InstructionsContent({
|
|||||||
|
|
||||||
{instructions.length === 0 ? (
|
{instructions.length === 0 ? (
|
||||||
<EmptyState>
|
<EmptyState>
|
||||||
No instruction files found. Add a <code className="text-zinc-400">.github/copilot-instructions.md</code> or <code className="text-zinc-400">AGENTS.md</code> file to your project root.
|
No instruction files found. Add a <code className="text-[var(--color-text-secondary)]">.github/copilot-instructions.md</code> or <code className="text-[var(--color-text-secondary)]">AGENTS.md</code> file to your project root.
|
||||||
</EmptyState>
|
</EmptyState>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -361,28 +361,28 @@ function InstructionCard({ instruction }: { instruction: ProjectInstructionFile
|
|||||||
const isLong = instruction.content.length > 300;
|
const isLong = instruction.content.length > 300;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40">
|
<div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)]">
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center gap-3 px-5 py-3.5 text-left transition hover:bg-zinc-900/60"
|
className="flex w-full items-center gap-3 px-5 py-3.5 text-left transition-all duration-200 hover:bg-[var(--color-glass)]"
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<FileCode2 className="size-4 shrink-0 text-indigo-400" />
|
<FileCode2 className="size-4 shrink-0 text-[var(--color-text-accent)]" />
|
||||||
<span className="flex-1 text-[13px] font-medium text-zinc-200">{instruction.sourcePath}</span>
|
<span className="flex-1 text-[13px] font-medium text-[var(--color-text-primary)]">{instruction.sourcePath}</span>
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`size-3.5 shrink-0 text-zinc-500 transition-transform ${expanded ? 'rotate-180' : ''}`}
|
className={`size-3.5 shrink-0 text-[var(--color-text-muted)] transition-transform ${expanded ? 'rotate-180' : ''}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="border-t border-zinc-800 px-5 py-4">
|
<div className="border-t border-[var(--color-border)] px-5 py-4">
|
||||||
<pre className="whitespace-pre-wrap text-[11px] leading-relaxed text-zinc-400">
|
<pre className="whitespace-pre-wrap text-[11px] leading-relaxed text-[var(--color-text-secondary)]">
|
||||||
{instruction.content}
|
{instruction.content}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!expanded && (
|
{!expanded && (
|
||||||
<div className="px-5 pb-3">
|
<div className="px-5 pb-3">
|
||||||
<p className={`text-[11px] leading-relaxed text-zinc-500 ${isLong ? 'line-clamp-2' : ''}`}>
|
<p className={`text-[11px] leading-relaxed text-[var(--color-text-muted)] ${isLong ? 'line-clamp-2' : ''}`}>
|
||||||
{instruction.content}
|
{instruction.content}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -415,12 +415,12 @@ function AgentsContent({
|
|||||||
|
|
||||||
{agents.length === 0 ? (
|
{agents.length === 0 ? (
|
||||||
<EmptyState>
|
<EmptyState>
|
||||||
No custom agents found. Add <code className="text-zinc-400">.agent.md</code> files to <code className="text-zinc-400">.github/agents/</code> in your project.
|
No custom agents found. Add <code className="text-[var(--color-text-secondary)]">.agent.md</code> files to <code className="text-[var(--color-text-secondary)]">.github/agents/</code> in your project.
|
||||||
</EmptyState>
|
</EmptyState>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{agents.length > 1 && (
|
{agents.length > 1 && (
|
||||||
<div className="mb-3 text-[11px] text-zinc-500">
|
<div className="mb-3 text-[11px] text-[var(--color-text-muted)]">
|
||||||
{enabledCount} of {agents.length} agent{agents.length === 1 ? '' : 's'} enabled
|
{enabledCount} of {agents.length} agent{agents.length === 1 ? '' : 's'} enabled
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -449,26 +449,26 @@ function AgentCard({
|
|||||||
return (
|
return (
|
||||||
<div className={`rounded-xl border px-5 py-4 transition ${
|
<div className={`rounded-xl border px-5 py-4 transition ${
|
||||||
agent.enabled
|
agent.enabled
|
||||||
? 'border-zinc-800 bg-zinc-900/40'
|
? 'border-[var(--color-border)] bg-[var(--color-glass)]'
|
||||||
: 'border-zinc-800/50 bg-zinc-900/20 opacity-60'
|
: 'border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]/20 opacity-60'
|
||||||
}`}>
|
}`}>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<Sparkles className={`mt-0.5 size-4 shrink-0 ${agent.enabled ? 'text-amber-400' : 'text-zinc-600'}`} />
|
<Sparkles className={`mt-0.5 size-4 shrink-0 ${agent.enabled ? 'text-[var(--color-status-warning)]' : 'text-[var(--color-text-muted)]'}`} />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[13px] font-medium text-zinc-200">
|
<span className="text-[13px] font-medium text-[var(--color-text-primary)]">
|
||||||
{agent.displayName ?? agent.name}
|
{agent.displayName ?? agent.name}
|
||||||
</span>
|
</span>
|
||||||
{agent.tools && agent.tools.length > 0 && (
|
{agent.tools && agent.tools.length > 0 && (
|
||||||
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium text-zinc-500">
|
<span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
|
||||||
{agent.tools.length} tool{agent.tools.length === 1 ? '' : 's'}
|
{agent.tools.length} tool{agent.tools.length === 1 ? '' : 's'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{agent.description && (
|
{agent.description && (
|
||||||
<p className="mt-1 text-[12px] leading-relaxed text-zinc-500">{agent.description}</p>
|
<p className="mt-1 text-[12px] leading-relaxed text-[var(--color-text-muted)]">{agent.description}</p>
|
||||||
)}
|
)}
|
||||||
<p className="mt-1 text-[11px] text-zinc-600">{agent.sourcePath}</p>
|
<p className="mt-1 text-[11px] text-[var(--color-text-muted)]">{agent.sourcePath}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
aria-label={agent.enabled ? `Disable ${agent.name}` : `Enable ${agent.name}`}
|
aria-label={agent.enabled ? `Disable ${agent.name}` : `Enable ${agent.name}`}
|
||||||
@@ -504,7 +504,7 @@ function PromptsContent({
|
|||||||
|
|
||||||
{promptFiles.length === 0 ? (
|
{promptFiles.length === 0 ? (
|
||||||
<EmptyState>
|
<EmptyState>
|
||||||
No prompt files found. Add <code className="text-zinc-400">.prompt.md</code> files to <code className="text-zinc-400">.github/prompts/</code> in your project.
|
No prompt files found. Add <code className="text-[var(--color-text-secondary)]">.prompt.md</code> files to <code className="text-[var(--color-text-secondary)]">.github/prompts/</code> in your project.
|
||||||
</EmptyState>
|
</EmptyState>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -519,27 +519,27 @@ function PromptsContent({
|
|||||||
|
|
||||||
function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
|
function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 px-5 py-4">
|
<div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] px-5 py-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<FileText className="mt-0.5 size-4 shrink-0 text-emerald-400" />
|
<FileText className="mt-0.5 size-4 shrink-0 text-[var(--color-status-success)]" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[13px] font-medium text-zinc-200">{prompt.name}</span>
|
<span className="text-[13px] font-medium text-[var(--color-text-primary)]">{prompt.name}</span>
|
||||||
{prompt.variables.length > 0 && (
|
{prompt.variables.length > 0 && (
|
||||||
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium text-zinc-500">
|
<span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
|
||||||
{prompt.variables.length} variable{prompt.variables.length === 1 ? '' : 's'}
|
{prompt.variables.length} variable{prompt.variables.length === 1 ? '' : 's'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{prompt.description && (
|
{prompt.description && (
|
||||||
<p className="mt-1 text-[12px] leading-relaxed text-zinc-500">{prompt.description}</p>
|
<p className="mt-1 text-[12px] leading-relaxed text-[var(--color-text-muted)]">{prompt.description}</p>
|
||||||
)}
|
)}
|
||||||
{prompt.variables.length > 0 && (
|
{prompt.variables.length > 0 && (
|
||||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||||
{prompt.variables.map((v) => (
|
{prompt.variables.map((v) => (
|
||||||
<span
|
<span
|
||||||
key={v.name}
|
key={v.name}
|
||||||
className="rounded-md bg-zinc-800 px-2 py-0.5 text-[10px] font-medium text-zinc-400"
|
className="rounded-md bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-text-secondary)]"
|
||||||
title={v.placeholder}
|
title={v.placeholder}
|
||||||
>
|
>
|
||||||
{v.name}
|
{v.name}
|
||||||
@@ -547,7 +547,7 @@ function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="mt-1.5 text-[11px] text-zinc-600">{prompt.sourcePath}</p>
|
<p className="mt-1.5 text-[11px] text-[var(--color-text-muted)]">{prompt.sourcePath}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -607,14 +607,14 @@ function McpServersContent({
|
|||||||
{pending.length > 1 && (
|
{pending.length > 1 && (
|
||||||
<div className="mt-3 flex items-center gap-2">
|
<div className="mt-3 flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="rounded-lg bg-emerald-500/10 px-3 py-1.5 text-[12px] font-medium text-emerald-400 transition hover:bg-emerald-500/20"
|
className="rounded-lg bg-[var(--color-status-success)]/10 px-3 py-1.5 text-[12px] font-medium text-[var(--color-status-success)] transition-all duration-200 hover:bg-[var(--color-status-success)]/20"
|
||||||
onClick={() => onResolve(pending.map((s) => s.id), 'accept')}
|
onClick={() => onResolve(pending.map((s) => s.id), 'accept')}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Accept all ({pending.length})
|
Accept all ({pending.length})
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="rounded-lg px-3 py-1.5 text-[12px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="rounded-lg px-3 py-1.5 text-[12px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
|
||||||
onClick={() => onResolve(pending.map((s) => s.id), 'dismiss')}
|
onClick={() => onResolve(pending.map((s) => s.id), 'dismiss')}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -645,31 +645,31 @@ function DiscoveredServerRow({
|
|||||||
: server.url || 'No URL';
|
: server.url || 'No URL';
|
||||||
|
|
||||||
const statusBadge = status === 'accepted'
|
const statusBadge = status === 'accepted'
|
||||||
? 'bg-emerald-500/10 text-emerald-400'
|
? 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]'
|
||||||
: 'bg-amber-500/10 text-amber-400';
|
: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3 rounded-xl border border-transparent px-4 py-3 hover:border-zinc-800 hover:bg-zinc-900">
|
<div className="flex items-center gap-3 rounded-xl border border-transparent px-4 py-3 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]">
|
||||||
<Server className="size-4 shrink-0 text-zinc-600" />
|
<Server className="size-4 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="truncate text-[13px] font-medium text-zinc-200">{server.name}</span>
|
<span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{server.name}</span>
|
||||||
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-zinc-400">
|
<span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-secondary)]">
|
||||||
{server.transport}
|
{server.transport}
|
||||||
</span>
|
</span>
|
||||||
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}>
|
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}>
|
||||||
{status}
|
{status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 truncate text-[12px] text-zinc-500">
|
<p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">
|
||||||
{detail}
|
{detail}
|
||||||
<span className="ml-2 text-zinc-700">· {server.sourceLabel}</span>
|
<span className="ml-2 text-[var(--color-text-muted)]">· {server.sourceLabel}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{onAccept && (
|
{onAccept && (
|
||||||
<button
|
<button
|
||||||
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-emerald-400 transition hover:bg-emerald-500/10"
|
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-status-success)] transition-all duration-200 hover:bg-[var(--color-status-success)]/10"
|
||||||
onClick={onAccept}
|
onClick={onAccept}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -678,7 +678,7 @@ function DiscoveredServerRow({
|
|||||||
)}
|
)}
|
||||||
{onDismiss && (
|
{onDismiss && (
|
||||||
<button
|
<button
|
||||||
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
|
||||||
onClick={onDismiss}
|
onClick={onDismiss}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -707,18 +707,18 @@ function DangerZoneContent({
|
|||||||
description="Irreversible actions for this project."
|
description="Irreversible actions for this project."
|
||||||
title="Danger Zone"
|
title="Danger Zone"
|
||||||
/>
|
/>
|
||||||
<div className="rounded-xl border border-red-500/20 bg-red-500/5 px-5 py-5">
|
<div className="rounded-xl border border-[var(--color-status-error)]/20 bg-[var(--color-status-error)]/5 px-5 py-5">
|
||||||
<h4 className="text-[13px] font-semibold text-zinc-200">Remove project</h4>
|
<h4 className="text-[13px] font-semibold text-[var(--color-text-primary)]">Remove project</h4>
|
||||||
<p className="mt-1 text-[12px] text-zinc-500">
|
<p className="mt-1 text-[12px] text-[var(--color-text-muted)]">
|
||||||
Removing a project deletes all its sessions and discovered tooling from Aryx.
|
Removing a project deletes all its sessions and discovered tooling from Aryx.
|
||||||
Your project files on disk are not affected.
|
Your project files on disk are not affected.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 flex items-center gap-3">
|
<div className="mt-4 flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] font-medium transition ${
|
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] font-medium transition-all duration-200 ${
|
||||||
confirmingRemove
|
confirmingRemove
|
||||||
? 'bg-red-600 text-white hover:bg-red-500'
|
? 'bg-[var(--color-status-error)] text-white hover:bg-[var(--color-status-error)]'
|
||||||
: 'bg-red-500/10 text-red-400 hover:bg-red-500/20'
|
: 'bg-[var(--color-status-error)]/10 text-[var(--color-status-error)] hover:bg-[var(--color-status-error)]/20'
|
||||||
}`}
|
}`}
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -728,7 +728,7 @@ function DangerZoneContent({
|
|||||||
</button>
|
</button>
|
||||||
{confirmingRemove && (
|
{confirmingRemove && (
|
||||||
<button
|
<button
|
||||||
className="rounded-lg px-3 py-1.5 text-[13px] font-medium text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200"
|
className="rounded-lg px-3 py-1.5 text-[13px] font-medium text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={onCancelRemove}
|
onClick={onCancelRemove}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -755,8 +755,8 @@ function SectionHeader({
|
|||||||
return (
|
return (
|
||||||
<div className="mb-4 flex items-center justify-between gap-3">
|
<div className="mb-4 flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-[13px] font-semibold text-zinc-200">{title}</h3>
|
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">{title}</h3>
|
||||||
<p className="mt-0.5 text-[12px] text-zinc-500">{description}</p>
|
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">{description}</p>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
@@ -766,7 +766,7 @@ function SectionHeader({
|
|||||||
function RescanButton({ onClick, label = 'Re-scan' }: { onClick: () => void; label?: string }) {
|
function RescanButton({ onClick, label = 'Re-scan' }: { onClick: () => void; label?: string }) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1.5 rounded-lg bg-zinc-800 px-3 py-1.5 text-[13px] font-medium text-zinc-200 transition hover:bg-zinc-700"
|
className="flex items-center gap-1.5 rounded-lg bg-[var(--color-surface-3)] px-3 py-1.5 text-[13px] font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-surface-3)]"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
title={label === 'Scan' ? 'Scan for files' : 'Re-scan for changes'}
|
title={label === 'Scan' ? 'Scan for files' : 'Re-scan for changes'}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -779,7 +779,7 @@ function RescanButton({ onClick, label = 'Re-scan' }: { onClick: () => void; lab
|
|||||||
|
|
||||||
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/20 px-5 py-8 text-center text-[12px] leading-relaxed text-zinc-500">
|
<div className="rounded-xl border border-dashed border-[var(--color-border)] bg-[var(--color-surface-1)]/20 px-5 py-8 text-center text-[12px] leading-relaxed text-[var(--color-text-muted)]">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ const iconComponents: Record<ModelProvider, React.FC<{ className?: string }>> =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const providerColors: Record<ModelProvider, string> = {
|
export const providerColors: Record<ModelProvider, string> = {
|
||||||
openai: 'text-emerald-400',
|
openai: 'text-[var(--color-status-success)]',
|
||||||
anthropic: 'text-orange-400',
|
anthropic: 'text-[var(--color-accent-purple)]',
|
||||||
google: 'text-blue-400',
|
google: 'text-[var(--color-status-info)]',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ProviderIcon({ provider, className }: ProviderIconProps) {
|
export function ProviderIcon({ provider, className }: ProviderIconProps) {
|
||||||
|
|||||||
@@ -30,21 +30,21 @@ import type { RunTimelineEventRecord, SessionRunRecord } from '@shared/domain/ru
|
|||||||
/* ── Mode accent colours (shared with ActivityPanel) ───────── */
|
/* ── Mode accent colours (shared with ActivityPanel) ───────── */
|
||||||
|
|
||||||
const modeAccent: Record<OrchestrationMode, { dot: string; ring: string; text: string }> = {
|
const modeAccent: Record<OrchestrationMode, { dot: string; ring: string; text: string }> = {
|
||||||
single: { dot: 'bg-indigo-400', ring: 'ring-indigo-500/30', text: 'text-indigo-400' },
|
single: { dot: 'bg-[#245CF9]', ring: 'ring-[#245CF9]/30', text: 'text-[#245CF9]' },
|
||||||
sequential: { dot: 'bg-amber-400', ring: 'ring-amber-500/30', text: 'text-amber-400' },
|
sequential: { dot: 'bg-[var(--color-status-warning)]', ring: 'ring-[var(--color-status-warning)]/30', text: 'text-[var(--color-status-warning)]' },
|
||||||
concurrent: { dot: 'bg-emerald-400', ring: 'ring-emerald-500/30', text: 'text-emerald-400' },
|
concurrent: { dot: 'bg-[var(--color-status-success)]', ring: 'ring-[var(--color-status-success)]/30', text: 'text-[var(--color-status-success)]' },
|
||||||
handoff: { dot: 'bg-sky-400', ring: 'ring-sky-500/30', text: 'text-sky-400' },
|
handoff: { dot: 'bg-[var(--color-accent-sky)]', ring: 'ring-[var(--color-accent-sky)]/30', text: 'text-[var(--color-accent-sky)]' },
|
||||||
'group-chat': { dot: 'bg-violet-400', ring: 'ring-violet-500/30', text: 'text-violet-400' },
|
'group-chat': { dot: 'bg-[var(--color-accent-purple)]', ring: 'ring-[var(--color-accent-purple)]/30', text: 'text-[var(--color-accent-purple)]' },
|
||||||
magentic: { dot: 'bg-zinc-500', ring: 'ring-zinc-600/30', text: 'text-zinc-500' },
|
magentic: { dot: 'bg-[var(--color-text-muted)]', ring: 'ring-[var(--color-text-muted)]/30', text: 'text-[var(--color-text-muted)]' },
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ── Status badges ─────────────────────────────────────────── */
|
/* ── Status badges ─────────────────────────────────────────── */
|
||||||
|
|
||||||
const runStatusStyles: Record<SessionRunRecord['status'], { icon: ReactNode; className: string }> = {
|
const runStatusStyles: Record<SessionRunRecord['status'], { icon: ReactNode; className: string }> = {
|
||||||
running: { icon: <CircleDot className="size-3" />, className: 'text-blue-400' },
|
running: { icon: <CircleDot className="size-3" />, className: 'text-[var(--color-status-info)]' },
|
||||||
completed: { icon: <CheckCircle2 className="size-3" />, className: 'text-emerald-400' },
|
completed: { icon: <CheckCircle2 className="size-3" />, className: 'text-[var(--color-status-success)]' },
|
||||||
cancelled: { icon: <XCircle className="size-3" />, className: 'text-zinc-400' },
|
cancelled: { icon: <XCircle className="size-3" />, className: 'text-[var(--color-text-muted)]' },
|
||||||
error: { icon: <XCircle className="size-3" />, className: 'text-red-400' },
|
error: { icon: <XCircle className="size-3" />, className: 'text-[var(--color-status-error)]' },
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ── Event node icon ───────────────────────────────────────── */
|
/* ── Event node icon ───────────────────────────────────────── */
|
||||||
@@ -53,23 +53,23 @@ function EventIcon({ kind, status }: { kind: RunTimelineEventRecord['kind']; sta
|
|||||||
const base = 'size-3.5';
|
const base = 'size-3.5';
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case 'run-started':
|
case 'run-started':
|
||||||
return <Play className={`${base} text-zinc-500`} />;
|
return <Play className={`${base} text-[var(--color-text-muted)]`} />;
|
||||||
case 'thinking':
|
case 'thinking':
|
||||||
return <Brain className={`${base} ${status === 'running' ? 'text-sky-400 animate-pulse' : 'text-zinc-500'}`} />;
|
return <Brain className={`${base} ${status === 'running' ? 'text-[var(--color-accent-sky)] animate-pulse' : 'text-[var(--color-text-muted)]'}`} />;
|
||||||
case 'handoff':
|
case 'handoff':
|
||||||
return <ArrowRight className={`${base} text-amber-400`} />;
|
return <ArrowRight className={`${base} text-[var(--color-status-warning)]`} />;
|
||||||
case 'tool-call':
|
case 'tool-call':
|
||||||
return <Wrench className={`${base} text-violet-400`} />;
|
return <Wrench className={`${base} text-[var(--color-accent-purple)]`} />;
|
||||||
case 'approval':
|
case 'approval':
|
||||||
return <AlertTriangle className={`${base} ${status === 'running' ? 'text-amber-400 animate-pulse' : status === 'error' ? 'text-red-400' : 'text-emerald-400'}`} />;
|
return <AlertTriangle className={`${base} ${status === 'running' ? 'text-[var(--color-status-warning)] animate-pulse' : status === 'error' ? 'text-[var(--color-status-error)]' : 'text-[var(--color-status-success)]'}`} />;
|
||||||
case 'message':
|
case 'message':
|
||||||
return <MessageSquare className={`${base} ${status === 'running' ? 'text-blue-400 animate-pulse' : status === 'error' ? 'text-red-400' : 'text-emerald-400'}`} />;
|
return <MessageSquare className={`${base} ${status === 'running' ? 'text-[var(--color-status-info)] animate-pulse' : status === 'error' ? 'text-[var(--color-status-error)]' : 'text-[var(--color-status-success)]'}`} />;
|
||||||
case 'run-completed':
|
case 'run-completed':
|
||||||
return <CheckCircle2 className={`${base} text-emerald-400`} />;
|
return <CheckCircle2 className={`${base} text-[var(--color-status-success)]`} />;
|
||||||
case 'run-cancelled':
|
case 'run-cancelled':
|
||||||
return <XCircle className={`${base} text-zinc-400`} />;
|
return <XCircle className={`${base} text-[var(--color-text-muted)]`} />;
|
||||||
case 'run-failed':
|
case 'run-failed':
|
||||||
return <AlertTriangle className={`${base} text-red-400`} />;
|
return <AlertTriangle className={`${base} text-[var(--color-status-error)]`} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,19 +92,19 @@ function TimelineEventRow({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`group relative flex w-full gap-2.5 text-left ${terminal ? 'py-1' : 'py-1.5'} ${isClickable ? 'cursor-pointer' : 'cursor-default'}`}
|
className={`group relative flex w-full gap-2.5 text-left transition-all duration-200 ${terminal ? 'py-1' : 'py-1.5'} ${isClickable ? 'cursor-pointer' : 'cursor-default'}`}
|
||||||
disabled={!isClickable}
|
disabled={!isClickable}
|
||||||
onClick={isClickable ? () => onJumpToMessage(event.messageId!) : undefined}
|
onClick={isClickable ? () => onJumpToMessage(event.messageId!) : undefined}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{/* Vertical connector line */}
|
{/* Vertical connector line */}
|
||||||
{!isLast && (
|
{!isLast && (
|
||||||
<div className="absolute left-[7px] top-[22px] bottom-0 w-px bg-zinc-800" />
|
<div className="absolute left-[7px] top-[22px] bottom-0 w-px bg-[var(--color-border)]" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Node */}
|
{/* Node */}
|
||||||
<div className="relative z-10 flex shrink-0 items-start pt-0.5">
|
<div className="relative z-10 flex shrink-0 items-start pt-0.5">
|
||||||
<div className="flex size-[15px] items-center justify-center rounded-full bg-[var(--color-surface-1)]">
|
<div className={`flex size-[15px] items-center justify-center rounded-full ${event.status === 'running' ? 'brand-gradient-bg' : 'bg-[var(--color-surface-2)]'}`}>
|
||||||
<EventIcon kind={event.kind} status={event.status} />
|
<EventIcon kind={event.kind} status={event.status} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,41 +112,41 @@ function TimelineEventRow({
|
|||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className={`text-[11px] font-medium ${terminal ? 'text-zinc-600' : 'text-zinc-300'} ${isClickable ? 'group-hover:text-indigo-300' : ''}`}>
|
<span className={`text-[11px] font-medium ${terminal ? 'text-[var(--color-text-muted)]' : 'text-[var(--color-text-secondary)]'} ${isClickable ? 'group-hover:text-[var(--color-text-accent)]' : ''}`}>
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
{/* Approval kind badge */}
|
{/* Approval kind badge */}
|
||||||
{event.kind === 'approval' && event.approvalKind && (
|
{event.kind === 'approval' && event.approvalKind && (
|
||||||
<span className={`rounded-full px-1.5 py-0.5 text-[8px] font-semibold uppercase tracking-wider ${
|
<span className={`rounded-full px-1.5 py-0.5 text-[8px] font-semibold uppercase tracking-wider ${
|
||||||
event.status === 'running'
|
event.status === 'running'
|
||||||
? 'bg-amber-500/15 text-amber-400'
|
? 'bg-[var(--color-status-warning)]/15 text-[var(--color-status-warning)]'
|
||||||
: event.status === 'completed'
|
: event.status === 'completed'
|
||||||
? 'bg-emerald-500/15 text-emerald-400'
|
? 'bg-[var(--color-status-success)]/15 text-[var(--color-status-success)]'
|
||||||
: 'bg-red-500/15 text-red-400'
|
: 'bg-[var(--color-status-error)]/15 text-[var(--color-status-error)]'
|
||||||
}`}>
|
}`}>
|
||||||
{event.approvalKind === 'final-response' ? 'response' : 'tool'}
|
{event.approvalKind === 'final-response' ? 'response' : 'tool'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="ml-auto shrink-0 text-[9px] tabular-nums text-zinc-700">{timestamp}</span>
|
<span className="font-mono ml-auto shrink-0 text-[9px] tabular-nums text-[var(--color-text-muted)]">{timestamp}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content preview for message events */}
|
{/* Content preview for message events */}
|
||||||
{preview && (
|
{preview && (
|
||||||
<p className={`mt-0.5 text-[10px] leading-snug text-zinc-600 ${isClickable ? 'group-hover:text-zinc-500' : ''}`}>
|
<p className={`mt-0.5 text-[10px] leading-snug text-[var(--color-text-muted)] ${isClickable ? 'group-hover:text-[var(--color-text-secondary)]' : ''}`}>
|
||||||
{preview}
|
{preview}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Approval detail */}
|
{/* Approval detail */}
|
||||||
{event.kind === 'approval' && event.approvalDetail && (
|
{event.kind === 'approval' && event.approvalDetail && (
|
||||||
<p className="mt-0.5 text-[10px] leading-snug text-zinc-500">
|
<p className="mt-0.5 text-[10px] leading-snug text-[var(--color-text-muted)]">
|
||||||
{truncateContent(event.approvalDetail, 120)}
|
{truncateContent(event.approvalDetail, 120)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Error detail */}
|
{/* Error detail */}
|
||||||
{event.error && (
|
{event.error && (
|
||||||
<p className="mt-0.5 text-[10px] leading-snug text-red-500/80">
|
<p className="mt-0.5 text-[10px] leading-snug text-[var(--color-status-error)]/80">
|
||||||
{truncateContent(event.error, 120)}
|
{truncateContent(event.error, 120)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -169,15 +169,15 @@ function ThinkingGroupRow({
|
|||||||
return (
|
return (
|
||||||
<div className="group relative flex w-full gap-2.5 py-1">
|
<div className="group relative flex w-full gap-2.5 py-1">
|
||||||
{!isLast && (
|
{!isLast && (
|
||||||
<div className="absolute left-[7px] top-[22px] bottom-0 w-px bg-zinc-800" />
|
<div className="absolute left-[7px] top-[22px] bottom-0 w-px bg-[var(--color-border)]" />
|
||||||
)}
|
)}
|
||||||
<div className="relative z-10 flex shrink-0 items-start pt-0.5">
|
<div className="relative z-10 flex shrink-0 items-start pt-0.5">
|
||||||
<div className="flex size-[15px] items-center justify-center rounded-full bg-[var(--color-surface-1)]">
|
<div className="flex size-[15px] items-center justify-center rounded-full bg-[var(--color-surface-2)]">
|
||||||
<Brain className="size-3.5 text-zinc-500" />
|
<Brain className="size-3.5 text-[var(--color-text-muted)]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<span className="text-[11px] text-zinc-600">
|
<span className="text-[11px] text-[var(--color-text-muted)]">
|
||||||
{agentName ? `${agentName} thinking` : 'Thinking'} ×{events.length}
|
{agentName ? `${agentName} thinking` : 'Thinking'} ×{events.length}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,26 +225,26 @@ function RunCard({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40">
|
<div className="glass-surface rounded-lg">
|
||||||
{/* Run header */}
|
{/* Run header */}
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center gap-2 px-3 py-2 text-left transition hover:bg-zinc-800/30"
|
className="flex w-full items-center gap-2 px-3 py-2 text-left transition-all duration-200 hover:bg-[var(--color-surface-3)]"
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{expanded
|
{expanded
|
||||||
? <ChevronDown className="size-3 shrink-0 text-zinc-600" />
|
? <ChevronDown className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
: <ChevronRight className="size-3 shrink-0 text-zinc-600" />}
|
: <ChevronRight className="size-3 shrink-0 text-[var(--color-text-muted)]" />}
|
||||||
|
|
||||||
<Bot className={`size-3 shrink-0 ${accent.text}`} />
|
<Bot className={`size-3 shrink-0 ${accent.text}`} />
|
||||||
|
|
||||||
<span className="min-w-0 flex-1 truncate text-[11px] font-medium text-zinc-300">
|
<span className="min-w-0 flex-1 truncate text-[11px] font-medium text-[var(--color-text-secondary)]">
|
||||||
{run.patternName}
|
{run.patternName}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Status */}
|
{/* Status */}
|
||||||
<span className={`flex items-center gap-1 shrink-0 ${statusStyle.className}`}>
|
<span className={`flex items-center gap-1 shrink-0 ${statusStyle.className}`}>
|
||||||
{run.status === 'running' && <span className="size-1.5 animate-pulse rounded-full bg-blue-400" />}
|
{run.status === 'running' && <span className="size-1.5 animate-pulse rounded-full bg-[var(--color-status-info)]" />}
|
||||||
{run.status !== 'running' && statusStyle.icon}
|
{run.status !== 'running' && statusStyle.icon}
|
||||||
<span className="text-[9px] font-medium">{formatRunStatusLabel(run.status)}</span>
|
<span className="text-[9px] font-medium">{formatRunStatusLabel(run.status)}</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -252,13 +252,13 @@ function RunCard({
|
|||||||
|
|
||||||
{/* Expanded timeline */}
|
{/* Expanded timeline */}
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="border-t border-zinc-800/60 px-3 pb-2 pt-1.5">
|
<div className="border-t border-[var(--color-border-subtle)] px-3 pb-2 pt-1.5">
|
||||||
{/* Agent badges */}
|
{/* Agent badges */}
|
||||||
{run.agents.length > 1 && (
|
{run.agents.length > 1 && (
|
||||||
<div className="mb-2 flex flex-wrap gap-1">
|
<div className="mb-2 flex flex-wrap gap-1">
|
||||||
{run.agents.map((agent) => (
|
{run.agents.map((agent) => (
|
||||||
<span
|
<span
|
||||||
className="rounded-full bg-zinc-800/80 px-2 py-0.5 text-[9px] font-medium text-zinc-500"
|
className="rounded-full bg-[var(--color-surface-2)] px-2 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)]"
|
||||||
key={agent.agentId}
|
key={agent.agentId}
|
||||||
>
|
>
|
||||||
{agent.agentName}
|
{agent.agentName}
|
||||||
@@ -281,7 +281,7 @@ function RunCard({
|
|||||||
|
|
||||||
{/* Duration footer */}
|
{/* Duration footer */}
|
||||||
{duration && (
|
{duration && (
|
||||||
<div className="mt-1 border-t border-zinc-800/40 pt-1.5 text-[9px] tabular-nums text-zinc-700">
|
<div className="font-mono mt-1 border-t border-[var(--color-border-subtle)] pt-1.5 text-[9px] tabular-nums text-[var(--color-text-muted)]">
|
||||||
Duration: {duration}
|
Duration: {duration}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -295,7 +295,7 @@ function RunCard({
|
|||||||
|
|
||||||
function EmptyTimeline() {
|
function EmptyTimeline() {
|
||||||
return (
|
return (
|
||||||
<p className="py-4 text-center text-[11px] text-zinc-600">
|
<p className="py-4 text-center text-[11px] text-[var(--color-text-muted)]">
|
||||||
Send a message to see the run timeline
|
Send a message to see the run timeline
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ const navGroups: NavGroup[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
function modeBadgeClasses(pattern: PatternDefinition) {
|
function modeBadgeClasses(pattern: PatternDefinition) {
|
||||||
if (pattern.availability === 'unavailable') return 'bg-amber-500/10 text-amber-400';
|
if (pattern.availability === 'unavailable') return 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]';
|
||||||
return 'bg-zinc-800 text-zinc-400';
|
return 'bg-[var(--color-surface-3)] text-[var(--color-text-secondary)]';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsPanel({
|
export function SettingsPanel({
|
||||||
@@ -210,13 +210,13 @@ export function SettingsPanel({
|
|||||||
<div className="fixed inset-0 z-50 flex flex-col bg-[var(--color-surface-0)]">
|
<div className="fixed inset-0 z-50 flex flex-col bg-[var(--color-surface-0)]">
|
||||||
<div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
|
<div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
|
||||||
<button
|
<button
|
||||||
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200"
|
className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="size-4" />
|
<ChevronLeft className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
<h2 className="text-[13px] font-semibold text-zinc-100">Settings</h2>
|
<h2 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">Settings</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex min-h-0 flex-1">
|
<div className="flex min-h-0 flex-1">
|
||||||
@@ -224,7 +224,7 @@ export function SettingsPanel({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{navGroups.map((group) => (
|
{navGroups.map((group) => (
|
||||||
<div key={group.label}>
|
<div key={group.label}>
|
||||||
<span className="mb-1 block px-3 text-[10px] font-semibold uppercase tracking-wider text-zinc-600">
|
<span className="mb-1 block px-3 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
{group.label}
|
{group.label}
|
||||||
</span>
|
</span>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
@@ -232,16 +232,16 @@ export function SettingsPanel({
|
|||||||
const isActive = item.id === activeSection;
|
const isActive = item.id === activeSection;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition ${
|
className={`flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition-all duration-200 ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-zinc-800 font-medium text-zinc-100'
|
? 'bg-[var(--color-surface-3)] font-medium text-[var(--color-text-primary)]'
|
||||||
: 'text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-300'
|
: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-secondary)]'
|
||||||
}`}
|
}`}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => setActiveSection(item.id)}
|
onClick={() => setActiveSection(item.id)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span className={isActive ? 'text-zinc-300' : 'text-zinc-500'}>{item.icon}</span>
|
<span className={isActive ? 'text-[var(--color-text-secondary)]' : 'text-[var(--color-text-muted)]'}>{item.icon}</span>
|
||||||
{item.label}
|
{item.label}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@@ -322,8 +322,8 @@ function AppearanceSection({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
<h3 className="text-[13px] font-semibold text-zinc-200">Appearance</h3>
|
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">Appearance</h3>
|
||||||
<p className="mt-0.5 text-[12px] text-zinc-500">
|
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">
|
||||||
Choose how Aryx looks on your device
|
Choose how Aryx looks on your device
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -333,27 +333,27 @@ function AppearanceSection({
|
|||||||
const isSelected = option.value === theme;
|
const isSelected = option.value === theme;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition ${
|
className={`flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition-all duration-200 ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-indigo-500/50 bg-indigo-500/10'
|
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)]'
|
||||||
: 'border-[var(--color-border)] hover:border-zinc-600 hover:bg-zinc-800/40'
|
: 'border-[var(--color-border)] hover:border-[var(--color-border)] hover:bg-[var(--color-surface-3)]/40'
|
||||||
}`}
|
}`}
|
||||||
key={option.value}
|
key={option.value}
|
||||||
onClick={() => onSetTheme(option.value)}
|
onClick={() => onSetTheme(option.value)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition ${
|
className={`flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition-all duration-200 ${
|
||||||
isSelected ? 'border-indigo-500' : 'border-zinc-600'
|
isSelected ? 'border-[var(--color-accent)]' : 'border-[var(--color-border)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isSelected && <div className="size-2 rounded-full bg-indigo-500" />}
|
{isSelected && <div className="size-2 rounded-full bg-[var(--color-accent)]" />}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className={`text-[13px] font-medium ${isSelected ? 'text-zinc-100' : 'text-zinc-300'}`}>
|
<span className={`text-[13px] font-medium ${isSelected ? 'text-[var(--color-text-primary)]' : 'text-[var(--color-text-secondary)]'}`}>
|
||||||
{option.label}
|
{option.label}
|
||||||
</span>
|
</span>
|
||||||
<p className="text-[12px] text-zinc-500">{option.description}</p>
|
<p className="text-[12px] text-[var(--color-text-muted)]">{option.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@@ -379,12 +379,12 @@ function ConnectionSection({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
<h3 className="text-[13px] font-semibold text-zinc-200">GitHub Copilot</h3>
|
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">GitHub Copilot</h3>
|
||||||
<p className="mt-0.5 text-[12px] text-zinc-500">
|
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">
|
||||||
Aryx uses your installed GitHub Copilot CLI for AI capabilities
|
Aryx uses your installed GitHub Copilot CLI for AI capabilities
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 rounded-xl border border-[var(--color-border)] bg-zinc-900/30 p-4">
|
<div className="mt-4 rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] p-4">
|
||||||
<CopilotStatusCard
|
<CopilotStatusCard
|
||||||
connection={connection}
|
connection={connection}
|
||||||
isRefreshing={isRefreshing}
|
isRefreshing={isRefreshing}
|
||||||
@@ -418,25 +418,25 @@ function PatternsSection({
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{patterns.map((pattern) => (
|
{patterns.map((pattern) => (
|
||||||
<button
|
<button
|
||||||
className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition hover:border-zinc-800 hover:bg-zinc-900"
|
className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition-all duration-200 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]"
|
||||||
key={pattern.id}
|
key={pattern.id}
|
||||||
onClick={() => onEditPattern(pattern)}
|
onClick={() => onEditPattern(pattern)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[13px] font-medium text-zinc-200">{pattern.name}</span>
|
<span className="text-[13px] font-medium text-[var(--color-text-primary)]">{pattern.name}</span>
|
||||||
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide ${modeBadgeClasses(pattern)}`}>
|
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide ${modeBadgeClasses(pattern)}`}>
|
||||||
{pattern.mode}
|
{pattern.mode}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 truncate text-[12px] text-zinc-500">{pattern.description}</p>
|
<p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">{pattern.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[12px] text-zinc-600">
|
<span className="text-[12px] text-[var(--color-text-muted)]">
|
||||||
{pattern.agents.length} agent{pattern.agents.length === 1 ? '' : 's'}
|
{pattern.agents.length} agent{pattern.agents.length === 1 ? '' : 's'}
|
||||||
</span>
|
</span>
|
||||||
<ChevronRight className="size-4 text-zinc-700 transition group-hover:text-zinc-500" />
|
<ChevronRight className="size-4 text-[var(--color-text-muted)] transition-all duration-200 group-hover:text-[var(--color-text-muted)]" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -537,8 +537,8 @@ function SectionHeader({
|
|||||||
return (
|
return (
|
||||||
<div className="mb-4 flex items-center justify-between gap-3">
|
<div className="mb-4 flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-[13px] font-semibold text-zinc-200">{title}</h3>
|
<h3 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">{title}</h3>
|
||||||
<p className="mt-0.5 text-[12px] text-zinc-500">{description}</p>
|
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">{description}</p>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
@@ -548,7 +548,7 @@ function SectionHeader({
|
|||||||
function SectionAction({ label, onClick }: { label: string; onClick: () => void }) {
|
function SectionAction({ label, onClick }: { label: string; onClick: () => void }) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1.5 rounded-lg bg-zinc-800 px-3 py-1.5 text-[13px] font-medium text-zinc-200 transition hover:bg-zinc-700"
|
className="flex items-center gap-1.5 rounded-lg bg-[var(--color-surface-3)] px-3 py-1.5 text-[13px] font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-surface-3)]"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -571,27 +571,27 @@ function ToolingListButton({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition hover:border-zinc-800 hover:bg-zinc-900"
|
className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition-all duration-200 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="truncate text-[13px] font-medium text-zinc-200">{label}</span>
|
<span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{label}</span>
|
||||||
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-zinc-400">
|
<span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-secondary)]">
|
||||||
{meta}
|
{meta}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 truncate text-[12px] text-zinc-500">{detail}</p>
|
<p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">{detail}</p>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="size-4 text-zinc-700 transition group-hover:text-zinc-500" />
|
<ChevronRight className="size-4 text-[var(--color-text-muted)] transition-all duration-200 group-hover:text-[var(--color-text-muted)]" />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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/20 px-5 py-8 text-center text-[12px] leading-relaxed text-zinc-500">
|
<div className="rounded-xl border border-dashed border-[var(--color-border)] bg-[var(--color-surface-1)]/20 px-5 py-8 text-center text-[12px] leading-relaxed text-[var(--color-text-muted)]">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -648,8 +648,8 @@ function DiscoveredSubSection({
|
|||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-[12px] font-medium text-zinc-300">{label}</span>
|
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
|
||||||
<p className="text-[11px] text-zinc-600">{description}</p>
|
<p className="text-[11px] text-[var(--color-text-muted)]">{description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -692,30 +692,30 @@ function DiscoveredServerRow({
|
|||||||
: server.url || 'No URL';
|
: server.url || 'No URL';
|
||||||
|
|
||||||
const statusBadge = status === 'accepted'
|
const statusBadge = status === 'accepted'
|
||||||
? 'bg-emerald-500/10 text-emerald-400'
|
? 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]'
|
||||||
: 'bg-amber-500/10 text-amber-400';
|
: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3 rounded-xl border border-transparent px-4 py-3 hover:border-zinc-800 hover:bg-zinc-900">
|
<div className="flex items-center gap-3 rounded-xl border border-transparent px-4 py-3 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="truncate text-[13px] font-medium text-zinc-200">{server.name}</span>
|
<span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{server.name}</span>
|
||||||
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-zinc-400">
|
<span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-secondary)]">
|
||||||
{server.transport}
|
{server.transport}
|
||||||
</span>
|
</span>
|
||||||
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}>
|
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${statusBadge}`}>
|
||||||
{status}
|
{status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 truncate text-[12px] text-zinc-500">
|
<p className="mt-0.5 truncate text-[12px] text-[var(--color-text-muted)]">
|
||||||
{detail}
|
{detail}
|
||||||
<span className="ml-2 text-zinc-700">· {server.sourceLabel}</span>
|
<span className="ml-2 text-[var(--color-text-muted)]">· {server.sourceLabel}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{onAccept && (
|
{onAccept && (
|
||||||
<button
|
<button
|
||||||
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-emerald-400 transition hover:bg-emerald-500/10"
|
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-status-success)] transition-all duration-200 hover:bg-[var(--color-status-success)]/10"
|
||||||
onClick={onAccept}
|
onClick={onAccept}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -724,7 +724,7 @@ function DiscoveredServerRow({
|
|||||||
)}
|
)}
|
||||||
{onDismiss && (
|
{onDismiss && (
|
||||||
<button
|
<button
|
||||||
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="rounded-lg px-2.5 py-1 text-[12px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
|
||||||
onClick={onDismiss}
|
onClick={onDismiss}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -772,12 +772,12 @@ function TroubleshootingSection({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 rounded-xl border border-red-500/20 bg-red-500/5 p-5">
|
<div className="mt-8 rounded-xl border border-[var(--color-status-error)]/20 bg-[var(--color-status-error)]/5 p-5">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<TriangleAlert className="mt-0.5 size-4 shrink-0 text-red-400" />
|
<TriangleAlert className="mt-0.5 size-4 shrink-0 text-[var(--color-status-error)]" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h4 className="text-[13px] font-semibold text-red-300">Reset Local Workspace</h4>
|
<h4 className="text-[13px] font-semibold text-[var(--color-status-error)]">Reset Local Workspace</h4>
|
||||||
<p className="mt-1 text-[12px] leading-relaxed text-zinc-400">
|
<p className="mt-1 text-[12px] leading-relaxed text-[var(--color-text-secondary)]">
|
||||||
Restore Aryx to its initial state. This permanently removes all sessions, custom patterns,
|
Restore Aryx to its initial state. This permanently removes all sessions, custom patterns,
|
||||||
MCP server definitions, LSP profiles, and scratchpad contents. Your GitHub Copilot sign-in
|
MCP server definitions, LSP profiles, and scratchpad contents. Your GitHub Copilot sign-in
|
||||||
is not affected.
|
is not affected.
|
||||||
@@ -785,7 +785,7 @@ function TroubleshootingSection({
|
|||||||
|
|
||||||
{!confirmingReset ? (
|
{!confirmingReset ? (
|
||||||
<button
|
<button
|
||||||
className="mt-3 rounded-lg border border-red-500/30 bg-red-500/10 px-3.5 py-1.5 text-[13px] font-medium text-red-300 transition hover:border-red-500/50 hover:bg-red-500/20"
|
className="mt-3 rounded-lg border border-[var(--color-status-error)]/30 bg-[var(--color-status-error)]/10 px-3.5 py-1.5 text-[13px] font-medium text-[var(--color-status-error)] transition-all duration-200 hover:border-[var(--color-status-error)]/50 hover:bg-[var(--color-status-error)]/20"
|
||||||
onClick={() => setConfirmingReset(true)}
|
onClick={() => setConfirmingReset(true)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -794,7 +794,7 @@ function TroubleshootingSection({
|
|||||||
) : (
|
) : (
|
||||||
<div className="mt-3 flex items-center gap-2">
|
<div className="mt-3 flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="rounded-lg bg-red-600 px-3.5 py-1.5 text-[13px] font-medium text-white transition hover:bg-red-500 disabled:opacity-50"
|
className="rounded-lg bg-[var(--color-status-error)] px-3.5 py-1.5 text-[13px] font-medium text-white transition-all duration-200 hover:bg-[var(--color-status-error)] disabled:opacity-50"
|
||||||
disabled={isResetting}
|
disabled={isResetting}
|
||||||
onClick={() => void handleReset()}
|
onClick={() => void handleReset()}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -802,7 +802,7 @@ function TroubleshootingSection({
|
|||||||
{isResetting ? 'Resetting…' : 'Confirm reset'}
|
{isResetting ? 'Resetting…' : 'Confirm reset'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="rounded-lg border border-[var(--color-border)] px-3.5 py-1.5 text-[13px] font-medium text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200"
|
className="rounded-lg border border-[var(--color-border)] px-3.5 py-1.5 text-[13px] font-medium text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
disabled={isResetting}
|
disabled={isResetting}
|
||||||
onClick={() => setConfirmingReset(false)}
|
onClick={() => setConfirmingReset(false)}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -831,16 +831,16 @@ function TroubleshootingAction({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition hover:border-zinc-800 hover:bg-zinc-900"
|
className="group flex w-full items-center gap-3 rounded-xl border border-transparent px-4 py-3 text-left transition-all duration-200 hover:border-[var(--color-border)] hover:bg-[var(--color-surface-1)]"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span className="text-zinc-500 transition group-hover:text-zinc-300">{icon}</span>
|
<span className="text-[var(--color-text-muted)] transition-all duration-200 group-hover:text-[var(--color-text-secondary)]">{icon}</span>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<span className="text-[13px] font-medium text-zinc-200">{label}</span>
|
<span className="text-[13px] font-medium text-[var(--color-text-primary)]">{label}</span>
|
||||||
<p className="mt-0.5 text-[12px] text-zinc-500">{description}</p>
|
<p className="mt-0.5 text-[12px] text-[var(--color-text-muted)]">{description}</p>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="size-4 text-zinc-700 transition group-hover:text-zinc-500" />
|
<ChevronRight className="size-4 text-[var(--color-text-muted)] transition-all duration-200 group-hover:text-[var(--color-text-muted)]" />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ interface SidebarProps {
|
|||||||
/* ── Mode icon + accent colour mapping ─────────────────────── */
|
/* ── Mode icon + accent colour mapping ─────────────────────── */
|
||||||
|
|
||||||
const modeVisuals: Record<OrchestrationMode, { icon: LucideIcon; color: string }> = {
|
const modeVisuals: Record<OrchestrationMode, { icon: LucideIcon; color: string }> = {
|
||||||
single: { icon: MessageSquare, color: 'text-indigo-400' },
|
single: { icon: MessageSquare, color: 'text-[#245CF9]' },
|
||||||
sequential: { icon: ListOrdered, color: 'text-amber-400' },
|
sequential: { icon: ListOrdered, color: 'text-[var(--color-status-warning)]' },
|
||||||
concurrent: { icon: GitFork, color: 'text-emerald-400' },
|
concurrent: { icon: GitFork, color: 'text-[var(--color-status-success)]' },
|
||||||
handoff: { icon: ArrowLeftRight, color: 'text-sky-400' },
|
handoff: { icon: ArrowLeftRight, color: 'text-[var(--color-accent-sky)]' },
|
||||||
'group-chat': { icon: Users, color: 'text-violet-400' },
|
'group-chat': { icon: Users, color: 'text-[var(--color-accent-purple)]' },
|
||||||
magentic: { icon: Lock, color: 'text-zinc-500' },
|
magentic: { icon: Lock, color: 'text-[var(--color-text-muted)]' },
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ── Relative time helper ──────────────────────────────────── */
|
/* ── Relative time helper ──────────────────────────────────── */
|
||||||
@@ -83,7 +83,7 @@ function relativeTime(iso: string): string {
|
|||||||
function GitContextBadge({ git }: { git: ProjectGitContext }) {
|
function GitContextBadge({ git }: { git: ProjectGitContext }) {
|
||||||
if (git.status === 'not-repository') {
|
if (git.status === 'not-repository') {
|
||||||
return (
|
return (
|
||||||
<span className="text-[10px] text-zinc-600" title="Not a git repository">
|
<span className="text-[10px] text-[var(--color-text-muted)]" title="Not a git repository">
|
||||||
no repo
|
no repo
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -117,7 +117,7 @@ function GitContextBadge({ git }: { git: ProjectGitContext }) {
|
|||||||
if (git.behind) parts.push(`↓${git.behind}`);
|
if (git.behind) parts.push(`↓${git.behind}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="flex items-center gap-1 text-[10px] text-zinc-500" title={parts.join(' · ') || branchLabel}>
|
<span className="flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]" title={parts.join(' · ') || branchLabel}>
|
||||||
<GitBranch className="size-2.5 shrink-0" />
|
<GitBranch className="size-2.5 shrink-0" />
|
||||||
<span className="max-w-[80px] truncate">{branchLabel}</span>
|
<span className="max-w-[80px] truncate">{branchLabel}</span>
|
||||||
{git.isDirty && <Circle className="size-1.5 shrink-0 fill-amber-500 text-amber-500" />}
|
{git.isDirty && <Circle className="size-1.5 shrink-0 fill-amber-500 text-amber-500" />}
|
||||||
@@ -140,7 +140,7 @@ function ActionMenuItem({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-2 px-3 py-1.5 text-[12px] transition hover:bg-zinc-800 ${className ?? 'text-zinc-300'}`}
|
className={`flex w-full items-center gap-2 px-3 py-1.5 text-[12px] transition-all duration-150 hover:bg-[var(--color-surface-2)] ${className ?? 'text-[var(--color-text-primary)]'}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -213,10 +213,10 @@ function SessionItem({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`group relative flex w-full cursor-pointer items-start gap-2.5 rounded-lg px-2.5 py-2 text-left transition-all duration-150 ${
|
className={`group relative flex w-full cursor-pointer items-start gap-2.5 rounded-lg px-2.5 py-2 text-left transition-all duration-200 ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-indigo-500/10 ring-1 ring-indigo-500/25'
|
? 'bg-[var(--color-accent-muted)] ring-1 ring-[var(--color-border-glow)]'
|
||||||
: 'hover:bg-zinc-800/60'
|
: 'hover:bg-[var(--color-surface-2)]/60'
|
||||||
} ${isRunning ? 'sidebar-running' : ''} ${session.isArchived ? 'opacity-50' : ''}`}
|
} ${isRunning ? 'sidebar-running' : ''} ${session.isArchived ? 'opacity-50' : ''}`}
|
||||||
onClick={isRenaming ? undefined : onSelect}
|
onClick={isRenaming ? undefined : onSelect}
|
||||||
role="button"
|
role="button"
|
||||||
@@ -225,19 +225,19 @@ function SessionItem({
|
|||||||
>
|
>
|
||||||
{/* Running/approval left accent bar */}
|
{/* Running/approval left accent bar */}
|
||||||
{isRunning && !hasPendingApproval && (
|
{isRunning && !hasPendingApproval && (
|
||||||
<span className="absolute inset-y-1.5 left-0 w-[3px] rounded-full bg-blue-400 sidebar-pulse" />
|
<span className="absolute inset-y-1.5 left-0 w-[3px] rounded-full accent-flow" />
|
||||||
)}
|
)}
|
||||||
{hasPendingApproval && (
|
{hasPendingApproval && (
|
||||||
<span className="absolute inset-y-1.5 left-0 w-[3px] rounded-full bg-amber-400" />
|
<span className="absolute inset-y-1.5 left-0 w-[3px] rounded-full bg-[var(--color-status-warning)]" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mode icon */}
|
{/* Mode icon */}
|
||||||
<span
|
<span
|
||||||
className={`mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-md ${
|
className={`mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-md ${
|
||||||
isActive ? 'bg-indigo-500/15' : 'bg-zinc-800/80'
|
isActive ? 'bg-[var(--color-accent-muted)]' : 'bg-[var(--color-surface-2)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<ModeIcon className={`size-3.5 ${isActive ? 'text-indigo-400' : visual.color}`} />
|
<ModeIcon className={`size-3.5 ${isActive ? 'text-[var(--color-accent)]' : visual.color}`} />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
@@ -247,7 +247,7 @@ function SessionItem({
|
|||||||
{isRenaming ? (
|
{isRenaming ? (
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className="w-full rounded bg-zinc-800 px-1.5 py-0.5 text-[13px] font-medium text-zinc-100 outline-none ring-1 ring-indigo-500/50"
|
className="w-full rounded bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[13px] font-medium text-[var(--color-text-primary)] outline-none ring-1 ring-[var(--color-border-glow)]"
|
||||||
value={renameText}
|
value={renameText}
|
||||||
onChange={(e) => setRenameText(e.target.value)}
|
onChange={(e) => setRenameText(e.target.value)}
|
||||||
onKeyDown={handleRenameKeyDown}
|
onKeyDown={handleRenameKeyDown}
|
||||||
@@ -257,7 +257,7 @@ function SessionItem({
|
|||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className={`truncate text-[13px] font-medium leading-tight ${
|
className={`truncate text-[13px] font-medium leading-tight ${
|
||||||
isActive ? 'text-indigo-100' : 'text-zinc-200 group-hover:text-zinc-100'
|
isActive ? 'text-[var(--color-text-primary)]' : 'text-[var(--color-text-primary)] group-hover:text-[var(--color-text-primary)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{session.title}
|
{session.title}
|
||||||
@@ -267,36 +267,36 @@ function SessionItem({
|
|||||||
|
|
||||||
<div className="mt-1 flex items-center gap-2">
|
<div className="mt-1 flex items-center gap-2">
|
||||||
{agentCount > 1 && (
|
{agentCount > 1 && (
|
||||||
<span className="inline-flex items-center gap-0.5 text-[10px] text-zinc-500">
|
<span className="inline-flex items-center gap-0.5 text-[10px] text-[var(--color-text-muted)]">
|
||||||
<Users className="size-2.5" />
|
<Users className="size-2.5" />
|
||||||
{agentCount}
|
{agentCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{isRunning && !hasPendingApproval && (
|
{isRunning && !hasPendingApproval && (
|
||||||
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-blue-400">
|
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-[var(--color-accent-sky)]">
|
||||||
<span className="size-1.5 rounded-full bg-blue-400 sidebar-pulse" />
|
<span className="size-1.5 rounded-full bg-[var(--color-accent-sky)] sidebar-pulse" />
|
||||||
Running
|
Running
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{hasPendingApproval && (
|
{hasPendingApproval && (
|
||||||
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-amber-400">
|
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-[var(--color-status-warning)]">
|
||||||
<span className="size-1.5 rounded-full bg-amber-400 animate-pulse" />
|
<span className="size-1.5 rounded-full bg-[var(--color-status-warning)] animate-pulse" />
|
||||||
Awaiting approval{queuedCount > 0 && ` (+${queuedCount})`}
|
Awaiting approval{queuedCount > 0 && ` (+${queuedCount})`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{isError && (
|
{isError && (
|
||||||
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-red-400">
|
<span className="inline-flex items-center gap-1 text-[10px] font-medium text-[var(--color-status-error)]">
|
||||||
<span className="size-1.5 rounded-full bg-red-400" />
|
<span className="size-1.5 rounded-full bg-[var(--color-status-error)]" />
|
||||||
Error
|
Error
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{session.isArchived && (
|
{session.isArchived && (
|
||||||
<span className="inline-flex items-center gap-1 text-[10px] text-zinc-600">
|
<span className="inline-flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]">
|
||||||
<Archive className="size-2.5" />
|
<Archive className="size-2.5" />
|
||||||
Archived
|
Archived
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="ml-auto text-[10px] text-zinc-600 group-hover:text-zinc-500">
|
<span className="ml-auto text-[10px] text-[var(--color-text-muted)] group-hover:text-[var(--color-text-secondary)]">
|
||||||
{relativeTime(session.updatedAt)}
|
{relativeTime(session.updatedAt)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -305,7 +305,7 @@ function SessionItem({
|
|||||||
{/* Actions button (hidden during rename) */}
|
{/* Actions button (hidden during rename) */}
|
||||||
{!isRenaming && (
|
{!isRenaming && (
|
||||||
<button
|
<button
|
||||||
className="absolute right-1.5 top-1.5 flex size-6 items-center justify-center rounded-md text-zinc-600 opacity-0 transition hover:bg-zinc-700 hover:text-zinc-300 group-hover:opacity-100"
|
className="absolute right-1.5 top-1.5 flex size-6 items-center justify-center rounded-md text-[var(--color-text-muted)] opacity-0 transition-all duration-150 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)] group-hover:opacity-100"
|
||||||
onClick={(e) => { e.stopPropagation(); onOpenMenu(e); }}
|
onClick={(e) => { e.stopPropagation(); onOpenMenu(e); }}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -376,19 +376,19 @@ function ProjectGroup({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
className="group flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left text-[13px] font-semibold text-zinc-400 transition hover:bg-zinc-800/40 hover:text-zinc-200"
|
className="group flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left text-[13px] font-semibold text-[var(--color-text-secondary)] transition-all duration-150 hover:bg-[var(--color-surface-2)]/40 hover:text-[var(--color-text-primary)]"
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{expanded ? (
|
{expanded ? (
|
||||||
<ChevronDown className="size-3 shrink-0 text-zinc-500" />
|
<ChevronDown className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronRight className="size-3 shrink-0 text-zinc-500" />
|
<ChevronRight className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
)}
|
)}
|
||||||
{isScratchpad ? (
|
{isScratchpad ? (
|
||||||
<MessageSquare className="size-3.5 shrink-0 text-zinc-500 transition group-hover:text-indigo-400" />
|
<MessageSquare className="size-3.5 shrink-0 text-[var(--color-text-muted)] transition group-hover:text-[var(--color-accent)]" />
|
||||||
) : (
|
) : (
|
||||||
<FolderOpen className="size-3.5 shrink-0 text-zinc-500 transition group-hover:text-indigo-400" />
|
<FolderOpen className="size-3.5 shrink-0 text-[var(--color-text-muted)] transition group-hover:text-[var(--color-accent)]" />
|
||||||
)}
|
)}
|
||||||
<span className="truncate">{project.name}</span>
|
<span className="truncate">{project.name}</span>
|
||||||
|
|
||||||
@@ -399,7 +399,7 @@ function ProjectGroup({
|
|||||||
<div className="ml-auto flex items-center gap-1.5">
|
<div className="ml-auto flex items-center gap-1.5">
|
||||||
{!isScratchpad && onOpenProjectSettings && (
|
{!isScratchpad && onOpenProjectSettings && (
|
||||||
<span
|
<span
|
||||||
className="flex size-5 items-center justify-center rounded text-zinc-600 opacity-0 transition hover:bg-zinc-700 hover:text-zinc-300 group-hover:opacity-100"
|
className="flex size-5 items-center justify-center rounded text-[var(--color-text-muted)] opacity-0 transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)] group-hover:opacity-100"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onOpenProjectSettings(project.id);
|
onOpenProjectSettings(project.id);
|
||||||
@@ -412,7 +412,7 @@ function ProjectGroup({
|
|||||||
)}
|
)}
|
||||||
{!isScratchpad && onRefreshGitContext && (
|
{!isScratchpad && onRefreshGitContext && (
|
||||||
<span
|
<span
|
||||||
className="flex size-5 items-center justify-center rounded text-zinc-600 opacity-0 transition hover:bg-zinc-700 hover:text-zinc-300 group-hover:opacity-100"
|
className="flex size-5 items-center justify-center rounded text-[var(--color-text-muted)] opacity-0 transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)] group-hover:opacity-100"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onRefreshGitContext(project.id);
|
onRefreshGitContext(project.id);
|
||||||
@@ -424,8 +424,8 @@ function ProjectGroup({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{runningCount > 0 && (
|
{runningCount > 0 && (
|
||||||
<span className="flex items-center gap-1 rounded-full bg-blue-500/10 px-1.5 py-0.5 text-[10px] font-medium text-blue-400">
|
<span className="flex items-center gap-1 rounded-full bg-[var(--color-accent-sky)]/10 px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-accent-sky)]">
|
||||||
<span className="size-1.5 rounded-full bg-blue-400 sidebar-pulse" />
|
<span className="size-1.5 rounded-full bg-[var(--color-accent-sky)] sidebar-pulse" />
|
||||||
{runningCount}
|
{runningCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -442,14 +442,14 @@ function ProjectGroup({
|
|||||||
{pendingDiscoveryCount} new
|
{pendingDiscoveryCount} new
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="rounded-full bg-zinc-800 px-1.5 py-0.5 text-[10px] font-medium text-zinc-500">
|
<span className="rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]">
|
||||||
{visibleSessions.length}
|
{visibleSessions.length}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="ml-2 mt-0.5 space-y-0.5 border-l border-zinc-800/60 pl-2">
|
<div className="ml-2 mt-0.5 space-y-0.5 border-l border-[var(--color-border-subtle)] pl-2">
|
||||||
{visibleSessions.length > 0 &&
|
{visibleSessions.length > 0 &&
|
||||||
visibleSessions.map((session) => (
|
visibleSessions.map((session) => (
|
||||||
<SessionItem
|
<SessionItem
|
||||||
@@ -466,7 +466,7 @@ function ProjectGroup({
|
|||||||
))}
|
))}
|
||||||
{onNewSession ? (
|
{onNewSession ? (
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-zinc-700/60 bg-zinc-800/20 px-2.5 py-1.5 text-[12px] font-medium text-zinc-500 transition hover:border-indigo-500/40 hover:bg-indigo-500/5 hover:text-indigo-300"
|
className="flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-[var(--color-border)] bg-[var(--color-surface-1)]/40 px-2.5 py-1.5 text-[12px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:border-[var(--color-border-glow)] hover:bg-[var(--color-accent-muted)] hover:text-[var(--color-accent)]"
|
||||||
onClick={onNewSession}
|
onClick={onNewSession}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -475,7 +475,7 @@ function ProjectGroup({
|
|||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
visibleSessions.length === 0 && (
|
visibleSessions.length === 0 && (
|
||||||
<div className="px-3 py-3 text-center text-[12px] text-zinc-600">
|
<div className="px-3 py-3 text-center text-[12px] text-[var(--color-text-muted)]">
|
||||||
{isScratchpad ? 'No scratchpad chats yet' : 'No sessions yet'}
|
{isScratchpad ? 'No scratchpad chats yet' : 'No sessions yet'}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -562,19 +562,19 @@ export function Sidebar({
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* Header — extra top padding clears the title bar overlay zone */}
|
{/* Header — extra top padding clears the title bar overlay zone */}
|
||||||
<div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] px-4 pb-3 pt-3">
|
<div className="drag-region flex items-center justify-between border-b border-[var(--color-border-subtle)] px-4 pb-3 pt-3">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<img alt="aryx" className="size-8 rounded-xl" src={appIconUrl} />
|
<img alt="aryx" className="size-8 rounded-xl" src={appIconUrl} />
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-semibold text-zinc-100">aryx</span>
|
<span className="font-display text-sm font-semibold text-[var(--color-text-primary)]">aryx</span>
|
||||||
<span className="ml-1.5 rounded bg-zinc-800 px-1 py-0.5 text-[9px] font-medium text-zinc-500">
|
<span className="ml-1.5 rounded bg-[var(--color-surface-2)] px-1 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)]">
|
||||||
ALPHA
|
ALPHA
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-muted)] transition-all duration-150 hover:bg-[var(--color-surface-2)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={onOpenSettings}
|
onClick={onOpenSettings}
|
||||||
title="Settings"
|
title="Settings"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -587,16 +587,16 @@ export function Sidebar({
|
|||||||
{/* Search + Filters */}
|
{/* Search + Filters */}
|
||||||
<div className="space-y-2 px-3 pt-3 pb-1">
|
<div className="space-y-2 px-3 pt-3 pb-1">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-zinc-500" />
|
<Search className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-[var(--color-text-muted)]" />
|
||||||
<input
|
<input
|
||||||
className="w-full rounded-lg border border-zinc-800 bg-zinc-900/60 py-1.5 pl-8 pr-8 text-[12px] text-zinc-200 placeholder-zinc-600 outline-none transition focus:border-zinc-700 focus:bg-zinc-900"
|
className="w-full rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-0)]/60 py-1.5 pl-8 pr-8 text-[12px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none transition-all duration-200 focus:border-[var(--color-border-glow)] focus:bg-[var(--color-surface-0)] focus:shadow-[0_0_12px_rgba(36,92,249,0.06)]"
|
||||||
placeholder="Search sessions…"
|
placeholder="Search sessions…"
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
/>
|
/>
|
||||||
{searchText && (
|
{searchText && (
|
||||||
<button
|
<button
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300"
|
className="absolute right-2 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={() => setSearchText('')}
|
onClick={() => setSearchText('')}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -615,11 +615,11 @@ export function Sidebar({
|
|||||||
{isQueryActive ? (
|
{isQueryActive ? (
|
||||||
/* ── Flat search / filter results ──────────────────────── */
|
/* ── Flat search / filter results ──────────────────────── */
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-zinc-600">
|
<div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-muted)]">
|
||||||
Results ({queryResults.length})
|
Results ({queryResults.length})
|
||||||
</div>
|
</div>
|
||||||
{queryResults.length === 0 ? (
|
{queryResults.length === 0 ? (
|
||||||
<div className="px-3 py-6 text-center text-[12px] text-zinc-600">
|
<div className="px-3 py-6 text-center text-[12px] text-[var(--color-text-muted)]">
|
||||||
No sessions match your search
|
No sessions match your search
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -643,7 +643,7 @@ export function Sidebar({
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{scratchpadProject && (
|
{scratchpadProject && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-zinc-600">
|
<div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-muted)]">
|
||||||
Scratchpad
|
Scratchpad
|
||||||
</div>
|
</div>
|
||||||
<ProjectGroup
|
<ProjectGroup
|
||||||
@@ -666,21 +666,21 @@ export function Sidebar({
|
|||||||
{userProjects.length === 0 ? (
|
{userProjects.length === 0 ? (
|
||||||
<div className="flex flex-col items-center gap-4 px-4 py-8 text-center">
|
<div className="flex flex-col items-center gap-4 px-4 py-8 text-center">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="flex size-14 items-center justify-center rounded-2xl bg-zinc-800/50 ring-1 ring-zinc-700/50">
|
<div className="flex size-14 items-center justify-center rounded-2xl bg-[var(--color-surface-2)] ring-1 ring-[var(--color-border)]">
|
||||||
<FolderOpen className="size-7 text-zinc-600" />
|
<FolderOpen className="size-7 text-[var(--color-text-muted)]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute -bottom-1 -right-1 flex size-6 items-center justify-center rounded-full bg-indigo-600 ring-2 ring-[var(--color-surface-1)]">
|
<div className="absolute -bottom-1 -right-1 flex size-6 items-center justify-center rounded-full brand-gradient-bg ring-2 ring-[var(--color-surface-1)]">
|
||||||
<Plus className="size-3 text-white" />
|
<Plus className="size-3 text-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[13px] font-medium text-zinc-300">No projects yet</p>
|
<p className="text-[13px] font-medium text-[var(--color-text-primary)]">No projects yet</p>
|
||||||
<p className="mt-1 text-[12px] leading-relaxed text-zinc-500">
|
<p className="mt-1 text-[12px] leading-relaxed text-[var(--color-text-muted)]">
|
||||||
Use Scratchpad for ad-hoc chat or add a repo<br />to work against project files
|
Use Scratchpad for ad-hoc chat or add a repo<br />to work against project files
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="rounded-lg bg-indigo-600 px-4 py-2 text-[13px] font-medium text-white transition hover:bg-indigo-500"
|
className="rounded-lg brand-gradient-bg px-4 py-2 text-[13px] font-medium text-white shadow-[0_2px_12px_rgba(36,92,249,0.25)] transition-all duration-200 hover:shadow-[0_4px_20px_rgba(36,92,249,0.35)]"
|
||||||
onClick={onAddProject}
|
onClick={onAddProject}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -689,7 +689,7 @@ export function Sidebar({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-zinc-600">
|
<div className="px-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-muted)]">
|
||||||
Projects
|
Projects
|
||||||
</div>
|
</div>
|
||||||
{userProjects.map((project) => (
|
{userProjects.map((project) => (
|
||||||
@@ -717,9 +717,9 @@ export function Sidebar({
|
|||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
{userProjects.length > 0 && (
|
{userProjects.length > 0 && (
|
||||||
<div className="border-t border-[var(--color-border)] px-3 py-2">
|
<div className="border-t border-[var(--color-border-subtle)] px-3 py-2">
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-[12px] text-zinc-500 transition hover:bg-zinc-800/60 hover:text-zinc-300"
|
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-[12px] text-[var(--color-text-muted)] transition-all duration-150 hover:bg-[var(--color-surface-2)]/60 hover:text-[var(--color-text-primary)]"
|
||||||
onClick={onAddProject}
|
onClick={onAddProject}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -734,7 +734,7 @@ export function Sidebar({
|
|||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-40" onClick={closeMenu} onKeyDown={(e) => { if (e.key === 'Escape') closeMenu(); }} />
|
<div className="fixed inset-0 z-40" onClick={closeMenu} onKeyDown={(e) => { if (e.key === 'Escape') closeMenu(); }} />
|
||||||
<div
|
<div
|
||||||
className="fixed z-50 w-40 rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-xl"
|
className="fixed z-50 w-40 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-[0_8px_32px_rgba(0,0,0,0.4)]"
|
||||||
role="menu"
|
role="menu"
|
||||||
style={{ top: menuState.top, left: menuState.left }}
|
style={{ top: menuState.top, left: menuState.left }}
|
||||||
>
|
>
|
||||||
@@ -771,7 +771,7 @@ export function Sidebar({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ActionMenuItem
|
<ActionMenuItem
|
||||||
className="text-red-400 hover:bg-red-500/10"
|
className="text-[var(--color-status-error)] hover:bg-[var(--color-status-error)]/10"
|
||||||
icon={Trash2}
|
icon={Trash2}
|
||||||
label="Delete"
|
label="Delete"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -11,28 +11,28 @@ import type { TerminalExitInfo } from '@shared/domain/terminal';
|
|||||||
/* ── Theme ────────────────────────────────────────────────── */
|
/* ── Theme ────────────────────────────────────────────────── */
|
||||||
|
|
||||||
const terminalTheme = {
|
const terminalTheme = {
|
||||||
background: '#09090b', // zinc-950
|
background: '#07080e',
|
||||||
foreground: '#e4e4e7', // zinc-200
|
foreground: '#e8eaf0',
|
||||||
cursor: '#818cf8', // indigo-400
|
cursor: '#245CF9',
|
||||||
cursorAccent: '#09090b',
|
cursorAccent: '#07080e',
|
||||||
selectionBackground: '#6366f14d', // indigo-500/30
|
selectionBackground: 'rgba(36, 92, 249, 0.2)',
|
||||||
selectionForeground: '#e4e4e7',
|
selectionForeground: '#e8eaf0',
|
||||||
black: '#27272a', // zinc-800
|
black: '#1e2233',
|
||||||
red: '#f87171', // red-400
|
red: '#f87171',
|
||||||
green: '#4ade80', // green-400
|
green: '#4ade80',
|
||||||
yellow: '#facc15', // yellow-400
|
yellow: '#facc15',
|
||||||
blue: '#60a5fa', // blue-400
|
blue: '#248CFD',
|
||||||
magenta: '#c084fc', // purple-400
|
magenta: '#a855f7',
|
||||||
cyan: '#22d3ee', // cyan-400
|
cyan: '#22d3ee',
|
||||||
white: '#e4e4e7', // zinc-200
|
white: '#e8eaf0',
|
||||||
brightBlack: '#52525b', // zinc-600
|
brightBlack: '#4e5368',
|
||||||
brightRed: '#fca5a5', // red-300
|
brightRed: '#fca5a5',
|
||||||
brightGreen: '#86efac', // green-300
|
brightGreen: '#86efac',
|
||||||
brightYellow: '#fde047', // yellow-300
|
brightYellow: '#fde047',
|
||||||
brightBlue: '#93c5fd', // blue-300
|
brightBlue: '#60a5fa',
|
||||||
brightMagenta: '#d8b4fe',// purple-300
|
brightMagenta: '#c084fc',
|
||||||
brightCyan: '#67e8f9', // cyan-300
|
brightCyan: '#67e8f9',
|
||||||
brightWhite: '#fafafa', // zinc-50
|
brightWhite: '#f8f9fc',
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ── Constants ────────────────────────────────────────────── */
|
/* ── Constants ────────────────────────────────────────────── */
|
||||||
@@ -94,7 +94,7 @@ export function TerminalPanel({
|
|||||||
|
|
||||||
const terminal = new Terminal({
|
const terminal = new Terminal({
|
||||||
theme: terminalTheme,
|
theme: terminalTheme,
|
||||||
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace',
|
fontFamily: '"JetBrains Mono Variable", "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.4,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
@@ -214,12 +214,12 @@ export function TerminalPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col border-t border-[var(--color-border)] bg-[#09090b]"
|
className="flex flex-col border-t border-[var(--color-border)] bg-[var(--color-surface-0)]"
|
||||||
style={{ height, minHeight: MIN_HEIGHT }}
|
style={{ height, minHeight: MIN_HEIGHT }}
|
||||||
>
|
>
|
||||||
{/* Resize handle */}
|
{/* Resize handle */}
|
||||||
<div
|
<div
|
||||||
className={`h-1 shrink-0 cursor-row-resize transition-colors ${isDragging ? 'bg-indigo-500/40' : 'hover:bg-zinc-700/60'}`}
|
className={`h-1 shrink-0 cursor-row-resize transition-colors ${isDragging ? 'bg-[var(--color-accent)]/40' : 'hover:bg-[var(--color-surface-3)]/60'}`}
|
||||||
onMouseDown={handleDragStart}
|
onMouseDown={handleDragStart}
|
||||||
role="separator"
|
role="separator"
|
||||||
aria-orientation="horizontal"
|
aria-orientation="horizontal"
|
||||||
@@ -237,15 +237,15 @@ export function TerminalPanel({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Header bar */}
|
{/* Header bar */}
|
||||||
<div className="flex h-7 shrink-0 items-center gap-2 border-b border-zinc-800/60 px-3">
|
<div className="flex h-7 shrink-0 items-center gap-2 border-b border-[var(--color-border)] px-3">
|
||||||
<span className={`size-1.5 shrink-0 rounded-full ${isRunning ? 'bg-emerald-400' : 'bg-zinc-600'}`} />
|
<span className={`size-1.5 shrink-0 rounded-full ${isRunning ? 'bg-emerald-400' : 'bg-[var(--color-text-muted)]'}`} />
|
||||||
<span className="min-w-0 flex-1 truncate text-[11px] text-zinc-500">
|
<span className="min-w-0 flex-1 truncate font-mono text-[11px] text-[var(--color-text-muted)]">
|
||||||
{snapshot ? `${snapshot.shell} — ${snapshot.cwd}` : 'Terminal'}
|
{snapshot ? `${snapshot.shell} — ${snapshot.cwd}` : 'Terminal'}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-0.5">
|
<div className="flex items-center gap-0.5">
|
||||||
<button
|
<button
|
||||||
aria-label="Restart terminal"
|
aria-label="Restart terminal"
|
||||||
className="rounded p-0.5 text-zinc-600 transition hover:bg-zinc-800 hover:text-zinc-400"
|
className="rounded p-0.5 text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
|
||||||
onClick={handleRestart}
|
onClick={handleRestart}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -253,7 +253,7 @@ export function TerminalPanel({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Minimize terminal"
|
aria-label="Minimize terminal"
|
||||||
className="rounded p-0.5 text-zinc-600 transition hover:bg-zinc-800 hover:text-zinc-400"
|
className="rounded p-0.5 text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
|
||||||
onClick={onMinimize}
|
onClick={onMinimize}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -261,7 +261,7 @@ export function TerminalPanel({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Close terminal"
|
aria-label="Close terminal"
|
||||||
className="rounded p-0.5 text-zinc-600 transition hover:bg-zinc-800 hover:text-red-400"
|
className="rounded p-0.5 text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-red-400"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { MessageSquare, Plus, Settings } from 'lucide-react';
|
import { FolderPlus, MessageSquarePlus, Settings } from 'lucide-react';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
|
||||||
|
import appIconUrl from '../../../assets/icons/icon.png';
|
||||||
|
|
||||||
interface WelcomePaneProps {
|
interface WelcomePaneProps {
|
||||||
hasProjects: boolean;
|
hasProjects: boolean;
|
||||||
@@ -7,6 +10,42 @@ interface WelcomePaneProps {
|
|||||||
onOpenSettings: () => void;
|
onOpenSettings: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fadeUp = (delay: number) =>
|
||||||
|
({
|
||||||
|
initial: { opacity: 0, y: 12 },
|
||||||
|
animate: { opacity: 1, y: 0 },
|
||||||
|
transition: { duration: 0.45, ease: [0.25, 0.46, 0.45, 0.94] as const, delay },
|
||||||
|
}) as const;
|
||||||
|
|
||||||
|
interface ActionCardProps {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ActionCard({ icon, title, description, onClick }: ActionCardProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
className="group flex w-full cursor-pointer items-center gap-4 rounded-xl border border-[var(--color-glass-border)] bg-[var(--color-glass)] px-5 py-4 text-left backdrop-blur-sm transition-all duration-200 hover:border-[var(--color-border-glow)] hover:shadow-[0_0_20px_rgba(36,92,249,0.08),0_4px_12px_rgba(0,0,0,0.2)]"
|
||||||
|
>
|
||||||
|
<div className="brand-gradient-bg flex size-9 shrink-0 items-center justify-center rounded-full">
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<span className="block text-[13px] font-medium text-[var(--color-text-primary)]">
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
<span className="block text-[12px] leading-relaxed text-[var(--color-text-muted)]">
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function WelcomePane({
|
export function WelcomePane({
|
||||||
hasProjects,
|
hasProjects,
|
||||||
onNewScratchpad,
|
onNewScratchpad,
|
||||||
@@ -14,48 +53,66 @@ export function WelcomePane({
|
|||||||
onOpenSettings,
|
onOpenSettings,
|
||||||
}: WelcomePaneProps) {
|
}: WelcomePaneProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col items-center justify-center px-8">
|
<div className="relative flex h-full flex-col items-center justify-center overflow-hidden px-8">
|
||||||
<div className="flex flex-col items-center gap-6 text-center">
|
{/* Ambient nebula glow */}
|
||||||
<div className="flex size-16 items-center justify-center rounded-2xl bg-indigo-600/10">
|
<div
|
||||||
<MessageSquare className="size-8 text-indigo-400" />
|
className="pointer-events-none absolute inset-0"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
background: [
|
||||||
|
'radial-gradient(ellipse 50% 40% at 50% 45%, rgba(36, 92, 249, 0.07) 0%, transparent 70%)',
|
||||||
|
'radial-gradient(ellipse 40% 35% at 55% 50%, rgba(138, 41, 230, 0.05) 0%, transparent 65%)',
|
||||||
|
'radial-gradient(ellipse 60% 50% at 45% 48%, rgba(54, 21, 207, 0.04) 0%, transparent 60%)',
|
||||||
|
].join(', '),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div>
|
<div className="relative z-10 flex w-full max-w-xs flex-col items-center gap-8 text-center">
|
||||||
<h1 className="text-base font-semibold text-zinc-100">Welcome to aryx</h1>
|
{/* Icon */}
|
||||||
<p className="mt-2 max-w-md text-[13px] leading-relaxed text-zinc-500">
|
<motion.div {...fadeUp(0)}>
|
||||||
|
<img
|
||||||
|
src={appIconUrl}
|
||||||
|
alt="aryx"
|
||||||
|
width={64}
|
||||||
|
height={64}
|
||||||
|
className="drop-shadow-[0_0_24px_rgba(36,92,249,0.3)]"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Heading */}
|
||||||
|
<motion.div {...fadeUp(0.08)}>
|
||||||
|
<h1 className="font-display brand-gradient-text text-2xl font-bold tracking-tight">
|
||||||
|
aryx
|
||||||
|
</h1>
|
||||||
|
<p className="mt-2 max-w-sm text-[13px] leading-relaxed text-[var(--color-text-secondary)]">
|
||||||
Start a scratchpad conversation for ad-hoc questions or connect a project to work with
|
Start a scratchpad conversation for ad-hoc questions or connect a project to work with
|
||||||
repo-aware Copilot agents.
|
repo-aware Copilot agents.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="flex flex-col items-center gap-2">
|
{/* Action cards */}
|
||||||
<button
|
<motion.div {...fadeUp(0.16)} className="flex w-full flex-col gap-2.5">
|
||||||
className="flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-2.5 text-[13px] font-medium text-white transition hover:bg-indigo-500"
|
<ActionCard
|
||||||
|
icon={<MessageSquarePlus className="size-4 text-white" />}
|
||||||
|
title="New Scratchpad"
|
||||||
|
description="Ask anything without a project context"
|
||||||
onClick={onNewScratchpad}
|
onClick={onNewScratchpad}
|
||||||
type="button"
|
/>
|
||||||
>
|
|
||||||
<Plus className="size-4" />
|
|
||||||
New Scratchpad
|
|
||||||
</button>
|
|
||||||
{!hasProjects && (
|
{!hasProjects && (
|
||||||
<button
|
<ActionCard
|
||||||
className="flex items-center gap-2 rounded-lg px-4 py-2 text-[13px] text-zinc-500 transition hover:bg-zinc-900 hover:text-zinc-300"
|
icon={<FolderPlus className="size-4 text-white" />}
|
||||||
|
title="Add Your First Project"
|
||||||
|
description="Connect a repo for full agent support"
|
||||||
onClick={onAddProject}
|
onClick={onAddProject}
|
||||||
type="button"
|
/>
|
||||||
>
|
|
||||||
<Plus className="size-3.5" />
|
|
||||||
Add Your First Project
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
<button
|
<ActionCard
|
||||||
className="flex items-center gap-2 rounded-lg px-4 py-2 text-[13px] text-zinc-500 transition hover:bg-zinc-900 hover:text-zinc-300"
|
icon={<Settings className="size-4 text-white" />}
|
||||||
|
title="Manage Patterns"
|
||||||
|
description="Customize agent behaviors and workflows"
|
||||||
onClick={onOpenSettings}
|
onClick={onOpenSettings}
|
||||||
type="button"
|
/>
|
||||||
>
|
</motion.div>
|
||||||
<Settings className="size-3.5" />
|
|
||||||
Manage patterns
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,50 +30,50 @@ export function ApprovalBanner({
|
|||||||
const approvalToolLabel = approvalToolKey ? resolveToolLabel(approvalToolKey) : undefined;
|
const approvalToolLabel = approvalToolKey ? resolveToolLabel(approvalToolKey) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-amber-500/30 bg-amber-500/5 px-4 py-3" role="alert">
|
<div className="rounded-xl border border-[var(--color-glass-border)] border-l-4 border-l-[var(--color-status-warning)] bg-[var(--color-glass)] px-4 py-3" role="alert">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start gap-2.5">
|
<div className="flex items-start gap-2.5">
|
||||||
<ShieldAlert className="mt-0.5 size-4 shrink-0 text-amber-400" />
|
<ShieldAlert className="mt-0.5 size-4 shrink-0 text-[var(--color-status-warning)]" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[13px] font-semibold text-amber-200">{approval.title}</span>
|
<span className="text-[13px] font-semibold text-[var(--color-status-warning)]">{approval.title}</span>
|
||||||
<span className="rounded-full bg-amber-500/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-amber-400">
|
<span className="rounded-full bg-[var(--color-status-warning)]/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-warning)]">
|
||||||
{kindLabel}
|
{kindLabel}
|
||||||
</span>
|
</span>
|
||||||
{showPosition && (
|
{showPosition && (
|
||||||
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-[9px] font-semibold tabular-nums text-zinc-400">
|
<span className="rounded-full bg-[var(--color-surface-2)] px-2 py-0.5 text-[9px] font-semibold tabular-nums text-[var(--color-text-secondary)]">
|
||||||
{position} of {total}
|
{position} of {total}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-1 flex flex-wrap items-center gap-2 text-[11px] text-zinc-400">
|
<div className="mt-1 flex flex-wrap items-center gap-2 text-[11px] text-[var(--color-text-secondary)]">
|
||||||
{approval.agentName && <span>Agent: <span className="text-zinc-300">{approval.agentName}</span></span>}
|
{approval.agentName && <span>Agent: <span className="text-[var(--color-text-primary)]">{approval.agentName}</span></span>}
|
||||||
{approval.toolName && <span>Tool: <span className="text-zinc-300">{approval.toolName}</span></span>}
|
{approval.toolName && <span>Tool: <span className="text-[var(--color-text-primary)]">{approval.toolName}</span></span>}
|
||||||
{approval.permissionKind && <span>Permission: <span className="text-zinc-300">{approval.permissionKind}</span></span>}
|
{approval.permissionKind && <span>Permission: <span className="text-[var(--color-text-primary)]">{approval.permissionKind}</span></span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{approval.permissionDetail
|
{approval.permissionDetail
|
||||||
? <PermissionDetailView detail={approval.permissionDetail} />
|
? <PermissionDetailView detail={approval.permissionDetail} />
|
||||||
: approval.detail && (
|
: approval.detail && (
|
||||||
<p className="mt-1.5 text-[12px] leading-relaxed text-zinc-400">{approval.detail}</p>
|
<p className="mt-1.5 text-[12px] leading-relaxed text-[var(--color-text-secondary)]">{approval.detail}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Final-response message preview */}
|
{/* Final-response message preview */}
|
||||||
{hasMessages && (
|
{hasMessages && (
|
||||||
<div className="mt-3 space-y-2 rounded-lg border border-zinc-800 bg-zinc-900/60 p-3">
|
<div className="mt-3 space-y-2 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] p-3">
|
||||||
<p className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
|
<p className="text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Pending messages — not yet published
|
Pending messages — not yet published
|
||||||
</p>
|
</p>
|
||||||
{approval.messages!.map((message) => (
|
{approval.messages!.map((message) => (
|
||||||
<div className="mt-2" key={message.id}>
|
<div className="mt-2" key={message.id}>
|
||||||
<div className="mb-1 flex items-center gap-2 text-[11px] font-medium text-zinc-500">
|
<div className="mb-1 flex items-center gap-2 text-[11px] font-medium text-[var(--color-text-muted)]">
|
||||||
<Bot className="size-3" />
|
<Bot className="size-3" />
|
||||||
<span>{message.authorName}</span>
|
<span>{message.authorName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg border border-zinc-800/60 bg-zinc-900/40 px-3 py-2 text-[13px] leading-relaxed text-zinc-300">
|
<div className="rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]/60 px-3 py-2 text-[13px] leading-relaxed text-[var(--color-text-secondary)]">
|
||||||
<MarkdownContent content={message.content} />
|
<MarkdownContent content={message.content} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,7 +84,7 @@ export function ApprovalBanner({
|
|||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="mt-3 flex items-center gap-2">
|
<div className="mt-3 flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-600 px-3.5 py-1.5 text-[12px] font-medium text-white transition hover:bg-emerald-500 disabled:cursor-not-allowed disabled:opacity-50"
|
className="brand-gradient-bg inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-[12px] font-medium text-white transition-all duration-200 hover:brightness-110 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={isResolving}
|
disabled={isResolving}
|
||||||
onClick={() => onResolve('approved')}
|
onClick={() => onResolve('approved')}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -95,7 +95,7 @@ export function ApprovalBanner({
|
|||||||
{canAlwaysApprove && (
|
{canAlwaysApprove && (
|
||||||
<button
|
<button
|
||||||
aria-label={`Always approve ${approvalToolLabel}`}
|
aria-label={`Always approve ${approvalToolLabel}`}
|
||||||
className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-600/20 px-3.5 py-1.5 text-[12px] font-medium text-emerald-300 transition hover:bg-emerald-600/30 disabled:cursor-not-allowed disabled:opacity-50"
|
className="inline-flex items-center gap-1.5 rounded-lg bg-[var(--color-status-success)]/15 px-3.5 py-1.5 text-[12px] font-medium text-[var(--color-status-success)] transition-all duration-200 hover:bg-[var(--color-status-success)]/25 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={isResolving}
|
disabled={isResolving}
|
||||||
onClick={() => onResolve('approved', true)}
|
onClick={() => onResolve('approved', true)}
|
||||||
title={`Auto-approve "${approvalToolLabel}" for the rest of this session`}
|
title={`Auto-approve "${approvalToolLabel}" for the rest of this session`}
|
||||||
@@ -106,7 +106,7 @@ export function ApprovalBanner({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="inline-flex items-center gap-1.5 rounded-lg bg-zinc-800 px-3.5 py-1.5 text-[12px] font-medium text-zinc-300 transition hover:bg-zinc-700 hover:text-white disabled:cursor-not-allowed disabled:opacity-50"
|
className="inline-flex items-center gap-1.5 rounded-lg bg-[var(--color-surface-2)] px-3.5 py-1.5 text-[12px] font-medium text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={isResolving}
|
disabled={isResolving}
|
||||||
onClick={() => onResolve('rejected')}
|
onClick={() => onResolve('rejected')}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -115,7 +115,7 @@ export function ApprovalBanner({
|
|||||||
Reject
|
Reject
|
||||||
</button>
|
</button>
|
||||||
{showPosition && (
|
{showPosition && (
|
||||||
<span className="ml-auto text-[10px] text-zinc-600">
|
<span className="ml-auto text-[10px] text-[var(--color-text-muted)]">
|
||||||
Next approval will appear after this one is resolved
|
Next approval will appear after this one is resolved
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -130,42 +130,42 @@ export function QueuedApprovalsList({ approvals }: { approvals: PendingApprovalR
|
|||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-zinc-800 bg-zinc-900/40 px-3 py-2">
|
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-glass)] px-3 py-2">
|
||||||
<button
|
<button
|
||||||
aria-expanded={expanded}
|
aria-expanded={expanded}
|
||||||
className="flex w-full items-center gap-2 text-left"
|
className="flex w-full items-center gap-2 text-left"
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<ShieldCheck className="size-3 text-zinc-500" />
|
<ShieldCheck className="size-3 text-[var(--color-text-muted)]" />
|
||||||
<span className="text-[11px] font-medium text-zinc-400">
|
<span className="text-[11px] font-medium text-[var(--color-text-secondary)]">
|
||||||
{approvals.length} queued approval{approvals.length === 1 ? '' : 's'}
|
{approvals.length} queued approval{approvals.length === 1 ? '' : 's'}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`ml-auto size-3 text-zinc-600 transition-transform ${expanded ? 'rotate-180' : ''}`}
|
className={`ml-auto size-3 text-[var(--color-text-muted)] transition-transform ${expanded ? 'rotate-180' : ''}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="mt-2 space-y-1.5 border-t border-zinc-800/60 pt-2">
|
<div className="mt-2 space-y-1.5 border-t border-[var(--color-border-subtle)] pt-2">
|
||||||
{approvals.map((approval) => {
|
{approvals.map((approval) => {
|
||||||
const kindLabel = approval.kind === 'final-response' ? 'response' : 'tool';
|
const kindLabel = approval.kind === 'final-response' ? 'response' : 'tool';
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-2 rounded-md bg-zinc-800/40 px-2.5 py-1.5"
|
className="flex items-center gap-2 rounded-md bg-[var(--color-surface-2)]/40 px-2.5 py-1.5"
|
||||||
key={approval.id}
|
key={approval.id}
|
||||||
>
|
>
|
||||||
<ShieldAlert className="size-3 shrink-0 text-zinc-600" />
|
<ShieldAlert className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
<span className="min-w-0 flex-1 truncate text-[11px] text-zinc-400">
|
<span className="min-w-0 flex-1 truncate text-[11px] text-[var(--color-text-secondary)]">
|
||||||
{(approval.permissionDetail && permissionDetailSummary(approval.permissionDetail)) || approval.title}
|
{(approval.permissionDetail && permissionDetailSummary(approval.permissionDetail)) || approval.title}
|
||||||
</span>
|
</span>
|
||||||
<span className="shrink-0 rounded-full bg-zinc-800 px-1.5 py-0.5 text-[8px] font-semibold uppercase tracking-wider text-zinc-500">
|
<span className="shrink-0 rounded-full bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[8px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
{kindLabel}
|
{kindLabel}
|
||||||
</span>
|
</span>
|
||||||
{approval.toolName && (
|
{approval.toolName && (
|
||||||
<span className="shrink-0 text-[10px] text-zinc-500">{approval.toolName}</span>
|
<span className="shrink-0 text-[10px] text-[var(--color-text-muted)]">{approval.toolName}</span>
|
||||||
)}
|
)}
|
||||||
{approval.agentName && (
|
{approval.agentName && (
|
||||||
<span className="shrink-0 text-[10px] text-zinc-600">{approval.agentName}</span>
|
<span className="shrink-0 text-[10px] text-[var(--color-text-muted)]">{approval.agentName}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import { RotateCcw, Server, ShieldCheck } from 'lucide-react';
|
|||||||
function TierBadge({ tier }: { tier: ModelDefinition['tier'] }) {
|
function TierBadge({ tier }: { tier: ModelDefinition['tier'] }) {
|
||||||
if (!tier) return null;
|
if (!tier) return null;
|
||||||
const styles = {
|
const styles = {
|
||||||
premium: 'bg-amber-500/10 text-amber-400',
|
premium: 'bg-[var(--color-status-warning)]/10 text-[var(--color-status-warning)]',
|
||||||
standard: 'bg-zinc-700/50 text-zinc-500',
|
standard: 'bg-[var(--color-surface-3)]/50 text-[var(--color-text-muted)]',
|
||||||
fast: 'bg-emerald-500/10 text-emerald-400',
|
fast: 'bg-[var(--color-status-success)]/10 text-[var(--color-status-success)]',
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<span className={`ml-auto rounded px-1.5 py-0.5 text-[9px] font-medium ${styles[tier]}`}>
|
<span className={`ml-auto rounded px-1.5 py-0.5 text-[9px] font-medium ${styles[tier]}`}>
|
||||||
@@ -64,10 +64,10 @@ export function InlineModelPill({
|
|||||||
<button
|
<button
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition ${
|
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition-all duration-200 ${
|
||||||
open
|
open
|
||||||
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300'
|
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
|
||||||
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
|
: 'border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/40 text-[var(--color-text-secondary)] hover:border-[var(--color-border)] hover:text-[var(--color-text-primary)]'
|
||||||
} disabled:cursor-not-allowed disabled:opacity-50`}
|
} disabled:cursor-not-allowed disabled:opacity-50`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
@@ -79,19 +79,19 @@ export function InlineModelPill({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{open && !disabled && (
|
{open && !disabled && (
|
||||||
<div className="absolute bottom-full right-0 z-40 mb-1.5 max-h-72 w-64 overflow-y-auto rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-2xl" role="listbox">
|
<div className="absolute bottom-full right-0 z-40 mb-1.5 max-h-72 w-64 overflow-y-auto rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-2xl" role="listbox">
|
||||||
{groupedModels.map((pg) => (
|
{groupedModels.map((pg) => (
|
||||||
<div key={pg.id}>
|
<div key={pg.id}>
|
||||||
<div className="flex items-center gap-2 px-3 pb-1 pt-2.5">
|
<div className="flex items-center gap-2 px-3 pb-1 pt-2.5">
|
||||||
<ProviderIcon provider={pg.id} className="size-3.5" />
|
<ProviderIcon provider={pg.id} className="size-3.5" />
|
||||||
<span className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
|
<span className="text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
{pg.label}
|
{pg.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{pg.models.map((model) => (
|
{pg.models.map((model) => (
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
|
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
|
||||||
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
|
model.id === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-primary)]'
|
||||||
}`}
|
}`}
|
||||||
key={model.id}
|
key={model.id}
|
||||||
onClick={() => { onChange(model.id); setOpen(false); }}
|
onClick={() => { onChange(model.id); setOpen(false); }}
|
||||||
@@ -107,13 +107,13 @@ export function InlineModelPill({
|
|||||||
))}
|
))}
|
||||||
{otherModels.length > 0 && (
|
{otherModels.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="px-3 pb-1 pt-2.5 text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
|
<div className="px-3 pb-1 pt-2.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Other
|
Other
|
||||||
</div>
|
</div>
|
||||||
{otherModels.map((model) => (
|
{otherModels.map((model) => (
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
|
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
|
||||||
model.id === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
|
model.id === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-primary)]'
|
||||||
}`}
|
}`}
|
||||||
key={model.id}
|
key={model.id}
|
||||||
onClick={() => { onChange(model.id); setOpen(false); }}
|
onClick={() => { onChange(model.id); setOpen(false); }}
|
||||||
@@ -155,7 +155,7 @@ export function InlineThinkingPill({
|
|||||||
|
|
||||||
if (supportedEfforts && supportedEfforts.length === 0) {
|
if (supportedEfforts && supportedEfforts.length === 0) {
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 rounded border border-zinc-800/40 bg-zinc-800/20 px-1.5 py-0.5 text-pill text-zinc-600">
|
<span className="inline-flex items-center gap-1 rounded border border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/20 px-1.5 py-0.5 text-pill text-[var(--color-text-muted)]">
|
||||||
<Sparkles className="size-2.5" />
|
<Sparkles className="size-2.5" />
|
||||||
N/A
|
N/A
|
||||||
</span>
|
</span>
|
||||||
@@ -169,10 +169,10 @@ export function InlineThinkingPill({
|
|||||||
<button
|
<button
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition ${
|
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition-all duration-200 ${
|
||||||
open
|
open
|
||||||
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300'
|
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
|
||||||
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
|
: 'border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/40 text-[var(--color-text-secondary)] hover:border-[var(--color-border)] hover:text-[var(--color-text-primary)]'
|
||||||
} disabled:cursor-not-allowed disabled:opacity-50`}
|
} disabled:cursor-not-allowed disabled:opacity-50`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
@@ -184,11 +184,11 @@ export function InlineThinkingPill({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{open && !disabled && (
|
{open && !disabled && (
|
||||||
<div className="absolute bottom-full right-0 z-40 mb-1.5 w-36 overflow-hidden rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-2xl" role="listbox">
|
<div className="absolute bottom-full right-0 z-40 mb-1.5 w-36 overflow-hidden rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-2xl" role="listbox">
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition hover:bg-zinc-800 ${
|
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] transition-all duration-200 hover:bg-[var(--color-surface-3)] ${
|
||||||
option.value === value ? 'bg-indigo-500/10 text-indigo-200' : 'text-zinc-300'
|
option.value === value ? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]' : 'text-[var(--color-text-primary)]'
|
||||||
}`}
|
}`}
|
||||||
key={option.value}
|
key={option.value}
|
||||||
onClick={() => { onChange(option.value); setOpen(false); }}
|
onClick={() => { onChange(option.value); setOpen(false); }}
|
||||||
@@ -235,10 +235,10 @@ export function InlineToolsPill({
|
|||||||
<button
|
<button
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition ${
|
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition-all duration-200 ${
|
||||||
open
|
open
|
||||||
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300'
|
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
|
||||||
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
|
: 'border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/40 text-[var(--color-text-secondary)] hover:border-[var(--color-border)] hover:text-[var(--color-text-primary)]'
|
||||||
} disabled:cursor-not-allowed disabled:opacity-50`}
|
} disabled:cursor-not-allowed disabled:opacity-50`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
@@ -250,7 +250,7 @@ export function InlineToolsPill({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{open && !disabled && (
|
{open && !disabled && (
|
||||||
<div className="absolute bottom-full left-0 z-40 mb-1.5 w-64 overflow-y-auto rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-2xl">
|
<div className="absolute bottom-full left-0 z-40 mb-1.5 w-64 overflow-y-auto rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] py-1 shadow-2xl">
|
||||||
{workspaceMcpServers.length > 0 && (
|
{workspaceMcpServers.length > 0 && (
|
||||||
<McpServerGroup
|
<McpServerGroup
|
||||||
label="Workspace MCP"
|
label="Workspace MCP"
|
||||||
@@ -277,7 +277,7 @@ export function InlineToolsPill({
|
|||||||
)}
|
)}
|
||||||
{lspProfiles.length > 0 && (
|
{lspProfiles.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="px-3 pb-1 pt-2 text-[9px] font-semibold uppercase tracking-wider text-zinc-600">
|
<div className="px-3 pb-1 pt-2 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Language Servers
|
Language Servers
|
||||||
</div>
|
</div>
|
||||||
{lspProfiles.map((profile) => (
|
{lspProfiles.map((profile) => (
|
||||||
@@ -315,7 +315,7 @@ function McpServerGroup({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="px-3 pb-1 pt-2 text-[9px] font-semibold uppercase tracking-wider text-zinc-600">
|
<div className="px-3 pb-1 pt-2 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
{servers.map((server) => (
|
{servers.map((server) => (
|
||||||
@@ -475,12 +475,12 @@ export function InlineApprovalPill({
|
|||||||
<button
|
<button
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition ${
|
className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-pill font-medium transition-all duration-200 ${
|
||||||
open
|
open
|
||||||
? 'border-indigo-500/40 bg-indigo-500/10 text-indigo-300'
|
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] text-[var(--color-text-accent)]'
|
||||||
: isOverridden
|
: isOverridden
|
||||||
? 'border-amber-500/30 bg-amber-500/5 text-amber-400 hover:border-amber-500/50'
|
? 'border-[var(--color-status-warning)]/30 bg-[var(--color-status-warning)]/5 text-[var(--color-status-warning)] hover:border-[var(--color-status-warning)]/50'
|
||||||
: 'border-zinc-700/60 bg-zinc-800/40 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
|
: 'border-[var(--color-border-subtle)] bg-[var(--color-surface-2)]/40 text-[var(--color-text-secondary)] hover:border-[var(--color-border)] hover:text-[var(--color-text-primary)]'
|
||||||
} disabled:cursor-not-allowed disabled:opacity-50`}
|
} disabled:cursor-not-allowed disabled:opacity-50`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
@@ -493,26 +493,26 @@ export function InlineApprovalPill({
|
|||||||
)}
|
)}
|
||||||
<span>
|
<span>
|
||||||
{effectiveAutoApprovedCount}/{totalItemCount} auto-approved
|
{effectiveAutoApprovedCount}/{totalItemCount} auto-approved
|
||||||
{isProbingAny && <span className="text-zinc-500"> · probing…</span>}
|
{isProbingAny && <span className="text-[var(--color-text-muted)]"> · probing…</span>}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown className={`size-2.5 transition ${open ? 'rotate-180' : ''}`} />
|
<ChevronDown className={`size-2.5 transition ${open ? 'rotate-180' : ''}`} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{open && !disabled && (
|
{open && !disabled && (
|
||||||
<div className="absolute bottom-full left-0 z-40 mb-1.5 max-h-[28rem] w-80 overflow-y-auto rounded-lg border border-zinc-700 bg-zinc-900 shadow-2xl">
|
<div className="absolute bottom-full left-0 z-40 mb-1.5 max-h-[28rem] w-80 overflow-y-auto rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-2xl">
|
||||||
{/* Header: session override / pattern defaults */}
|
{/* Header: session override / pattern defaults */}
|
||||||
<div className="sticky top-0 z-10 border-b border-zinc-800 bg-zinc-900">
|
<div className="sticky top-0 z-10 border-b border-[var(--color-border)] bg-[var(--color-surface-1)]">
|
||||||
<div className="flex items-center gap-2 px-3 py-2">
|
<div className="flex items-center gap-2 px-3 py-2">
|
||||||
<span className={`rounded-full px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider ${
|
<span className={`rounded-full px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider ${
|
||||||
isOverridden
|
isOverridden
|
||||||
? 'bg-amber-500/15 text-amber-400'
|
? 'bg-[var(--color-status-warning)]/15 text-[var(--color-status-warning)]'
|
||||||
: 'bg-zinc-800 text-zinc-500'
|
: 'bg-[var(--color-surface-2)] text-[var(--color-text-muted)]'
|
||||||
}`}>
|
}`}>
|
||||||
{isOverridden ? 'Session override' : 'Pattern defaults'}
|
{isOverridden ? 'Session override' : 'Pattern defaults'}
|
||||||
</span>
|
</span>
|
||||||
{isOverridden && (
|
{isOverridden && (
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1 rounded-full px-2 py-0.5 text-[9px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="flex items-center gap-1 rounded-full px-2 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={() => onUpdate({})}
|
onClick={() => onUpdate({})}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -524,12 +524,12 @@ export function InlineApprovalPill({
|
|||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
{showSearch && (
|
{showSearch && (
|
||||||
<div className="border-t border-zinc-800/50 px-3 py-1.5">
|
<div className="border-t border-[var(--color-border-subtle)] px-3 py-1.5">
|
||||||
<div className="flex items-center gap-2 rounded border border-zinc-800 bg-zinc-800/30 px-2 py-1">
|
<div className="flex items-center gap-2 rounded border border-[var(--color-border)] bg-[var(--color-surface-2)]/30 px-2 py-1">
|
||||||
<Search className="size-3 shrink-0 text-zinc-600" />
|
<Search className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
<input
|
<input
|
||||||
autoFocus
|
autoFocus
|
||||||
className="w-full bg-transparent text-[12px] text-zinc-300 placeholder-zinc-600 outline-none"
|
className="w-full bg-transparent text-[12px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none"
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
placeholder="Filter tools…"
|
placeholder="Filter tools…"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -558,31 +558,31 @@ export function InlineApprovalPill({
|
|||||||
<div key={group.id}>
|
<div key={group.id}>
|
||||||
{/* Group header */}
|
{/* Group header */}
|
||||||
{isBuiltin ? (
|
{isBuiltin ? (
|
||||||
<div className={`px-3 pb-1 ${groupIdx > 0 ? 'pt-2.5' : 'pt-1'} text-[9px] font-semibold uppercase tracking-wider text-zinc-600`}>
|
<div className={`px-3 pb-1 ${groupIdx > 0 ? 'pt-2.5' : 'pt-1'} text-[9px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]`}>
|
||||||
{group.label}
|
{group.label}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={`flex w-full cursor-pointer items-center gap-1.5 px-2.5 py-1.5 text-left transition hover:bg-zinc-800/60 ${groupIdx > 0 ? 'mt-0.5' : ''}`}
|
className={`flex w-full cursor-pointer items-center gap-1.5 px-2.5 py-1.5 text-left transition-all duration-200 hover:bg-[var(--color-surface-3)]/60 ${groupIdx > 0 ? 'mt-0.5' : ''}`}
|
||||||
onClick={() => toggleExpanded(group.id)}
|
onClick={() => toggleExpanded(group.id)}
|
||||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleExpanded(group.id); } }}
|
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleExpanded(group.id); } }}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
{probing ? (
|
{probing ? (
|
||||||
<Loader2 className="size-3 shrink-0 animate-spin text-indigo-400" aria-label="Probing server" />
|
<Loader2 className="size-3 shrink-0 animate-spin text-[var(--color-text-accent)]" aria-label="Probing server" />
|
||||||
) : group.tools.length > 0 ? (
|
) : group.tools.length > 0 ? (
|
||||||
<ChevronRight className={`size-3 shrink-0 text-zinc-600 transition ${expanded ? 'rotate-90' : ''}`} />
|
<ChevronRight className={`size-3 shrink-0 text-[var(--color-text-muted)] transition ${expanded ? 'rotate-90' : ''}`} />
|
||||||
) : (
|
) : (
|
||||||
<Server className="size-3 shrink-0 text-zinc-600" />
|
<Server className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
)}
|
)}
|
||||||
<span className="min-w-0 flex-1 truncate text-[12px] font-medium text-zinc-300">{group.label}</span>
|
<span className="min-w-0 flex-1 truncate text-[12px] font-medium text-[var(--color-text-primary)]">{group.label}</span>
|
||||||
{probing ? (
|
{probing ? (
|
||||||
<span className="shrink-0 rounded-full bg-indigo-500/10 px-1.5 py-px text-[9px] font-medium text-indigo-400">
|
<span className="shrink-0 rounded-full bg-[var(--color-accent-muted)] px-1.5 py-px text-[9px] font-medium text-[var(--color-text-accent)]">
|
||||||
probing…
|
probing…
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="shrink-0 rounded-full bg-zinc-800/80 px-1.5 py-px text-[9px] font-medium tabular-nums text-zinc-500">
|
<span className="shrink-0 rounded-full bg-[var(--color-surface-2)]/80 px-1.5 py-px text-[9px] font-medium tabular-nums text-[var(--color-text-muted)]">
|
||||||
{approvedLabel}
|
{approvedLabel}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -619,7 +619,7 @@ export function InlineApprovalPill({
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{filteredGroups.length === 0 && searchLower && (
|
{filteredGroups.length === 0 && searchLower && (
|
||||||
<div className="px-3 py-4 text-center text-[12px] text-zinc-600">
|
<div className="px-3 py-4 text-center text-[12px] text-[var(--color-text-muted)]">
|
||||||
No tools match "{search}"
|
No tools match "{search}"
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -643,13 +643,13 @@ function GroupToggle({
|
|||||||
<button
|
<button
|
||||||
aria-pressed={allApproved}
|
aria-pressed={allApproved}
|
||||||
className={`relative inline-flex h-[16px] w-[28px] shrink-0 items-center rounded-full transition-colors ${
|
className={`relative inline-flex h-[16px] w-[28px] shrink-0 items-center rounded-full transition-colors ${
|
||||||
allApproved ? 'bg-indigo-500' : someApproved ? 'bg-zinc-600' : 'bg-zinc-700'
|
allApproved ? 'bg-[var(--color-accent)]' : someApproved ? 'bg-[var(--color-surface-3)]' : 'bg-[var(--color-surface-3)]'
|
||||||
}`}
|
}`}
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{someApproved ? (
|
{someApproved ? (
|
||||||
<Minus className="absolute left-1/2 size-2 -translate-x-1/2 text-zinc-300" strokeWidth={3} />
|
<Minus className="absolute left-1/2 size-2 -translate-x-1/2 text-[var(--color-text-primary)]" strokeWidth={3} />
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className={`inline-block size-[12px] rounded-full bg-white shadow transition-transform ${
|
className={`inline-block size-[12px] rounded-full bg-white shadow transition-transform ${
|
||||||
@@ -677,16 +677,16 @@ export function InlineTerminalPill({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
aria-pressed={isOpen}
|
aria-pressed={isOpen}
|
||||||
className={`inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium transition ${
|
className={`inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium transition-all duration-200 ${
|
||||||
isOpen
|
isOpen
|
||||||
? 'bg-indigo-500/15 text-indigo-300 hover:bg-indigo-500/20'
|
? 'bg-[var(--color-accent-muted)] text-[var(--color-text-accent)] hover:bg-[var(--color-accent)]/20'
|
||||||
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
|
: 'text-[var(--color-text-muted)] hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]'
|
||||||
} disabled:cursor-not-allowed disabled:opacity-50`}
|
} disabled:cursor-not-allowed disabled:opacity-50`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{isRunning && <span className="size-1.5 shrink-0 rounded-full bg-emerald-400" />}
|
{isRunning && <span className="size-1.5 shrink-0 rounded-full bg-[var(--color-status-success)]" />}
|
||||||
<TerminalSquare className="size-3" />
|
<TerminalSquare className="size-3" />
|
||||||
<span>Terminal</span>
|
<span>Terminal</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -66,19 +66,19 @@ export function InlinePromptPill({
|
|||||||
<button
|
<button
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
className="inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<FileText className="size-3" />
|
<FileText className="size-3" />
|
||||||
Prompts
|
Prompts
|
||||||
<span className="text-zinc-600">({promptFiles.length})</span>
|
<span className="text-[var(--color-text-muted)]">({promptFiles.length})</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{open && !disabled && (
|
{open && !disabled && (
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-full left-0 z-40 mb-1.5 w-80 rounded-lg border border-zinc-700 bg-zinc-900 shadow-xl"
|
className="absolute bottom-full left-0 z-40 mb-1.5 w-80 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-xl"
|
||||||
role="listbox"
|
role="listbox"
|
||||||
>
|
>
|
||||||
{selectedPrompt ? (
|
{selectedPrompt ? (
|
||||||
@@ -114,24 +114,24 @@ function PromptList({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="max-h-64 overflow-y-auto py-1">
|
<div className="max-h-64 overflow-y-auto py-1">
|
||||||
<div className="px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
|
<div className="px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Prompt files
|
Prompt files
|
||||||
</div>
|
</div>
|
||||||
{promptFiles.map((prompt) => (
|
{promptFiles.map((prompt) => (
|
||||||
<button
|
<button
|
||||||
key={prompt.id}
|
key={prompt.id}
|
||||||
className="flex w-full items-start gap-2.5 px-3 py-2 text-left transition hover:bg-zinc-800"
|
className="flex w-full items-start gap-2.5 px-3 py-2 text-left transition-all duration-200 hover:bg-[var(--color-surface-3)]"
|
||||||
onClick={() => onSelect(prompt)}
|
onClick={() => onSelect(prompt)}
|
||||||
role="option"
|
role="option"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<FileText className="mt-0.5 size-3.5 shrink-0 text-zinc-500" />
|
<FileText className="mt-0.5 size-3.5 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="truncate text-[12px] font-medium text-zinc-200">
|
<div className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">
|
||||||
{prompt.name}
|
{prompt.name}
|
||||||
</div>
|
</div>
|
||||||
{prompt.description && (
|
{prompt.description && (
|
||||||
<div className="mt-0.5 truncate text-[11px] text-zinc-500">
|
<div className="mt-0.5 truncate text-[11px] text-[var(--color-text-muted)]">
|
||||||
{prompt.description}
|
{prompt.description}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -140,7 +140,7 @@ function PromptList({
|
|||||||
{prompt.variables.map((v) => (
|
{prompt.variables.map((v) => (
|
||||||
<span
|
<span
|
||||||
key={v.name}
|
key={v.name}
|
||||||
className="rounded bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-500"
|
className="rounded bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[10px] text-[var(--color-text-muted)]"
|
||||||
>
|
>
|
||||||
{v.name}
|
{v.name}
|
||||||
</span>
|
</span>
|
||||||
@@ -173,7 +173,7 @@ function PromptVariableForm({
|
|||||||
<div className="p-3">
|
<div className="p-3">
|
||||||
<div className="mb-3 flex items-center gap-2">
|
<div className="mb-3 flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="flex size-5 items-center justify-center rounded text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
|
className="flex size-5 items-center justify-center rounded text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={onBack}
|
onClick={onBack}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="Back to prompt list"
|
aria-label="Back to prompt list"
|
||||||
@@ -181,11 +181,11 @@ function PromptVariableForm({
|
|||||||
<X className="size-3" />
|
<X className="size-3" />
|
||||||
</button>
|
</button>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="truncate text-[12px] font-medium text-zinc-200">
|
<div className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">
|
||||||
{prompt.name}
|
{prompt.name}
|
||||||
</div>
|
</div>
|
||||||
{prompt.description && (
|
{prompt.description && (
|
||||||
<div className="truncate text-[10px] text-zinc-500">{prompt.description}</div>
|
<div className="truncate text-[10px] text-[var(--color-text-muted)]">{prompt.description}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,10 +203,10 @@ function PromptVariableForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg px-3 py-2 text-[12px] font-medium transition ${
|
className={`mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg px-3 py-2 text-[12px] font-medium transition-all duration-200 ${
|
||||||
submitDisabled
|
submitDisabled
|
||||||
? 'bg-zinc-800 text-zinc-600'
|
? 'bg-[var(--color-surface-2)] text-[var(--color-text-muted)]'
|
||||||
: 'bg-indigo-600 text-white hover:bg-indigo-500'
|
: 'brand-gradient-bg text-white hover:brightness-110'
|
||||||
}`}
|
}`}
|
||||||
disabled={submitDisabled}
|
disabled={submitDisabled}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
@@ -232,11 +232,11 @@ function PromptVariableInput({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-[11px] font-medium text-zinc-400">
|
<label className="mb-1 block text-[11px] font-medium text-[var(--color-text-secondary)]">
|
||||||
{variable.name}
|
{variable.name}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
className="w-full rounded-lg border border-zinc-700 bg-zinc-800 px-2.5 py-1.5 text-[12px] text-zinc-200 placeholder-zinc-600 transition focus:border-indigo-500/50 focus:outline-none"
|
className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-2.5 py-1.5 text-[12px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] transition-all duration-200 focus:border-[var(--color-border-glow)] focus:outline-none"
|
||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' && onSubmit) {
|
if (e.key === 'Enter' && onSubmit) {
|
||||||
|
|||||||
@@ -24,20 +24,20 @@ export function McpAuthBanner({
|
|||||||
const hasFailed = mcpAuth.status === 'failed';
|
const hasFailed = mcpAuth.status === 'failed';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-amber-500/30 bg-amber-500/5 px-4 py-3" role="alert">
|
<div className="rounded-xl border border-[var(--color-glass-border)] border-l-4 border-l-[var(--color-status-warning)] bg-[var(--color-glass)] px-4 py-3" role="alert">
|
||||||
<div className="flex items-start gap-2.5">
|
<div className="flex items-start gap-2.5">
|
||||||
<KeyRound className="mt-0.5 size-4 shrink-0 text-amber-400" />
|
<KeyRound className="mt-0.5 size-4 shrink-0 text-[var(--color-status-warning)]" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[13px] font-semibold text-amber-200">Authentication required</span>
|
<span className="text-[13px] font-semibold text-[var(--color-status-warning)]">Authentication required</span>
|
||||||
<span className="rounded-full bg-amber-500/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-amber-400">
|
<span className="rounded-full bg-[var(--color-status-warning)]/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-warning)]">
|
||||||
MCP
|
MCP
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
aria-label="Dismiss authentication prompt"
|
aria-label="Dismiss authentication prompt"
|
||||||
className="rounded p-0.5 text-zinc-500 transition hover:bg-zinc-700/50 hover:text-zinc-300"
|
className="rounded p-0.5 text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-primary)]"
|
||||||
onClick={handleDismiss}
|
onClick={handleDismiss}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -45,21 +45,21 @@ export function McpAuthBanner({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-2 text-[13px] leading-relaxed text-zinc-200">
|
<p className="mt-2 text-[13px] leading-relaxed text-[var(--color-text-primary)]">
|
||||||
The MCP server{' '}
|
The MCP server{' '}
|
||||||
<span className="font-medium text-amber-200">{mcpAuth.serverName}</span>{' '}
|
<span className="font-medium text-[var(--color-status-warning)]">{mcpAuth.serverName}</span>{' '}
|
||||||
requires OAuth authentication to connect.
|
requires OAuth authentication to connect.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="mt-1 text-[11px] text-zinc-500">{mcpAuth.serverUrl}</p>
|
<p className="mt-1 text-[11px] text-[var(--color-text-muted)]">{mcpAuth.serverUrl}</p>
|
||||||
|
|
||||||
{hasFailed && mcpAuth.errorMessage && (
|
{hasFailed && mcpAuth.errorMessage && (
|
||||||
<p className="mt-2 text-[12px] text-red-400">{mcpAuth.errorMessage}</p>
|
<p className="mt-2 text-[12px] text-[var(--color-status-error)]">{mcpAuth.errorMessage}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-3 flex items-center gap-3">
|
<div className="mt-3 flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
className="inline-flex items-center gap-1.5 rounded-lg bg-amber-500/20 px-3 py-1.5 text-[12px] font-medium text-amber-200 transition hover:bg-amber-500/30 disabled:opacity-50"
|
className="brand-gradient-bg inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[12px] font-medium text-white transition-all duration-200 hover:brightness-110 disabled:opacity-50"
|
||||||
disabled={isAuthenticating}
|
disabled={isAuthenticating}
|
||||||
onClick={handleAuthenticate}
|
onClick={handleAuthenticate}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -75,7 +75,7 @@ export function McpAuthBanner({
|
|||||||
'Authenticate in browser'
|
'Authenticate in browser'
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<span className="text-[11px] text-zinc-500">
|
<span className="text-[11px] text-[var(--color-text-muted)]">
|
||||||
{isAuthenticating
|
{isAuthenticating
|
||||||
? 'Waiting for consent in the browser…'
|
? 'Waiting for consent in the browser…'
|
||||||
: 'Opens your browser for OAuth consent. Token is stored for this session only.'}
|
: 'Opens your browser for OAuth consent. Token is stored for this session only.'}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ function ShellDetail({ detail }: { detail: PermissionDetail }) {
|
|||||||
<div className="mt-2.5 space-y-2">
|
<div className="mt-2.5 space-y-2">
|
||||||
{detail.intention && <IntentionLine text={detail.intention} />}
|
{detail.intention && <IntentionLine text={detail.intention} />}
|
||||||
{detail.warning && (
|
{detail.warning && (
|
||||||
<div className="flex items-start gap-1.5 rounded-md bg-red-500/10 px-2.5 py-1.5 text-[11px] text-red-300">
|
<div className="flex items-start gap-1.5 rounded-md bg-[var(--color-status-error)]/10 px-2.5 py-1.5 text-[11px] text-[var(--color-status-error)]">
|
||||||
<AlertTriangle className="mt-0.5 size-3 shrink-0" />
|
<AlertTriangle className="mt-0.5 size-3 shrink-0" />
|
||||||
<span>{detail.warning}</span>
|
<span>{detail.warning}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,8 +89,8 @@ function WriteDetail({ detail }: { detail: PermissionDetail }) {
|
|||||||
<div className="mt-2.5 space-y-2">
|
<div className="mt-2.5 space-y-2">
|
||||||
{detail.intention && <IntentionLine text={detail.intention} />}
|
{detail.intention && <IntentionLine text={detail.intention} />}
|
||||||
{detail.fileName && (
|
{detail.fileName && (
|
||||||
<div className="flex items-center gap-1.5 text-[11px] text-zinc-300">
|
<div className="flex items-center gap-1.5 text-[11px] text-[var(--color-text-primary)]">
|
||||||
<FileEdit className="size-3 shrink-0 text-zinc-500" />
|
<FileEdit className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
<code className="font-mono">{detail.fileName}</code>
|
<code className="font-mono">{detail.fileName}</code>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -107,8 +107,8 @@ function ReadDetail({ detail }: { detail: PermissionDetail }) {
|
|||||||
<div className="mt-2.5 space-y-2">
|
<div className="mt-2.5 space-y-2">
|
||||||
{detail.intention && <IntentionLine text={detail.intention} />}
|
{detail.intention && <IntentionLine text={detail.intention} />}
|
||||||
{detail.path && (
|
{detail.path && (
|
||||||
<div className="flex items-center gap-1.5 text-[11px] text-zinc-300">
|
<div className="flex items-center gap-1.5 text-[11px] text-[var(--color-text-primary)]">
|
||||||
<FileText className="size-3 shrink-0 text-zinc-500" />
|
<FileText className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
<code className="font-mono">{detail.path}</code>
|
<code className="font-mono">{detail.path}</code>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -121,14 +121,14 @@ function McpDetail({ detail }: { detail: PermissionDetail }) {
|
|||||||
<div className="mt-2.5 space-y-2">
|
<div className="mt-2.5 space-y-2">
|
||||||
<div className="flex flex-wrap items-center gap-2 text-[11px]">
|
<div className="flex flex-wrap items-center gap-2 text-[11px]">
|
||||||
{detail.serverName && (
|
{detail.serverName && (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-indigo-500/15 px-2 py-0.5 text-indigo-300">
|
<span className="inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-muted)] px-2 py-0.5 text-[var(--color-text-accent)]">
|
||||||
<Server className="size-2.5" />
|
<Server className="size-2.5" />
|
||||||
{detail.serverName}
|
{detail.serverName}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{detail.toolTitle && <span className="text-zinc-300">{detail.toolTitle}</span>}
|
{detail.toolTitle && <span className="text-[var(--color-text-primary)]">{detail.toolTitle}</span>}
|
||||||
{detail.readOnly && (
|
{detail.readOnly && (
|
||||||
<span className="rounded-full bg-emerald-500/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-emerald-400">
|
<span className="rounded-full bg-[var(--color-status-success)]/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-success)]">
|
||||||
read-only
|
read-only
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -145,10 +145,10 @@ function UrlDetail({ detail }: { detail: PermissionDetail }) {
|
|||||||
<div className="mt-2.5 space-y-2">
|
<div className="mt-2.5 space-y-2">
|
||||||
{detail.intention && <IntentionLine text={detail.intention} />}
|
{detail.intention && <IntentionLine text={detail.intention} />}
|
||||||
{detail.url && (
|
{detail.url && (
|
||||||
<div className="flex items-center gap-1.5 rounded-md bg-zinc-800/60 px-2.5 py-1.5 text-[11px] text-blue-300">
|
<div className="flex items-center gap-1.5 rounded-md bg-[var(--color-surface-2)]/60 px-2.5 py-1.5 text-[11px] text-[var(--color-accent-sky)]">
|
||||||
<Globe className="size-3 shrink-0" />
|
<Globe className="size-3 shrink-0" />
|
||||||
<code className="min-w-0 flex-1 break-all font-mono">{detail.url}</code>
|
<code className="min-w-0 flex-1 break-all font-mono">{detail.url}</code>
|
||||||
<ExternalLink className="size-3 shrink-0 text-zinc-500" />
|
<ExternalLink className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -160,18 +160,18 @@ function MemoryDetail({ detail }: { detail: PermissionDetail }) {
|
|||||||
<div className="mt-2.5 space-y-1.5">
|
<div className="mt-2.5 space-y-1.5">
|
||||||
{detail.subject && (
|
{detail.subject && (
|
||||||
<div className="flex items-center gap-1.5 text-[11px]">
|
<div className="flex items-center gap-1.5 text-[11px]">
|
||||||
<BookOpen className="size-3 shrink-0 text-zinc-500" />
|
<BookOpen className="size-3 shrink-0 text-[var(--color-text-muted)]" />
|
||||||
<span className="font-medium text-zinc-300">{detail.subject}</span>
|
<span className="font-medium text-[var(--color-text-primary)]">{detail.subject}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{detail.fact && (
|
{detail.fact && (
|
||||||
<p className="rounded-md bg-zinc-800/60 px-2.5 py-1.5 text-[11px] leading-relaxed text-zinc-300">
|
<p className="rounded-md bg-[var(--color-surface-2)]/60 px-2.5 py-1.5 text-[11px] leading-relaxed text-[var(--color-text-primary)]">
|
||||||
{detail.fact}
|
{detail.fact}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{detail.citations && (
|
{detail.citations && (
|
||||||
<p className="text-[10px] text-zinc-500">
|
<p className="text-[10px] text-[var(--color-text-muted)]">
|
||||||
Source: <span className="text-zinc-400">{detail.citations}</span>
|
Source: <span className="text-[var(--color-text-secondary)]">{detail.citations}</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -182,7 +182,7 @@ function CustomToolDetail({ detail }: { detail: PermissionDetail }) {
|
|||||||
return (
|
return (
|
||||||
<div className="mt-2.5 space-y-2">
|
<div className="mt-2.5 space-y-2">
|
||||||
{detail.toolDescription && (
|
{detail.toolDescription && (
|
||||||
<p className="text-[11px] text-zinc-400">{detail.toolDescription}</p>
|
<p className="text-[11px] text-[var(--color-text-secondary)]">{detail.toolDescription}</p>
|
||||||
)}
|
)}
|
||||||
{detail.args && Object.keys(detail.args).length > 0 && (
|
{detail.args && Object.keys(detail.args).length > 0 && (
|
||||||
<CollapsibleCode label="Arguments" text={JSON.stringify(detail.args, null, 2)} />
|
<CollapsibleCode label="Arguments" text={JSON.stringify(detail.args, null, 2)} />
|
||||||
@@ -195,7 +195,7 @@ function HookDetail({ detail }: { detail: PermissionDetail }) {
|
|||||||
return (
|
return (
|
||||||
<div className="mt-2.5 space-y-2">
|
<div className="mt-2.5 space-y-2">
|
||||||
{detail.hookMessage && (
|
{detail.hookMessage && (
|
||||||
<div className="flex items-start gap-1.5 rounded-md bg-amber-500/10 px-2.5 py-1.5 text-[11px] text-amber-200">
|
<div className="flex items-start gap-1.5 rounded-md bg-[var(--color-status-warning)]/10 px-2.5 py-1.5 text-[11px] text-[var(--color-status-warning)]">
|
||||||
<AlertTriangle className="mt-0.5 size-3 shrink-0" />
|
<AlertTriangle className="mt-0.5 size-3 shrink-0" />
|
||||||
<span>{detail.hookMessage}</span>
|
<span>{detail.hookMessage}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -210,12 +210,12 @@ function HookDetail({ detail }: { detail: PermissionDetail }) {
|
|||||||
/* ── Shared primitives ──────────────────────────────────────── */
|
/* ── Shared primitives ──────────────────────────────────────── */
|
||||||
|
|
||||||
function IntentionLine({ text }: { text: string }) {
|
function IntentionLine({ text }: { text: string }) {
|
||||||
return <p className="text-[11px] italic text-zinc-400">{text}</p>;
|
return <p className="text-[11px] italic text-[var(--color-text-secondary)]">{text}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandBlock({ text }: { text: string }) {
|
function CommandBlock({ text }: { text: string }) {
|
||||||
return (
|
return (
|
||||||
<pre className="overflow-x-auto rounded-md bg-zinc-900/80 px-3 py-2 font-mono text-[11px] leading-relaxed text-emerald-300">
|
<pre className="overflow-x-auto rounded-md bg-[var(--color-surface-1)] px-3 py-2 font-mono text-[11px] leading-relaxed text-[var(--color-status-success)]">
|
||||||
{text}
|
{text}
|
||||||
</pre>
|
</pre>
|
||||||
);
|
);
|
||||||
@@ -225,12 +225,12 @@ function DiffBlock({ text }: { text: string }) {
|
|||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
return (
|
return (
|
||||||
<CollapsibleCode label="Diff" text={text} defaultExpanded>
|
<CollapsibleCode label="Diff" text={text} defaultExpanded>
|
||||||
<pre className="max-h-48 overflow-auto rounded-md bg-zinc-900/80 px-3 py-2 font-mono text-[10px] leading-relaxed">
|
<pre className="max-h-48 overflow-auto rounded-md bg-[var(--color-surface-1)] px-3 py-2 font-mono text-[10px] leading-relaxed">
|
||||||
{lines.map((line, i) => {
|
{lines.map((line, i) => {
|
||||||
let color = 'text-zinc-400';
|
let color = 'text-[var(--color-text-secondary)]';
|
||||||
if (line.startsWith('+')) color = 'text-emerald-400';
|
if (line.startsWith('+')) color = 'text-[var(--color-status-success)]';
|
||||||
else if (line.startsWith('-')) color = 'text-red-400';
|
else if (line.startsWith('-')) color = 'text-[var(--color-status-error)]';
|
||||||
else if (line.startsWith('@@')) color = 'text-blue-400';
|
else if (line.startsWith('@@')) color = 'text-[var(--color-accent-sky)]';
|
||||||
return (
|
return (
|
||||||
<div className={color} key={i}>
|
<div className={color} key={i}>
|
||||||
{line}
|
{line}
|
||||||
@@ -256,10 +256,10 @@ function CollapsibleCode({
|
|||||||
const [expanded, setExpanded] = useState(defaultExpanded);
|
const [expanded, setExpanded] = useState(defaultExpanded);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border border-zinc-800/60 bg-zinc-900/40">
|
<div className="rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface-1)]">
|
||||||
<button
|
<button
|
||||||
aria-expanded={expanded}
|
aria-expanded={expanded}
|
||||||
className="flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-[10px] font-medium text-zinc-500 hover:text-zinc-400"
|
className="flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-[10px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:text-[var(--color-text-secondary)]"
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -269,9 +269,9 @@ function CollapsibleCode({
|
|||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="border-t border-zinc-800/40 px-2.5 py-1.5">
|
<div className="border-t border-[var(--color-border-subtle)] px-2.5 py-1.5">
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<pre className="max-h-48 overflow-auto font-mono text-[10px] leading-relaxed text-zinc-300">
|
<pre className="max-h-48 overflow-auto font-mono text-[10px] leading-relaxed text-[var(--color-text-primary)]">
|
||||||
{text}
|
{text}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
@@ -283,9 +283,9 @@ function CollapsibleCode({
|
|||||||
|
|
||||||
function MetaList({ label, items }: { label: string; items: string[] }) {
|
function MetaList({ label, items }: { label: string; items: string[] }) {
|
||||||
return (
|
return (
|
||||||
<div className="text-[10px] text-zinc-500">
|
<div className="text-[10px] text-[var(--color-text-muted)]">
|
||||||
<span className="font-medium">{label}:</span>{' '}
|
<span className="font-medium">{label}:</span>{' '}
|
||||||
<span className="text-zinc-400">{items.join(', ')}</span>
|
<span className="text-[var(--color-text-secondary)]">{items.join(', ')}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,21 +16,21 @@ export function PlanReviewBanner({
|
|||||||
}, [planReview, onDismiss]);
|
}, [planReview, onDismiss]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-emerald-500/30 bg-emerald-500/5 px-4 py-3" role="alert">
|
<div className="rounded-xl border border-[var(--color-glass-border)] border-l-4 border-l-[var(--color-status-success)] bg-[var(--color-glass)] px-4 py-3" role="alert">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start gap-2.5">
|
<div className="flex items-start gap-2.5">
|
||||||
<ClipboardList className="mt-0.5 size-4 shrink-0 text-emerald-400" />
|
<ClipboardList className="mt-0.5 size-4 shrink-0 text-[var(--color-status-success)]" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[13px] font-semibold text-emerald-200">Plan ready for review</span>
|
<span className="text-[13px] font-semibold text-[var(--color-status-success)]">Plan ready for review</span>
|
||||||
<span className="rounded-full bg-emerald-500/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-emerald-400">
|
<span className="rounded-full bg-[var(--color-status-success)]/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-status-success)]">
|
||||||
Plan mode
|
Plan mode
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
aria-label="Dismiss plan"
|
aria-label="Dismiss plan"
|
||||||
className="rounded p-0.5 text-zinc-500 transition hover:bg-zinc-700/50 hover:text-zinc-300"
|
className="rounded p-0.5 text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)]/50 hover:text-[var(--color-text-primary)]"
|
||||||
onClick={handleDismiss}
|
onClick={handleDismiss}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -39,30 +39,30 @@ export function PlanReviewBanner({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{planReview.agentName && (
|
{planReview.agentName && (
|
||||||
<div className="mt-1 text-[11px] text-zinc-400">
|
<div className="mt-1 text-[11px] text-[var(--color-text-secondary)]">
|
||||||
Agent: <span className="text-zinc-300">{planReview.agentName}</span>
|
Agent: <span className="text-[var(--color-text-primary)]">{planReview.agentName}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Summary */}
|
{/* Summary */}
|
||||||
{planReview.summary && (
|
{planReview.summary && (
|
||||||
<p className="mt-2 text-[13px] leading-relaxed text-zinc-200">
|
<p className="mt-2 text-[13px] leading-relaxed text-[var(--color-text-primary)]">
|
||||||
{planReview.summary}
|
{planReview.summary}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Plan content (rendered markdown) */}
|
{/* Plan content (rendered markdown) */}
|
||||||
{planReview.planContent && (
|
{planReview.planContent && (
|
||||||
<div className="mt-3 max-h-80 overflow-y-auto rounded-lg border border-zinc-700/50 bg-zinc-900/60 p-3">
|
<div className="mt-3 max-h-80 overflow-y-auto rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-surface-1)] p-3">
|
||||||
<MarkdownContent content={planReview.planContent} />
|
<MarkdownContent content={planReview.planContent} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Guidance */}
|
{/* Guidance */}
|
||||||
<p className="mt-3 text-[12px] leading-relaxed text-zinc-400">
|
<p className="mt-3 text-[12px] leading-relaxed text-[var(--color-text-secondary)]">
|
||||||
Send a follow-up message to proceed — e.g.{' '}
|
Send a follow-up message to proceed — e.g.{' '}
|
||||||
<span className="text-zinc-300">"implement the plan"</span>,{' '}
|
<span className="text-[var(--color-text-primary)]">"implement the plan"</span>,{' '}
|
||||||
<span className="text-zinc-300">"adjust step 3"</span>, or ask for a different approach.
|
<span className="text-[var(--color-text-primary)]">"adjust step 3"</span>, or ask for a different approach.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ function formatElapsed(startedAt: string): string {
|
|||||||
function StatusIcon({ status }: { status: ActiveSubagent['status'] }) {
|
function StatusIcon({ status }: { status: ActiveSubagent['status'] }) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'running':
|
case 'running':
|
||||||
return <Loader2 className="size-3.5 animate-spin text-sky-400" aria-label="Running" />;
|
return <Loader2 className="size-3.5 animate-spin text-[var(--color-accent-sky)]" aria-label="Running" />;
|
||||||
case 'completed':
|
case 'completed':
|
||||||
return <CheckCircle2 className="size-3.5 text-emerald-400" aria-label="Completed" />;
|
return <CheckCircle2 className="size-3.5 text-[var(--color-status-success)]" aria-label="Completed" />;
|
||||||
case 'failed':
|
case 'failed':
|
||||||
return <XCircle className="size-3.5 text-red-400" aria-label="Failed" />;
|
return <XCircle className="size-3.5 text-[var(--color-status-error)]" aria-label="Failed" />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ function ElapsedTimer({ startedAt }: { startedAt: string }) {
|
|||||||
}, [startedAt]);
|
}, [startedAt]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="ml-auto shrink-0 text-[10px] tabular-nums text-zinc-600">{elapsed}</span>
|
<span className="ml-auto shrink-0 text-[10px] tabular-nums text-[var(--color-text-muted)]">{elapsed}</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,25 +42,25 @@ interface SubagentActivityCardProps {
|
|||||||
function SubagentActivityCard({ subagent }: SubagentActivityCardProps) {
|
function SubagentActivityCard({ subagent }: SubagentActivityCardProps) {
|
||||||
const borderClass =
|
const borderClass =
|
||||||
subagent.status === 'running'
|
subagent.status === 'running'
|
||||||
? 'border-sky-500/20'
|
? 'border-[var(--color-accent-sky)]/20'
|
||||||
: subagent.status === 'failed'
|
: subagent.status === 'failed'
|
||||||
? 'border-red-500/20'
|
? 'border-[var(--color-status-error)]/20'
|
||||||
: 'border-emerald-500/20';
|
: 'border-[var(--color-status-success)]/20';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 rounded-lg border bg-zinc-900/60 px-3 py-1.5 ${borderClass}`}
|
className={`flex items-center gap-2 rounded-lg border bg-[var(--color-glass)] px-3 py-1.5 transition-all duration-200 ${borderClass}`}
|
||||||
role="status"
|
role="status"
|
||||||
aria-label={`Sub-agent ${subagent.name}: ${subagent.activityLabel}`}
|
aria-label={`Sub-agent ${subagent.name}: ${subagent.activityLabel}`}
|
||||||
>
|
>
|
||||||
<StatusIcon status={subagent.status} />
|
<StatusIcon status={subagent.status} />
|
||||||
<Bot className="size-3 text-zinc-500" />
|
<Bot className="size-3 text-[var(--color-text-muted)]" />
|
||||||
<span className="text-[11px] font-medium text-zinc-300">{subagent.name}</span>
|
<span className="text-[11px] font-medium text-[var(--color-text-primary)]">{subagent.name}</span>
|
||||||
<span className="text-[10px] text-zinc-600">—</span>
|
<span className="text-[10px] text-[var(--color-text-muted)]">—</span>
|
||||||
<span className="text-[10px] text-zinc-500">{subagent.activityLabel}</span>
|
<span className="text-[10px] text-[var(--color-text-secondary)]">{subagent.activityLabel}</span>
|
||||||
{subagent.status === 'running' && <ElapsedTimer startedAt={subagent.startedAt} />}
|
{subagent.status === 'running' && <ElapsedTimer startedAt={subagent.startedAt} />}
|
||||||
{subagent.error && (
|
{subagent.error && (
|
||||||
<span className="truncate text-[10px] text-red-400" title={subagent.error}>
|
<span className="truncate text-[10px] text-[var(--color-status-error)]" title={subagent.error}>
|
||||||
{subagent.error}
|
{subagent.error}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export function ThinkingDots() {
|
export function ThinkingDots() {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5" aria-label="Thinking">
|
<div className="flex items-center gap-1.5" aria-label="Thinking">
|
||||||
<span className="thinking-dot size-2 rounded-full bg-zinc-500" />
|
<span className="thinking-dot size-2 rounded-full bg-[var(--color-accent)]" />
|
||||||
<span className="thinking-dot size-2 rounded-full bg-zinc-500" />
|
<span className="thinking-dot size-2 rounded-full bg-[var(--color-accent-sky)]" />
|
||||||
<span className="thinking-dot size-2 rounded-full bg-zinc-500" />
|
<span className="thinking-dot size-2 rounded-full bg-[var(--color-accent-purple)]" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,25 +42,25 @@ export function UserInputBanner({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-blue-500/30 bg-blue-500/5 px-4 py-3" role="alert">
|
<div className="rounded-xl border border-[var(--color-glass-border)] border-l-4 border-l-[var(--color-accent-sky)] bg-[var(--color-glass)] px-4 py-3" role="alert">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start gap-2.5">
|
<div className="flex items-start gap-2.5">
|
||||||
<MessageCircleQuestion className="mt-0.5 size-4 shrink-0 text-blue-400" />
|
<MessageCircleQuestion className="mt-0.5 size-4 shrink-0 text-[var(--color-accent-sky)]" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[13px] font-semibold text-blue-200">Agent question</span>
|
<span className="text-[13px] font-semibold text-[var(--color-accent-sky)]">Agent question</span>
|
||||||
<span className="rounded-full bg-blue-500/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-blue-400">
|
<span className="rounded-full bg-[var(--color-accent-sky)]/15 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-accent-sky)]">
|
||||||
User input
|
User input
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{userInput.agentName && (
|
{userInput.agentName && (
|
||||||
<div className="mt-1 text-[11px] text-zinc-400">
|
<div className="mt-1 text-[11px] text-[var(--color-text-secondary)]">
|
||||||
Agent: <span className="text-zinc-300">{userInput.agentName}</span>
|
Agent: <span className="text-[var(--color-text-primary)]">{userInput.agentName}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="mt-2 text-[13px] leading-relaxed text-zinc-200 whitespace-pre-wrap">
|
<p className="mt-2 text-[13px] leading-relaxed text-[var(--color-text-primary)] whitespace-pre-wrap">
|
||||||
{userInput.question}
|
{userInput.question}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,7 +71,7 @@ export function UserInputBanner({
|
|||||||
<div className="mt-3 flex flex-wrap gap-2">
|
<div className="mt-3 flex flex-wrap gap-2">
|
||||||
{userInput.choices!.map((choice) => (
|
{userInput.choices!.map((choice) => (
|
||||||
<button
|
<button
|
||||||
className="rounded-lg border border-blue-500/30 bg-blue-500/10 px-3.5 py-1.5 text-[12px] font-medium text-blue-200 transition hover:border-blue-400/50 hover:bg-blue-500/20 hover:text-white disabled:cursor-not-allowed disabled:opacity-50"
|
className="rounded-lg border border-[var(--color-accent-sky)]/30 bg-[var(--color-accent-sky)]/10 px-3.5 py-1.5 text-[12px] font-medium text-[var(--color-accent-sky)] transition-all duration-200 hover:border-[var(--color-accent-sky)]/50 hover:bg-[var(--color-accent-sky)]/20 hover:text-[var(--color-text-primary)] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
key={choice}
|
key={choice}
|
||||||
onClick={() => handleChoiceClick(choice)}
|
onClick={() => handleChoiceClick(choice)}
|
||||||
@@ -88,7 +88,7 @@ export function UserInputBanner({
|
|||||||
<div className="mt-3 flex items-center gap-2">
|
<div className="mt-3 flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
aria-label="Type your answer"
|
aria-label="Type your answer"
|
||||||
className="min-w-0 flex-1 rounded-lg border border-zinc-700 bg-zinc-900/60 px-3 py-1.5 text-[13px] text-zinc-200 placeholder-zinc-500 outline-none transition focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/30 disabled:cursor-not-allowed disabled:opacity-50"
|
className="min-w-0 flex-1 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-1.5 text-[13px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none transition-all duration-200 focus:border-[var(--color-border-glow)] focus:ring-1 focus:ring-[var(--color-border-glow)] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onChange={(e) => setFreeformText(e.target.value)}
|
onChange={(e) => setFreeformText(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
@@ -98,7 +98,7 @@ export function UserInputBanner({
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
aria-label="Submit answer"
|
aria-label="Submit answer"
|
||||||
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-3.5 py-1.5 text-[12px] font-medium text-white transition hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-50"
|
className="brand-gradient-bg inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-[12px] font-medium text-white transition-all duration-200 hover:brightness-110 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={isSubmitting || !freeformText.trim()}
|
disabled={isSubmitting || !freeformText.trim()}
|
||||||
onClick={handleFreeformSubmit}
|
onClick={handleFreeformSubmit}
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ const kindIcons: Record<PatternGraphNodeKind, typeof CircleUser> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const kindColors: Record<PatternGraphNodeKind, { bg: string; border: string; text: string }> = {
|
const kindColors: Record<PatternGraphNodeKind, { bg: string; border: string; text: string }> = {
|
||||||
'user-input': { bg: 'bg-indigo-500/10', border: 'border-indigo-500/30', text: 'text-indigo-400' },
|
'user-input': { bg: 'bg-[var(--color-accent)]/10', border: 'border-[var(--color-accent)]/30', text: 'text-[var(--color-accent-sky)]' },
|
||||||
'user-output': { bg: 'bg-indigo-500/10', border: 'border-indigo-500/30', text: 'text-indigo-400' },
|
'user-output': { bg: 'bg-[var(--color-accent)]/10', border: 'border-[var(--color-accent)]/30', text: 'text-[var(--color-accent-sky)]' },
|
||||||
agent: { bg: 'bg-zinc-800/80', border: 'border-zinc-600/40', text: 'text-zinc-200' },
|
agent: { bg: 'bg-[var(--color-surface-2)]/80', border: 'border-[var(--color-border)]/40', text: 'text-[var(--color-text-primary)]' },
|
||||||
distributor: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400' },
|
distributor: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400' },
|
||||||
collector: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400' },
|
collector: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400' },
|
||||||
orchestrator: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/30', text: 'text-emerald-400' },
|
orchestrator: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/30', text: 'text-emerald-400' },
|
||||||
@@ -38,9 +38,9 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex min-w-[120px] items-center gap-2 rounded-xl border px-3 py-2 shadow-md transition ${
|
className={`flex min-w-[120px] items-center gap-2 rounded-xl border px-3 py-2 shadow-md backdrop-blur-sm transition ${
|
||||||
colors.bg
|
colors.bg
|
||||||
} ${selected ? 'ring-2 ring-indigo-500/50' : ''} ${colors.border}`}
|
} ${selected ? 'ring-2 ring-[var(--color-accent)]/50' : ''} ${colors.border}`}
|
||||||
>
|
>
|
||||||
{renderIcon()}
|
{renderIcon()}
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
@@ -48,11 +48,11 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
|
|||||||
{data.label}
|
{data.label}
|
||||||
</div>
|
</div>
|
||||||
{isAgent && data.modelLabel && (
|
{isAgent && data.modelLabel && (
|
||||||
<div className="truncate text-[10px] text-zinc-500">{data.modelLabel}</div>
|
<div className="truncate text-[10px] text-[var(--color-text-muted)]">{data.modelLabel}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{data.readOnly && (
|
{data.readOnly && (
|
||||||
<span className="ml-1 rounded bg-zinc-700/50 px-1 py-0.5 text-[8px] font-medium text-zinc-500">
|
<span className="ml-1 rounded bg-[var(--color-surface-3)]/50 px-1 py-0.5 text-[8px] font-medium text-[var(--color-text-muted)]">
|
||||||
SYS
|
SYS
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -61,8 +61,8 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleStyles = {
|
const handleStyles = {
|
||||||
system: '!size-2 !border-zinc-600 !bg-zinc-400',
|
system: '!size-2 !border-[var(--color-border)] !bg-[var(--color-text-secondary)]',
|
||||||
agent: '!size-2 !border-indigo-400 !bg-indigo-500',
|
agent: '!size-2 !border-[var(--color-accent-sky)] !bg-[var(--color-accent)]',
|
||||||
hidden: '!size-0 !border-0 !bg-transparent !min-w-0 !min-h-0',
|
hidden: '!size-0 !border-0 !bg-transparent !min-w-0 !min-h-0',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ function PatternGraphCanvasInner({
|
|||||||
}, [graph, onGraphChange, fitView]);
|
}, [graph, onGraphChange, fitView]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full rounded-xl border border-zinc-800 bg-zinc-950/50">
|
<div className="h-full w-full rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-0)]/50">
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes.map((n) => ({
|
nodes={nodes.map((n) => ({
|
||||||
...n,
|
...n,
|
||||||
@@ -189,18 +189,18 @@ function PatternGraphCanvasInner({
|
|||||||
proOptions={{ hideAttribution: true }}
|
proOptions={{ hideAttribution: true }}
|
||||||
defaultEdgeOptions={{
|
defaultEdgeOptions={{
|
||||||
type: 'default',
|
type: 'default',
|
||||||
style: { stroke: '#6366f1', strokeWidth: 1.5 },
|
style: { stroke: '#245CF9', strokeWidth: 1.5 },
|
||||||
markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16, color: '#6366f1' },
|
markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16, color: '#245CF9' },
|
||||||
}}
|
}}
|
||||||
connectionLineStyle={{ stroke: '#6366f1', strokeWidth: 1.5 }}
|
connectionLineStyle={{ stroke: '#245CF9', strokeWidth: 1.5 }}
|
||||||
deleteKeyCode="Delete"
|
deleteKeyCode="Delete"
|
||||||
>
|
>
|
||||||
<Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#27272a" />
|
<Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#1a1e2e" />
|
||||||
<Panel position="top-right">
|
<Panel position="top-right">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleAutoLayout}
|
onClick={handleAutoLayout}
|
||||||
className="flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800/90 px-2.5 py-1.5 text-[11px] font-medium text-zinc-300 shadow-sm backdrop-blur transition hover:border-zinc-600 hover:bg-zinc-700/90 hover:text-zinc-100"
|
className="flex items-center gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)]/90 px-2.5 py-1.5 text-[11px] font-medium text-[var(--color-text-secondary)] shadow-sm backdrop-blur transition hover:border-[var(--color-border-glow)] hover:bg-[var(--color-surface-3)]/90 hover:text-[var(--color-text-primary)]"
|
||||||
title="Auto-layout nodes"
|
title="Auto-layout nodes"
|
||||||
>
|
>
|
||||||
<LayoutGrid className="size-3.5" />
|
<LayoutGrid className="size-3.5" />
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ function InputField({
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}) {
|
}) {
|
||||||
const base =
|
const base =
|
||||||
'w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 placeholder-zinc-600 outline-none transition focus:border-indigo-500/50';
|
'w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] outline-none transition focus:border-[var(--color-accent)]/50';
|
||||||
return (
|
return (
|
||||||
<label className="block space-y-1.5">
|
<label className="block space-y-1.5">
|
||||||
<span className="text-[12px] font-medium text-zinc-400">{label}</span>
|
<span className="text-[12px] font-medium text-[var(--color-text-secondary)]">{label}</span>
|
||||||
{multiline ? (
|
{multiline ? (
|
||||||
<textarea
|
<textarea
|
||||||
className={`${base} min-h-20 resize-y`}
|
className={`${base} min-h-20 resize-y`}
|
||||||
@@ -97,17 +97,17 @@ function SystemNodeInspector({ kind }: { kind: PatternGraphNodeKind }) {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<div className="flex size-8 items-center justify-center rounded-lg bg-indigo-500/10">
|
<div className="flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent)]/10">
|
||||||
<Icon className="size-4 text-indigo-400" />
|
<Icon className="size-4 text-[var(--color-accent-sky)]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[13px] font-semibold text-zinc-200">{kindLabels[kind]}</div>
|
<div className="text-[13px] font-semibold text-[var(--color-text-primary)]">{kindLabels[kind]}</div>
|
||||||
<span className="rounded bg-zinc-700/50 px-1.5 py-0.5 text-[9px] font-medium text-zinc-500">
|
<span className="rounded bg-[var(--color-surface-3)]/50 px-1.5 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)]">
|
||||||
System node
|
System node
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[12px] leading-relaxed text-zinc-500">{kindDescriptions[kind]}</p>
|
<p className="text-[12px] leading-relaxed text-[var(--color-text-muted)]">{kindDescriptions[kind]}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -140,16 +140,16 @@ function AgentNodeInspector({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<div className="flex size-8 items-center justify-center rounded-lg bg-zinc-800">
|
<div className="flex size-8 items-center justify-center rounded-lg bg-[var(--color-surface-2)]">
|
||||||
<Bot className="size-4 text-zinc-300" />
|
<Bot className="size-4 text-[var(--color-text-secondary)]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[13px] font-semibold text-zinc-200">{agent.name || 'Unnamed'}</div>
|
<div className="text-[13px] font-semibold text-[var(--color-text-primary)]">{agent.name || 'Unnamed'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{showReorder && (
|
{showReorder && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="flex size-6 items-center justify-center rounded text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300 disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-zinc-500"
|
className="flex size-6 items-center justify-center rounded text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)] disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-[var(--color-text-muted)]"
|
||||||
disabled={!canUp}
|
disabled={!canUp}
|
||||||
onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'up'))}
|
onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'up'))}
|
||||||
title="Move earlier in sequence"
|
title="Move earlier in sequence"
|
||||||
@@ -158,7 +158,7 @@ function AgentNodeInspector({
|
|||||||
<ChevronUp className="size-3.5" />
|
<ChevronUp className="size-3.5" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="flex size-6 items-center justify-center rounded text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300 disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-zinc-500"
|
className="flex size-6 items-center justify-center rounded text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)] disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-[var(--color-text-muted)]"
|
||||||
disabled={!canDown}
|
disabled={!canDown}
|
||||||
onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'down'))}
|
onClick={() => onGraphChange(swapSequentialOrder(graph, nodeId, 'down'))}
|
||||||
title="Move later in sequence"
|
title="Move later in sequence"
|
||||||
@@ -169,7 +169,7 @@ function AgentNodeInspector({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1 text-[12px] text-zinc-600 transition hover:text-red-400"
|
className="flex items-center gap-1 text-[12px] text-[var(--color-text-muted)] transition hover:text-red-400"
|
||||||
onClick={() => onAgentRemove(agent.id)}
|
onClick={() => onAgentRemove(agent.id)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -235,7 +235,7 @@ export function PatternGraphInspector({
|
|||||||
if (!selectedNodeId) {
|
if (!selectedNodeId) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center p-4">
|
<div className="flex h-full items-center justify-center p-4">
|
||||||
<p className="text-center text-[12px] text-zinc-600">
|
<p className="text-center text-[12px] text-[var(--color-text-muted)]">
|
||||||
Select a node on the graph to inspect it
|
Select a node on the graph to inspect it
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function LspProfileEditor({
|
|||||||
title={profile.name || 'Untitled LSP Profile'}
|
title={profile.name || 'Untitled LSP Profile'}
|
||||||
>
|
>
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
General
|
General
|
||||||
</h4>
|
</h4>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
@@ -50,7 +50,7 @@ export function LspProfileEditor({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Server
|
Server
|
||||||
</h4>
|
</h4>
|
||||||
<FormField label="Command" required>
|
<FormField label="Command" required>
|
||||||
@@ -71,7 +71,7 @@ export function LspProfileEditor({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
File matching
|
File matching
|
||||||
</h4>
|
</h4>
|
||||||
<FormField label="File extensions" required>
|
<FormField label="File extensions" required>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function McpServerEditor({
|
|||||||
title={server.name || 'Untitled MCP Server'}
|
title={server.name || 'Untitled MCP Server'}
|
||||||
>
|
>
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
General
|
General
|
||||||
</h4>
|
</h4>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
@@ -54,7 +54,7 @@ export function McpServerEditor({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
{server.transport === 'local' ? 'Process' : 'Endpoint'}
|
{server.transport === 'local' ? 'Process' : 'Endpoint'}
|
||||||
</h4>
|
</h4>
|
||||||
{server.transport === 'local' ? (
|
{server.transport === 'local' ? (
|
||||||
@@ -94,7 +94,7 @@ export function McpServerEditor({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-zinc-500">
|
<h4 className="text-[12px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
Advanced
|
Advanced
|
||||||
</h4>
|
</h4>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
|||||||
@@ -25,21 +25,21 @@ export function ToolingEditorShell({
|
|||||||
<div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] px-5 pb-3 pt-3">
|
<div className="drag-region flex items-center justify-between border-b border-[var(--color-border)] px-5 pb-3 pt-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
className="no-drag flex size-8 items-center justify-center rounded-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-200"
|
className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||||
onClick={onBack}
|
onClick={onBack}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="size-4" />
|
<ChevronLeft className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-[13px] font-semibold text-zinc-100">{title}</h2>
|
<h2 className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">{title}</h2>
|
||||||
<p className="text-[12px] text-zinc-500">{subtitle}</p>
|
<p className="text-[12px] text-[var(--color-text-muted)]">{subtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="no-drag flex items-center gap-2">
|
<div className="no-drag flex items-center gap-2">
|
||||||
{onDelete && (
|
{onDelete && (
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] text-red-400 transition hover:bg-red-500/10"
|
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[13px] text-[var(--color-status-error)] transition-all duration-200 hover:bg-[var(--color-status-error)]/10"
|
||||||
onClick={() => void onDelete()}
|
onClick={() => void onDelete()}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -48,7 +48,7 @@ export function ToolingEditorShell({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="rounded-lg bg-indigo-600 px-4 py-1.5 text-[13px] font-medium text-white transition hover:bg-indigo-500 disabled:cursor-not-allowed disabled:opacity-40"
|
className="rounded-lg bg-[var(--color-accent)] px-4 py-1.5 text-[13px] font-medium text-white transition-all duration-200 hover:bg-[var(--color-accent-sky)] disabled:cursor-not-allowed disabled:opacity-40"
|
||||||
disabled={disableSave}
|
disabled={disableSave}
|
||||||
onClick={() => void onSave()}
|
onClick={() => void onSave()}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -61,7 +61,7 @@ export function ToolingEditorShell({
|
|||||||
<div className="flex-1 overflow-y-auto px-6 py-5">
|
<div className="flex-1 overflow-y-auto px-6 py-5">
|
||||||
<div className="mx-auto max-w-2xl space-y-8">
|
<div className="mx-auto max-w-2xl space-y-8">
|
||||||
{error && (
|
{error && (
|
||||||
<div className="flex items-start gap-2 rounded-lg bg-amber-500/10 px-3 py-2 text-[13px] text-amber-300">
|
<div className="flex items-start gap-2 rounded-lg bg-[var(--color-status-warning)]/10 px-3 py-2 text-[13px] text-[var(--color-status-warning)]">
|
||||||
<AlertCircle className="mt-0.5 size-3.5 shrink-0" />
|
<AlertCircle className="mt-0.5 size-3.5 shrink-0" />
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ export function FormField({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="mb-1.5 block text-[12px] font-medium text-zinc-400">
|
<span className="mb-1.5 block text-[12px] font-medium text-[var(--color-text-secondary)]">
|
||||||
{label}
|
{label}
|
||||||
{required && <span className="ml-1 text-amber-400">*</span>}
|
{required && <span className="ml-1 text-[var(--color-status-warning)]">*</span>}
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import type { ReactNode } from 'react';
|
|||||||
|
|
||||||
export function InfoCallout({ children }: { children: ReactNode }) {
|
export function InfoCallout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start gap-2.5 rounded-lg border border-zinc-800 bg-zinc-900/30 px-3 py-2.5 text-[12px] leading-relaxed text-zinc-500">
|
<div className="flex items-start gap-2.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-glass)] px-3 py-2.5 text-[12px] leading-relaxed text-[var(--color-text-secondary)] backdrop-blur-sm">
|
||||||
<Info className="mt-0.5 size-3.5 shrink-0 text-zinc-600" />
|
<Info className="mt-0.5 size-3.5 shrink-0 text-[var(--color-accent)]" />
|
||||||
<span>{children}</span>
|
<span>{children}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ export interface PopoverToggleRowProps {
|
|||||||
export function PopoverToggleRow({ label, detail, enabled, onToggle }: PopoverToggleRowProps) {
|
export function PopoverToggleRow({ label, detail, enabled, onToggle }: PopoverToggleRowProps) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-left transition hover:bg-zinc-800"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-left transition-all duration-150 hover:bg-[var(--color-surface-2)]"
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="truncate text-[12px] font-medium text-zinc-300">{label}</div>
|
<div className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">{label}</div>
|
||||||
{detail && <div className="truncate text-[10px] text-zinc-600">{detail}</div>}
|
{detail && <div className="truncate text-[10px] text-[var(--color-text-muted)]">{detail}</div>}
|
||||||
</div>
|
</div>
|
||||||
<ToggleSwitch enabled={enabled} size="sm" />
|
<ToggleSwitch enabled={enabled} size="sm" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function SelectInput({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 outline-none transition focus:border-indigo-500/50"
|
className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] outline-none transition-all duration-200 focus:border-[var(--color-border-glow)] focus:shadow-[0_0_0_1px_rgba(36,92,249,0.15),0_0_12px_rgba(36,92,249,0.08)]"
|
||||||
onChange={(event) => onChange(event.target.value)}
|
onChange={(event) => onChange(event.target.value)}
|
||||||
value={value}
|
value={value}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function TextInput({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 outline-none transition placeholder:text-zinc-600 focus:border-indigo-500/50"
|
className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] outline-none transition-all duration-200 placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-border-glow)] focus:shadow-[0_0_0_1px_rgba(36,92,249,0.15),0_0_12px_rgba(36,92,249,0.08)]"
|
||||||
inputMode={inputMode}
|
inputMode={inputMode}
|
||||||
onChange={(event) => onChange(event.target.value)}
|
onChange={(event) => onChange(event.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function TextareaInput({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-[13px] text-zinc-100 outline-none transition placeholder:text-zinc-600 focus:border-indigo-500/50"
|
className="w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-2)] px-3 py-2 text-[13px] text-[var(--color-text-primary)] outline-none transition-all duration-200 placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-border-glow)] focus:shadow-[0_0_0_1px_rgba(36,92,249,0.15),0_0_12px_rgba(36,92,249,0.08)]"
|
||||||
onChange={(event) => onChange(event.target.value)}
|
onChange={(event) => onChange(event.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ export function ToggleSwitch({ enabled, size = 'md' }: ToggleSwitchProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`relative inline-flex ${trackSize} shrink-0 items-center rounded-full transition-colors ${
|
className={`relative inline-flex ${trackSize} shrink-0 items-center rounded-full transition-all duration-200 ${
|
||||||
enabled ? 'bg-indigo-500' : 'bg-zinc-700'
|
enabled ? 'brand-gradient-bg shadow-[0_0_8px_rgba(36,92,249,0.3)]' : 'bg-[var(--color-surface-3)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
/>
|
/>
|
||||||
<title>aryx</title>
|
<title>aryx</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-slate-950 text-slate-100">
|
<body class="bg-[var(--color-surface-0)] text-[var(--color-text-primary)]">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script
|
<script
|
||||||
type="module"
|
type="module"
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
|
import '@fontsource-variable/outfit';
|
||||||
|
import '@fontsource-variable/dm-sans';
|
||||||
|
import '@fontsource-variable/jetbrains-mono';
|
||||||
|
|
||||||
import App from '@renderer/App';
|
import App from '@renderer/App';
|
||||||
import '@renderer/styles.css';
|
import '@renderer/styles.css';
|
||||||
|
|
||||||
|
|||||||
+279
-129
@@ -1,79 +1,133 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/* ── Design tokens: "Luminous Depth" ────────────────────────── */
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-surface-0: #09090b;
|
/* Surface palette — blue-tinted darks */
|
||||||
--color-surface-1: #18181b;
|
--color-surface-0: #07080e;
|
||||||
--color-surface-2: #27272a;
|
--color-surface-1: #0e1019;
|
||||||
--color-surface-3: #3f3f46;
|
--color-surface-2: #161924;
|
||||||
--color-border: #27272a;
|
--color-surface-3: #1e2233;
|
||||||
--color-border-subtle: #1e1e22;
|
--color-surface-4: #272b3d;
|
||||||
--color-accent: #6366f1;
|
--color-border: #1a1e2e;
|
||||||
--color-accent-hover: #818cf8;
|
--color-border-subtle: #121520;
|
||||||
--color-accent-muted: rgba(99, 102, 241, 0.15);
|
|
||||||
|
/* Brand accents — derived from logo */
|
||||||
|
--color-accent: #245CF9;
|
||||||
|
--color-accent-hover: #4b7bfa;
|
||||||
|
--color-accent-muted: rgba(36, 92, 249, 0.12);
|
||||||
|
--color-accent-sky: #248CFD;
|
||||||
|
--color-accent-indigo: #0E13A4;
|
||||||
|
--color-accent-purple: #8A29E6;
|
||||||
|
--color-accent-royal: #5A20E0;
|
||||||
|
--color-accent-violet: #3615CF;
|
||||||
|
|
||||||
|
/* Text palette */
|
||||||
|
--color-text-primary: #e8eaf0;
|
||||||
|
--color-text-secondary: #8b90a5;
|
||||||
|
--color-text-muted: #4e5368;
|
||||||
|
--color-text-accent: #7ba1fc;
|
||||||
|
|
||||||
|
/* Status palette */
|
||||||
|
--color-status-success: #34d399;
|
||||||
|
--color-status-warning: #fbbf24;
|
||||||
|
--color-status-error: #f87171;
|
||||||
|
--color-status-info: #248CFD;
|
||||||
|
|
||||||
|
/* Glow / glass effects */
|
||||||
|
--color-border-glow: rgba(36, 92, 249, 0.25);
|
||||||
|
--color-glass: rgba(14, 16, 25, 0.7);
|
||||||
|
--color-glass-hover: rgba(22, 25, 36, 0.8);
|
||||||
|
--color-glass-border: rgba(26, 30, 46, 0.6);
|
||||||
|
|
||||||
|
/* Pill text */
|
||||||
--text-pill: 0.6875rem;
|
--text-pill: 0.6875rem;
|
||||||
--text-pill--line-height: 1.2;
|
--text-pill--line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
font-family:
|
font-family: "DM Sans Variable", "DM Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||||
Inter,
|
|
||||||
ui-sans-serif,
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
"Segoe UI",
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
||||||
|
--font-display: "Outfit Variable", "Outfit", ui-sans-serif, system-ui, sans-serif;
|
||||||
|
--font-body: "DM Sans Variable", "DM Sans", ui-sans-serif, system-ui, sans-serif;
|
||||||
|
--font-mono: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
|
||||||
|
|
||||||
|
/* Gradient presets */
|
||||||
|
--gradient-brand: linear-gradient(135deg, #245CF9, #8A29E6);
|
||||||
|
--gradient-brand-hover: linear-gradient(135deg, #4b7bfa, #a04ff0);
|
||||||
|
--gradient-surface: linear-gradient(180deg, #0e1019, #07080e);
|
||||||
|
--gradient-glow: radial-gradient(ellipse at center, rgba(36, 92, 249, 0.06), transparent 70%);
|
||||||
|
--gradient-border: linear-gradient(135deg, #245CF9, #3615CF);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Light mode colour overrides ────────────────────────────── */
|
/* ── Light mode overrides ────────────────────────────────────── */
|
||||||
|
|
||||||
[data-theme="light"] {
|
[data-theme="light"] {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
/* Remap semantic surface tokens */
|
/* Surfaces — cool-tinted whites */
|
||||||
--color-surface-0: #ffffff;
|
--color-surface-0: #f8f9fc;
|
||||||
--color-surface-1: #f4f4f5;
|
--color-surface-1: #f0f1f6;
|
||||||
--color-surface-2: #e4e4e7;
|
--color-surface-2: #e6e8f0;
|
||||||
--color-surface-3: #d4d4d8;
|
--color-surface-3: #d8dbe6;
|
||||||
--color-border: #e4e4e7;
|
--color-surface-4: #c8cdd9;
|
||||||
--color-border-subtle: #f0f0f2;
|
--color-border: #dfe1ea;
|
||||||
--color-accent-muted: rgba(99, 102, 241, 0.10);
|
--color-border-subtle: #eeeff4;
|
||||||
|
|
||||||
/* Swap zinc scale: dark values become light equivalents.
|
/* Text — deep blue-blacks */
|
||||||
Tailwind v4 exposes these as --color-zinc-{step}. */
|
--color-text-primary: #0f1729;
|
||||||
--color-zinc-50: #18181b;
|
--color-text-secondary: #5a6078;
|
||||||
--color-zinc-100: #27272a;
|
--color-text-muted: #9298ad;
|
||||||
--color-zinc-200: #3f3f46;
|
--color-text-accent: #245CF9;
|
||||||
--color-zinc-300: #52525b;
|
|
||||||
--color-zinc-400: #71717a;
|
/* Glass — light translucency */
|
||||||
--color-zinc-500: #71717a;
|
--color-glass: rgba(240, 241, 246, 0.75);
|
||||||
--color-zinc-600: #a1a1aa;
|
--color-glass-hover: rgba(230, 232, 240, 0.8);
|
||||||
--color-zinc-700: #d4d4d8;
|
--color-glass-border: rgba(223, 225, 234, 0.6);
|
||||||
--color-zinc-800: #e4e4e7;
|
--color-border-glow: rgba(36, 92, 249, 0.2);
|
||||||
--color-zinc-900: #f4f4f5;
|
--color-accent-muted: rgba(36, 92, 249, 0.08);
|
||||||
--color-zinc-950: #ffffff;
|
|
||||||
|
/* Gradients — lighter versions */
|
||||||
|
--gradient-glow: radial-gradient(ellipse at center, rgba(36, 92, 249, 0.04), transparent 70%);
|
||||||
|
--gradient-surface: linear-gradient(180deg, #f0f1f6, #f8f9fc);
|
||||||
|
|
||||||
|
/* Remap Tailwind zinc scale for light mode */
|
||||||
|
--color-zinc-50: #0f1729;
|
||||||
|
--color-zinc-100: #1e2233;
|
||||||
|
--color-zinc-200: #3a3f54;
|
||||||
|
--color-zinc-300: #5a6078;
|
||||||
|
--color-zinc-400: #7a7f94;
|
||||||
|
--color-zinc-500: #8b90a5;
|
||||||
|
--color-zinc-600: #9298ad;
|
||||||
|
--color-zinc-700: #c8cdd9;
|
||||||
|
--color-zinc-800: #dfe1ea;
|
||||||
|
--color-zinc-900: #f0f1f6;
|
||||||
|
--color-zinc-950: #f8f9fc;
|
||||||
|
|
||||||
/* Keep black/white correct for light mode */
|
|
||||||
--color-black: #000000;
|
--color-black: #000000;
|
||||||
--color-white: #ffffff;
|
--color-white: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Global base styles ──────────────────────────────────────── */
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--color-surface-0);
|
background: var(--color-surface-0);
|
||||||
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar styling */
|
/* ── Scrollbar ───────────────────────────────────────────────── */
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
@@ -86,14 +140,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--scrollbar-hover, #52525b);
|
background: var(--color-surface-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="light"] {
|
/* ── Window drag regions ─────────────────────────────────────── */
|
||||||
--scrollbar-hover: #a1a1aa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Window drag regions for custom title bar */
|
|
||||||
.drag-region {
|
.drag-region {
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
@@ -102,22 +153,58 @@ body {
|
|||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Thinking dots animation */
|
/* ── Utility classes ─────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.glass-surface {
|
||||||
|
background: var(--color-glass);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border: 1px solid var(--color-glass-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-border {
|
||||||
|
border: 1px solid var(--color-border-glow);
|
||||||
|
box-shadow: 0 0 12px rgba(36, 92, 249, 0.08), inset 0 0 12px rgba(36, 92, 249, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-gradient-text {
|
||||||
|
background: var(--gradient-brand);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-gradient-bg {
|
||||||
|
background: var(--gradient-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-gradient-bg:hover {
|
||||||
|
background: var(--gradient-brand-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-display {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-mono {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Thinking dots animation ─────────────────────────────────── */
|
||||||
|
|
||||||
@keyframes thinking-dot {
|
@keyframes thinking-dot {
|
||||||
0%,
|
0%, 80%, 100% {
|
||||||
60%,
|
opacity: 0.25;
|
||||||
100% {
|
|
||||||
opacity: 0.3;
|
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
}
|
}
|
||||||
30% {
|
40% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.thinking-dot {
|
.thinking-dot {
|
||||||
animation: thinking-dot 1.4s ease-in-out infinite;
|
animation: thinking-dot 1.6s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thinking-dot:nth-child(2) {
|
.thinking-dot:nth-child(2) {
|
||||||
@@ -128,14 +215,38 @@ body {
|
|||||||
animation-delay: 0.4s;
|
animation-delay: 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Auto-resize textarea helper */
|
/* Flowing gradient wave for thinking state */
|
||||||
|
@keyframes thinking-wave {
|
||||||
|
0% {
|
||||||
|
background-position: 200% center;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -200% center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-wave {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(36, 92, 249, 0.3) 25%,
|
||||||
|
rgba(138, 41, 230, 0.3) 50%,
|
||||||
|
rgba(36, 140, 253, 0.3) 75%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: thinking-wave 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Auto-resize textarea ────────────────────────────────────── */
|
||||||
|
|
||||||
.auto-resize-textarea {
|
.auto-resize-textarea {
|
||||||
field-sizing: content;
|
field-sizing: content;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Markdown composer (Lexical) ────────────────────────── */
|
/* ── Markdown composer (Lexical) ─────────────────────────────── */
|
||||||
|
|
||||||
.markdown-composer-content {
|
.markdown-composer-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -146,9 +257,9 @@ body {
|
|||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 10px 16px 4px 16px;
|
padding: 10px 16px 4px 16px;
|
||||||
font-size: 14px;
|
font-size: 13.5px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: #f4f4f5;
|
color: var(--color-text-primary);
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
@@ -162,9 +273,9 @@ body {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: 16px;
|
left: 16px;
|
||||||
font-size: 14px;
|
font-size: 13.5px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: #52525b;
|
color: var(--color-text-muted);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
@@ -176,6 +287,7 @@ body {
|
|||||||
|
|
||||||
/* Headings */
|
/* Headings */
|
||||||
.mc-h1, .mc-h2, .mc-h3 {
|
.mc-h1, .mc-h2, .mc-h3 {
|
||||||
|
font-family: var(--font-display);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
@@ -190,11 +302,11 @@ body {
|
|||||||
.mc-strikethrough { text-decoration: line-through; }
|
.mc-strikethrough { text-decoration: line-through; }
|
||||||
.mc-underline { text-decoration: underline; }
|
.mc-underline { text-decoration: underline; }
|
||||||
.mc-inline-code {
|
.mc-inline-code {
|
||||||
background: rgba(63, 63, 70, 0.6);
|
background: rgba(30, 34, 51, 0.7);
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
padding: 1px 4px;
|
padding: 1px 5px;
|
||||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
font-family: var(--font-mono);
|
||||||
font-size: 0.9em;
|
font-size: 0.88em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Lists */
|
/* Lists */
|
||||||
@@ -214,28 +326,28 @@ body {
|
|||||||
|
|
||||||
/* Blockquote */
|
/* Blockquote */
|
||||||
.mc-blockquote {
|
.mc-blockquote {
|
||||||
border-left: 3px solid #3f3f46;
|
border-left: 3px solid var(--color-surface-4);
|
||||||
padding-left: 0.75em;
|
padding-left: 0.75em;
|
||||||
color: #a1a1aa;
|
color: var(--color-text-secondary);
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Code block */
|
/* Code block */
|
||||||
.mc-code-block {
|
.mc-code-block {
|
||||||
display: block;
|
display: block;
|
||||||
background: rgba(24, 24, 27, 0.8);
|
background: rgba(7, 8, 14, 0.8);
|
||||||
border: 1px solid #27272a;
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
padding: 26px 10px 6px 10px;
|
padding: 26px 12px 8px 12px;
|
||||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
font-family: var(--font-mono);
|
||||||
font-size: 0.9em;
|
font-size: 0.88em;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
tab-size: 2;
|
tab-size: 2;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Language selector overlays positioned outside Lexical-managed DOM */
|
/* Language selector overlays */
|
||||||
.mc-code-overlays-container {
|
.mc-code-overlays-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -256,74 +368,76 @@ body {
|
|||||||
.mc-code-lang-select {
|
.mc-code-lang-select {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
background: rgba(39, 39, 42, 0.9);
|
background: var(--color-surface-2);
|
||||||
border: 1px solid #3f3f46;
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: #a1a1aa;
|
color: var(--color-text-secondary);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
padding: 2px 18px 2px 6px;
|
padding: 2px 18px 2px 6px;
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2371717a' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238b90a5' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: right 3px center;
|
background-position: right 3px center;
|
||||||
background-size: 10px;
|
background-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mc-code-lang-select:hover {
|
.mc-code-lang-select:hover {
|
||||||
border-color: #52525b;
|
border-color: var(--color-surface-4);
|
||||||
color: #d4d4d8;
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mc-code-lang-select:focus {
|
.mc-code-lang-select:focus {
|
||||||
border-color: rgba(99, 102, 241, 0.5);
|
border-color: var(--color-border-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Link */
|
/* Link */
|
||||||
.mc-link {
|
.mc-link {
|
||||||
color: #818cf8;
|
color: var(--color-text-accent);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-underline-offset: 2px;
|
text-underline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Syntax highlighting tokens (dark theme) */
|
/* ── Syntax highlighting tokens ──────────────────────────────── */
|
||||||
.mc-tok-comment { color: #6b7280; font-style: italic; }
|
|
||||||
.mc-tok-keyword { color: #c084fc; }
|
.mc-tok-comment { color: #5a6078; font-style: italic; }
|
||||||
.mc-tok-string { color: #86efac; }
|
.mc-tok-keyword { color: #b07ef5; }
|
||||||
.mc-tok-number { color: #fbbf24; }
|
.mc-tok-string { color: #6ee7b7; }
|
||||||
.mc-tok-boolean { color: #fbbf24; }
|
.mc-tok-number { color: #f9c846; }
|
||||||
.mc-tok-constant { color: #fbbf24; }
|
.mc-tok-boolean { color: #f9c846; }
|
||||||
.mc-tok-function { color: #93c5fd; }
|
.mc-tok-constant { color: #f9c846; }
|
||||||
.mc-tok-builtin { color: #67e8f9; }
|
.mc-tok-function { color: #7ba1fc; }
|
||||||
.mc-tok-operator { color: #f9a8d4; }
|
.mc-tok-builtin { color: #5ccbee; }
|
||||||
.mc-tok-punctuation { color: #a1a1aa; }
|
.mc-tok-operator { color: #e8a0c8; }
|
||||||
.mc-tok-property { color: #7dd3fc; }
|
.mc-tok-punctuation { color: #8b90a5; }
|
||||||
.mc-tok-variable { color: #fca5a5; }
|
.mc-tok-property { color: #65b8f7; }
|
||||||
.mc-tok-tag { color: #f87171; }
|
.mc-tok-variable { color: #f5a0a0; }
|
||||||
.mc-tok-attr { color: #fdba74; }
|
.mc-tok-tag { color: #f07070; }
|
||||||
.mc-tok-atrule { color: #c084fc; }
|
.mc-tok-attr { color: #f0b070; }
|
||||||
.mc-tok-selector { color: #86efac; }
|
.mc-tok-atrule { color: #b07ef5; }
|
||||||
.mc-tok-regex { color: #fcd34d; }
|
.mc-tok-selector { color: #6ee7b7; }
|
||||||
.mc-tok-symbol { color: #fbbf24; }
|
.mc-tok-regex { color: #f0d860; }
|
||||||
.mc-tok-namespace { color: #f87171; opacity: 0.8; }
|
.mc-tok-symbol { color: #f9c846; }
|
||||||
.mc-tok-deleted { color: #fca5a5; text-decoration: line-through; }
|
.mc-tok-namespace { color: #f07070; opacity: 0.8; }
|
||||||
.mc-tok-inserted { color: #86efac; }
|
.mc-tok-deleted { color: #f5a0a0; text-decoration: line-through; }
|
||||||
|
.mc-tok-inserted { color: #6ee7b7; }
|
||||||
|
|
||||||
|
/* ── React Flow overrides ────────────────────────────────────── */
|
||||||
|
|
||||||
/* React Flow dark theme overrides */
|
|
||||||
.react-flow__node {
|
.react-flow__node {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__edge-path {
|
.react-flow__edge-path {
|
||||||
stroke: #52525b;
|
stroke: var(--color-surface-4);
|
||||||
stroke-width: 1.5;
|
stroke-width: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__edge.selected .react-flow__edge-path {
|
.react-flow__edge.selected .react-flow__edge-path {
|
||||||
stroke: #818cf8;
|
stroke: #245CF9;
|
||||||
stroke-width: 2.5;
|
stroke-width: 2.5;
|
||||||
filter: drop-shadow(0 0 4px rgba(129, 140, 248, 0.4));
|
filter: drop-shadow(0 0 6px rgba(36, 92, 249, 0.4));
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__edge.animated .react-flow__edge-path {
|
.react-flow__edge.animated .react-flow__edge-path {
|
||||||
@@ -338,32 +452,33 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__controls {
|
.react-flow__controls {
|
||||||
border: 1px solid #27272a;
|
border: 1px solid var(--color-border);
|
||||||
background: #18181b;
|
background: var(--color-surface-1);
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__controls button {
|
.react-flow__controls button {
|
||||||
background: #18181b;
|
background: var(--color-surface-1);
|
||||||
border-color: #27272a;
|
border-color: var(--color-border);
|
||||||
color: #a1a1aa;
|
color: var(--color-text-secondary);
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__controls button:hover {
|
.react-flow__controls button:hover {
|
||||||
background: #27272a;
|
background: var(--color-surface-2);
|
||||||
color: #e4e4e7;
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__minimap {
|
.react-flow__minimap {
|
||||||
background: #09090b;
|
background: var(--color-surface-0);
|
||||||
border: 1px solid #27272a;
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar running-session pulse animation */
|
/* ── Sidebar running-session pulse ───────────────────────────── */
|
||||||
|
|
||||||
@keyframes sidebar-pulse {
|
@keyframes sidebar-pulse {
|
||||||
0%, 100% { opacity: 1; }
|
0%, 100% { opacity: 1; }
|
||||||
50% { opacity: 0.4; }
|
50% { opacity: 0.4; }
|
||||||
@@ -373,7 +488,41 @@ body {
|
|||||||
animation: sidebar-pulse 2s ease-in-out infinite;
|
animation: sidebar-pulse 2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Markdown prose styles for chat messages */
|
/* Flowing accent bar for running sessions */
|
||||||
|
@keyframes accent-flow {
|
||||||
|
0% { background-position: 0% 0%; }
|
||||||
|
100% { background-position: 0% 200%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.accent-flow {
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
#245CF9 0%,
|
||||||
|
#8A29E6 50%,
|
||||||
|
#245CF9 100%
|
||||||
|
);
|
||||||
|
background-size: 100% 200%;
|
||||||
|
animation: accent-flow 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Ambient glow effect ─────────────────────────────────────── */
|
||||||
|
|
||||||
|
@keyframes ambient-pulse {
|
||||||
|
0%, 100% { opacity: 0.4; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ambient-glow {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(80px);
|
||||||
|
opacity: 0.4;
|
||||||
|
animation: ambient-pulse 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Markdown prose styles ───────────────────────────────────── */
|
||||||
|
|
||||||
.markdown-content {
|
.markdown-content {
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
@@ -393,11 +542,12 @@ body {
|
|||||||
.markdown-content h4,
|
.markdown-content h4,
|
||||||
.markdown-content h5,
|
.markdown-content h5,
|
||||||
.markdown-content h6 {
|
.markdown-content h6 {
|
||||||
|
font-family: var(--font-display);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-zinc-200);
|
color: var(--color-text-primary);
|
||||||
margin-top: 1.25em;
|
margin-top: 1.25em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
line-height: 1.4;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-content h1 { font-size: 1.125rem; }
|
.markdown-content h1 { font-size: 1.125rem; }
|
||||||
@@ -430,9 +580,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown-content blockquote {
|
.markdown-content blockquote {
|
||||||
border-left: 3px solid var(--color-surface-3);
|
border-left: 3px solid var(--color-surface-4);
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
color: var(--color-zinc-400);
|
color: var(--color-text-secondary);
|
||||||
margin-top: 0.75em;
|
margin-top: 0.75em;
|
||||||
margin-bottom: 0.75em;
|
margin-bottom: 0.75em;
|
||||||
}
|
}
|
||||||
@@ -455,7 +605,7 @@ body {
|
|||||||
.markdown-content th {
|
.markdown-content th {
|
||||||
background: var(--color-surface-1);
|
background: var(--color-surface-1);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-zinc-300);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-content hr {
|
.markdown-content hr {
|
||||||
@@ -466,7 +616,7 @@ body {
|
|||||||
|
|
||||||
.markdown-content strong {
|
.markdown-content strong {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-zinc-200);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-content em {
|
.markdown-content em {
|
||||||
|
|||||||
Reference in New Issue
Block a user