diff --git a/desktop/src/components/skills/SkillList.tsx b/desktop/src/components/skills/SkillList.tsx index 37ef738b..7ecb7cf3 100644 --- a/desktop/src/components/skills/SkillList.tsx +++ b/desktop/src/components/skills/SkillList.tsx @@ -48,6 +48,11 @@ export function SkillList() { [skills], ) + const visibleGroupCount = useMemo( + () => SOURCE_ORDER.filter((source) => (grouped[source] ?? []).length > 0).length, + [grouped], + ) + if (isLoading) { return (
@@ -121,7 +126,7 @@ export function SkillList() {
-
+
= 2 ? 'xl:grid-cols-2' : ''}`}> {SOURCE_ORDER.map((source) => { const group = grouped[source] if (!group?.length) return null diff --git a/desktop/src/pages/Settings.tsx b/desktop/src/pages/Settings.tsx index 7d022db0..6f10f0cd 100644 --- a/desktop/src/pages/Settings.tsx +++ b/desktop/src/pages/Settings.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect, useMemo, useRef, type ReactNode } from 'react' import { useSettingsStore } from '../stores/settingsStore' import { useProviderStore } from '../stores/providerStore' import { useTranslation } from '../i18n' @@ -12,7 +12,8 @@ import type { ProviderPreset } from '../config/providerPresets' import type { SavedProvider, UpdateProviderInput, ProviderTestResult, ModelMapping } from '../types/provider' import { AdapterSettings } from './AdapterSettings' import { useAgentStore } from '../stores/agentStore' -import type { AgentDefinition } from '../api/agents' +import { useSessionStore } from '../stores/sessionStore' +import type { AgentDefinition, AgentSource } from '../api/agents' import { MarkdownRenderer } from '../components/markdown/MarkdownRenderer' import { useSkillStore } from '../stores/skillStore' import { SkillList } from '../components/skills/SkillList' @@ -627,29 +628,58 @@ const AGENT_COLORS: Record = { cyan: '#06b6d4', } +const AGENT_SOURCE_ORDER: AgentSource[] = [ + 'userSettings', + 'projectSettings', + 'localSettings', + 'policySettings', + 'plugin', + 'flagSettings', + 'built-in', +] + function AgentsSettings() { - const { agents, isLoading, error, selectedAgent, fetchAgents, selectAgent } = useAgentStore() + const { + activeAgents, + allAgents, + isLoading, + error, + selectedAgent, + fetchAgents, + selectAgent, + } = useAgentStore() + const sessions = useSessionStore((s) => s.sessions) + const activeSessionId = useSessionStore((s) => s.activeSessionId) const t = useTranslation() - useEffect(() => { fetchAgents() }, [fetchAgents]) + const activeSession = sessions.find((s) => s.id === activeSessionId) + const currentWorkDir = activeSession?.workDir || undefined + + useEffect(() => { + void fetchAgents(currentWorkDir) + }, [fetchAgents, currentWorkDir]) + + const groupedAgents = useMemo(() => { + const groups: Partial> = {} + for (const agent of allAgents) { + ;(groups[agent.source] ??= []).push(agent) + } + return groups + }, [allAgents]) + + const sourceCount = AGENT_SOURCE_ORDER.filter((source) => (groupedAgents[source] ?? []).length > 0).length if (selectedAgent) { - return selectAgent(null)} /> + return ( +
+ selectAgent(null)} /> +
+ ) } return ( -
-
-
-

{t('settings.agents.title')}

-

{t('settings.agents.description')}

-
- {agents.length > 0 && ( - {t('settings.agents.agentCount', { count: String(agents.length) })} - )} -
- - {isLoading && agents.length === 0 ? ( +
+ {isLoading && allAgents.length === 0 ? (
@@ -658,46 +688,158 @@ function AgentsSettings() { error_outline

{error}

- ) : agents.length === 0 ? ( -
+ ) : allAgents.length === 0 ? ( +
smart_toy

{t('settings.agents.empty')}

{t('settings.agents.emptyHint')}

) : ( -
- {agents.map((agent) => { - const dotColor = agent.color && AGENT_COLORS[agent.color] ? AGENT_COLORS[agent.color] : 'var(--color-text-tertiary)' - return ( - - ) - })} +
+ + smart_toy + +

+ {t('settings.agents.browserTitle')} +

+
+

+ {t('settings.agents.description')} +

+
+ +
+ + + +
+
+ + +
= 2 ? 'xl:grid-cols-2' : ''}`}> + {AGENT_SOURCE_ORDER.map((source) => { + const group = groupedAgents[source] + if (!group?.length) return null + + const sourceLabel = t(`settings.agents.source.${source}`) + return ( +
+
+
+
+ + + {getAgentSourceIcon(source)} + + +

+ {sourceLabel} +

+ + {group.length} + +
+

+ {t('settings.agents.groupHint', { + source: sourceLabel, + count: String(group.length), + })} +

+
+
+ +
+ {group.map((agent) => ( + + ))} +
+
+ ) + })} +
)}
@@ -706,61 +848,248 @@ function AgentsSettings() { function AgentDetailView({ agent, onBack }: { agent: AgentDefinition; onBack: () => void }) { const t = useTranslation() - const dotColor = agent.color && AGENT_COLORS[agent.color] ? AGENT_COLORS[agent.color] : 'var(--color-text-tertiary)' + const sourceLabel = t(`settings.agents.source.${agent.source}`) return ( -
- {/* Back button */} - - - {/* Agent header */} -
- -

{agent.name}

+
+
+
- {/* Description */} - {agent.description && ( -

{agent.description}

+
+
+
+
+ {t('settings.agents.entryEyebrow')} +
+
+ +

+ {agent.agentType} +

+ {sourceLabel} + {agent.modelDisplay && {agent.modelDisplay}} + + {agent.isActive + ? t('settings.agents.status.active') + : t('settings.agents.status.available')} + + {agent.overriddenBy && ( + + {t('settings.agents.overriddenByShort', { + source: t(`settings.agents.source.${agent.overriddenBy}`), + })} + + )} +
+
+ +
+
+ + {agent.tools?.length + ? t('settings.agents.toolCount', { count: String(agent.tools.length) }) + : t('settings.agents.noTools')} + + {agent.baseDir && {agent.baseDir}} +
+
+ +
+ + + + +
+
+
+ + {agent.tools && agent.tools.length > 0 && ( +
+
+ + build + +

+ {t('settings.agents.tools')} +

+
+
+ {agent.tools.map((tool) => ( + {tool} + ))} +
+
)} - {/* Meta info */} -
- {agent.model && ( -
- psychology - {t('settings.agents.model')}: {agent.model} +
+
+
+
+
+ + {agent.baseDir || sourceLabel} + +
+
+ {t('settings.agents.promptHint')} +
+
+
+ + {t('settings.agents.systemPrompt')} + +
- )} - {agent.tools && agent.tools.length > 0 && ( -
- build - {t('settings.agents.tools')}: {agent.tools.join(', ')} -
- )} -
- {/* System Prompt */} -
-

{t('settings.agents.systemPrompt')}

- {agent.systemPrompt ? ( -
- +
+ {agent.systemPrompt ? ( +
+ +
+ ) : ( +
+ + article + +

+ {t('settings.agents.noSystemPrompt')} +

+
+ )}
- ) : ( -

{t('settings.agents.noSystemPrompt')}

- )} +
+
+
+ ) +} + +function getAgentDotColor(color?: string) { + return color && AGENT_COLORS[color] ? AGENT_COLORS[color] : 'var(--color-text-tertiary)' +} + +function getAgentSourceIcon(source: AgentSource) { + switch (source) { + case 'userSettings': + return 'person' + case 'projectSettings': + return 'folder' + case 'localSettings': + return 'folder_lock' + case 'policySettings': + return 'shield' + case 'plugin': + return 'extension' + case 'flagSettings': + return 'terminal' + case 'built-in': + return 'inventory_2' + } +} + +function getAgentSourceAccentClass(source: AgentSource) { + switch (source) { + case 'userSettings': + return 'bg-[var(--color-primary-fixed)] text-[var(--color-brand)]' + case 'projectSettings': + return 'bg-[var(--color-success-container)] text-[var(--color-success)]' + case 'localSettings': + return 'bg-[var(--color-info-container)] text-[var(--color-info)]' + case 'policySettings': + return 'bg-[var(--color-warning-container)] text-[var(--color-warning)]' + case 'plugin': + return 'bg-[var(--color-warning-container)] text-[var(--color-warning)]' + case 'flagSettings': + return 'bg-[var(--color-error)]/10 text-[var(--color-error)]' + case 'built-in': + return 'bg-[var(--color-surface-container-high)] text-[var(--color-text-tertiary)]' + } +} + +function MetaPill({ children }: { children: ReactNode }) { + return ( + + {children} + + ) +} + +function SummaryCard({ + label, + value, + icon, + className = '', +}: { + label: string + value: string + icon: string + className?: string +}) { + return ( +
+
+ {icon} + {label} +
+
+ {value}
) } +function DetailStat({ + label, + value, + icon, +}: { + label: string + value: string + icon: string +}) { + return ( +
+
+ {icon} + {label} +
+
+ {value} +
+
+ ) +} // ─── Skill Settings ────────────────────────────────────── function SkillSettings() { @@ -769,14 +1098,14 @@ function SkillSettings() { if (selectedSkill) { return ( -
+
) } return ( -
+

{t('settings.skills.title')}