mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
fix(desktop): improve Agents/Skills layout and rendering
- Agent list items: replace dot with colored smart_toy icon, bold name - Agent description: use MarkdownRenderer for rich text support - Remove max-width constraints on Agent/Skill containers for responsive layout - Dynamically switch grid columns based on visible group count (single group fills full width) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5911388626
commit
db005e5e98
@ -48,6 +48,11 @@ export function SkillList() {
|
||||
[skills],
|
||||
)
|
||||
|
||||
const visibleGroupCount = useMemo(
|
||||
() => SOURCE_ORDER.filter((source) => (grouped[source] ?? []).length > 0).length,
|
||||
[grouped],
|
||||
)
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center py-12">
|
||||
@ -121,7 +126,7 @@ export function SkillList() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="grid gap-4 xl:grid-cols-2">
|
||||
<div className={`grid gap-4 ${visibleGroupCount >= 2 ? 'xl:grid-cols-2' : ''}`}>
|
||||
{SOURCE_ORDER.map((source) => {
|
||||
const group = grouped[source]
|
||||
if (!group?.length) return null
|
||||
|
||||
@ -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<string, string> = {
|
||||
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<Record<AgentSource, AgentDefinition[]>> = {}
|
||||
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 <AgentDetailView agent={selectedAgent} onBack={() => selectAgent(null)} />
|
||||
return (
|
||||
<div className="w-full min-w-0">
|
||||
<AgentDetailView agent={selectedAgent} onBack={() => selectAgent(null)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-[var(--color-text-primary)]">{t('settings.agents.title')}</h2>
|
||||
<p className="text-sm text-[var(--color-text-tertiary)] mt-0.5">{t('settings.agents.description')}</p>
|
||||
</div>
|
||||
{agents.length > 0 && (
|
||||
<span className="text-xs text-[var(--color-text-tertiary)]">{t('settings.agents.agentCount', { count: String(agents.length) })}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isLoading && agents.length === 0 ? (
|
||||
<div className="w-full min-w-0">
|
||||
{isLoading && allAgents.length === 0 ? (
|
||||
<div className="flex justify-center py-12">
|
||||
<div className="animate-spin w-5 h-5 border-2 border-[var(--color-brand)] border-t-transparent rounded-full" />
|
||||
</div>
|
||||
@ -658,46 +688,158 @@ function AgentsSettings() {
|
||||
<span className="material-symbols-outlined text-[40px] text-[var(--color-error)] mb-3 block">error_outline</span>
|
||||
<p className="text-sm text-[var(--color-error)] mb-2">{error}</p>
|
||||
<button
|
||||
onClick={() => fetchAgents()}
|
||||
onClick={() => void fetchAgents(currentWorkDir)}
|
||||
className="text-xs text-[var(--color-text-accent)] hover:underline"
|
||||
>
|
||||
Retry
|
||||
{t('common.retry')}
|
||||
</button>
|
||||
</div>
|
||||
) : agents.length === 0 ? (
|
||||
<div className="text-center py-12 px-4">
|
||||
) : allAgents.length === 0 ? (
|
||||
<div className="text-center py-12 px-4 rounded-2xl border border-dashed border-[var(--color-border)] bg-[var(--color-surface-container-low)]">
|
||||
<span className="material-symbols-outlined text-[40px] text-[var(--color-text-tertiary)] mb-3 block">smart_toy</span>
|
||||
<p className="text-sm text-[var(--color-text-secondary)] mb-1">{t('settings.agents.empty')}</p>
|
||||
<p className="text-xs text-[var(--color-text-tertiary)]">{t('settings.agents.emptyHint')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
{agents.map((agent) => {
|
||||
const dotColor = agent.color && AGENT_COLORS[agent.color] ? AGENT_COLORS[agent.color] : 'var(--color-text-tertiary)'
|
||||
return (
|
||||
<button
|
||||
key={agent.name}
|
||||
onClick={() => selectAgent(agent)}
|
||||
className="flex items-center gap-3 px-4 py-3.5 rounded-xl border border-[var(--color-border)] hover:border-[var(--color-border-focus)] hover:bg-[var(--color-surface-hover)] transition-all text-left group"
|
||||
>
|
||||
<span className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: dotColor }} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-semibold text-[var(--color-text-primary)] truncate">{agent.name}</span>
|
||||
{agent.model && (
|
||||
<span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-[var(--color-surface-container-high)] text-[var(--color-text-tertiary)] leading-none">{agent.model}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--color-text-tertiary)] truncate mt-0.5">
|
||||
{agent.description || t('settings.agents.noDescription')}
|
||||
</div>
|
||||
<div className="flex flex-col gap-6 min-w-0">
|
||||
<section className="rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-container-low)] overflow-hidden">
|
||||
<div className="grid gap-4 px-5 py-5 md:grid-cols-[minmax(0,1.6fr)_minmax(280px,1fr)] md:items-end">
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-[var(--color-text-tertiary)] mb-2">
|
||||
{t('settings.agents.browserEyebrow')}
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-[18px] text-[var(--color-text-tertiary)] opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
chevron_right
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<span className="material-symbols-outlined text-[22px] text-[var(--color-brand)]">
|
||||
smart_toy
|
||||
</span>
|
||||
<h3 className="text-lg font-semibold text-[var(--color-text-primary)]">
|
||||
{t('settings.agents.browserTitle')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm leading-6 text-[var(--color-text-secondary)] max-w-3xl">
|
||||
{t('settings.agents.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-3">
|
||||
<SummaryCard
|
||||
label={t('settings.agents.summary.totalAgents')}
|
||||
value={String(allAgents.length)}
|
||||
icon="smart_toy"
|
||||
/>
|
||||
<SummaryCard
|
||||
label={t('settings.agents.summary.activeAgents')}
|
||||
value={String(activeAgents.length)}
|
||||
icon="bolt"
|
||||
/>
|
||||
<SummaryCard
|
||||
label={t('settings.agents.summary.sources')}
|
||||
value={String(sourceCount)}
|
||||
icon="layers"
|
||||
className="col-span-2 md:col-span-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className={`grid gap-4 ${sourceCount >= 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 (
|
||||
<section
|
||||
key={source}
|
||||
className="rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] overflow-hidden min-w-0"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3 px-5 py-4 border-b border-[var(--color-border)] bg-[var(--color-surface-container-low)]">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1 flex-wrap">
|
||||
<span className={`inline-flex h-7 w-7 items-center justify-center rounded-full ${getAgentSourceAccentClass(source)}`}>
|
||||
<span className="material-symbols-outlined text-[16px]">
|
||||
{getAgentSourceIcon(source)}
|
||||
</span>
|
||||
</span>
|
||||
<h4 className="text-sm font-semibold text-[var(--color-text-primary)]">
|
||||
{sourceLabel}
|
||||
</h4>
|
||||
<span className="text-xs text-[var(--color-text-tertiary)]">
|
||||
{group.length}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs leading-5 text-[var(--color-text-tertiary)]">
|
||||
{t('settings.agents.groupHint', {
|
||||
source: sourceLabel,
|
||||
count: String(group.length),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col p-2">
|
||||
{group.map((agent) => (
|
||||
<button
|
||||
key={`${agent.source}-${agent.agentType}`}
|
||||
onClick={() => selectAgent(agent)}
|
||||
className="group rounded-xl border border-transparent px-3 py-3 text-left transition-all hover:border-[var(--color-border-focus)] hover:bg-[var(--color-surface-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface)]"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<span
|
||||
className="mt-0.5 flex-shrink-0 inline-flex items-center justify-center"
|
||||
style={{ color: getAgentDotColor(agent.color) }}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[18px]">smart_toy</span>
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-sm font-bold text-[var(--color-text-primary)] break-all">
|
||||
{agent.agentType}
|
||||
</span>
|
||||
{agent.modelDisplay && (
|
||||
<MetaPill>{agent.modelDisplay}</MetaPill>
|
||||
)}
|
||||
<MetaPill>{sourceLabel}</MetaPill>
|
||||
<MetaPill>
|
||||
{agent.isActive
|
||||
? t('settings.agents.status.active')
|
||||
: t('settings.agents.status.available')}
|
||||
</MetaPill>
|
||||
{agent.overriddenBy && (
|
||||
<MetaPill>
|
||||
{t('settings.agents.overriddenBy', {
|
||||
source: t(`settings.agents.source.${agent.overriddenBy}`),
|
||||
})}
|
||||
</MetaPill>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 text-xs leading-5 text-[var(--color-text-secondary)] break-words [&_.prose]:text-xs [&_.prose]:leading-5 [&_.prose]:text-[var(--color-text-secondary)]">
|
||||
<MarkdownRenderer
|
||||
content={agent.description || t('settings.agents.noDescription')}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-[var(--color-text-tertiary)]">
|
||||
<span>
|
||||
{agent.tools?.length
|
||||
? t('settings.agents.toolCount', { count: String(agent.tools.length) })
|
||||
: t('settings.agents.noTools')}
|
||||
</span>
|
||||
{agent.baseDir && (
|
||||
<span className="break-all">{agent.baseDir}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-[18px] text-[var(--color-text-tertiary)] opacity-60 transition-transform group-hover:translate-x-0.5 group-hover:opacity-100">
|
||||
chevron_right
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -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 (
|
||||
<div className="max-w-2xl">
|
||||
{/* Back button */}
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="flex items-center gap-1 text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors mb-4"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[16px]">arrow_back</span>
|
||||
{t('settings.agents.backToList')}
|
||||
</button>
|
||||
|
||||
{/* Agent header */}
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<span className="w-3 h-3 rounded-full flex-shrink-0" style={{ backgroundColor: dotColor }} />
|
||||
<h2 className="text-lg font-semibold text-[var(--color-text-primary)]">{agent.name}</h2>
|
||||
<div className="flex h-full min-h-0 flex-col gap-4 min-w-0">
|
||||
<div>
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="inline-flex items-center gap-1 rounded-lg px-2 py-1 text-sm text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)]"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[16px]">arrow_back</span>
|
||||
{t('settings.agents.backToList')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{agent.description && (
|
||||
<p className="text-sm text-[var(--color-text-secondary)] mb-4">{agent.description}</p>
|
||||
<section className="rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-container-low)] overflow-hidden">
|
||||
<div className="grid gap-4 px-5 py-5 lg:grid-cols-[minmax(0,1.5fr)_minmax(280px,0.9fr)] lg:items-start">
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-[var(--color-text-tertiary)] mb-2">
|
||||
{t('settings.agents.entryEyebrow')}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 mb-2">
|
||||
<span
|
||||
className="h-3 w-3 rounded-full flex-shrink-0"
|
||||
style={{ backgroundColor: getAgentDotColor(agent.color) }}
|
||||
/>
|
||||
<h3 className="text-[22px] font-semibold leading-tight text-[var(--color-text-primary)] break-all">
|
||||
{agent.agentType}
|
||||
</h3>
|
||||
<MetaPill>{sourceLabel}</MetaPill>
|
||||
{agent.modelDisplay && <MetaPill>{agent.modelDisplay}</MetaPill>}
|
||||
<MetaPill>
|
||||
{agent.isActive
|
||||
? t('settings.agents.status.active')
|
||||
: t('settings.agents.status.available')}
|
||||
</MetaPill>
|
||||
{agent.overriddenBy && (
|
||||
<MetaPill>
|
||||
{t('settings.agents.overriddenByShort', {
|
||||
source: t(`settings.agents.source.${agent.overriddenBy}`),
|
||||
})}
|
||||
</MetaPill>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-4xl text-sm leading-6 text-[var(--color-text-secondary)]">
|
||||
<MarkdownRenderer
|
||||
content={agent.description || t('settings.agents.noDescription')}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap gap-x-4 gap-y-2 text-xs text-[var(--color-text-tertiary)]">
|
||||
<span>
|
||||
{agent.tools?.length
|
||||
? t('settings.agents.toolCount', { count: String(agent.tools.length) })
|
||||
: t('settings.agents.noTools')}
|
||||
</span>
|
||||
{agent.baseDir && <span className="break-all">{agent.baseDir}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4 lg:grid-cols-2">
|
||||
<DetailStat
|
||||
label={t('settings.agents.summary.source')}
|
||||
value={sourceLabel}
|
||||
icon="layers"
|
||||
/>
|
||||
<DetailStat
|
||||
label={t('settings.agents.summary.model')}
|
||||
value={agent.modelDisplay || '—'}
|
||||
icon="psychology"
|
||||
/>
|
||||
<DetailStat
|
||||
label={t('settings.agents.summary.tools')}
|
||||
value={String(agent.tools?.length ?? 0)}
|
||||
icon="build"
|
||||
/>
|
||||
<DetailStat
|
||||
label={t('settings.agents.summary.status')}
|
||||
value={agent.isActive ? t('settings.agents.status.active') : t('settings.agents.status.available')}
|
||||
icon="bolt"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{agent.tools && agent.tools.length > 0 && (
|
||||
<section className="rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] px-5 py-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="material-symbols-outlined text-[18px] text-[var(--color-text-tertiary)]">
|
||||
build
|
||||
</span>
|
||||
<h4 className="text-sm font-semibold text-[var(--color-text-primary)]">
|
||||
{t('settings.agents.tools')}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{agent.tools.map((tool) => (
|
||||
<MetaPill key={tool}>{tool}</MetaPill>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Meta info */}
|
||||
<div className="flex flex-wrap gap-4 mb-6">
|
||||
{agent.model && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="material-symbols-outlined text-[16px] text-[var(--color-text-tertiary)]">psychology</span>
|
||||
<span className="text-xs text-[var(--color-text-secondary)]">{t('settings.agents.model')}: <strong>{agent.model}</strong></span>
|
||||
<section className="flex flex-1 min-h-0 min-w-0 overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)]">
|
||||
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-[var(--color-border)] bg-[var(--color-surface-container-low)] px-4 py-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-xs font-mono text-[var(--color-text-secondary)] break-all">
|
||||
{agent.baseDir || sourceLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 text-[11px] text-[var(--color-text-tertiary)]">
|
||||
{t('settings.agents.promptHint')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-full bg-[var(--color-surface)] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-tertiary)] border border-[var(--color-border)]">
|
||||
{t('settings.agents.systemPrompt')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{agent.tools && agent.tools.length > 0 && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="material-symbols-outlined text-[16px] text-[var(--color-text-tertiary)]">build</span>
|
||||
<span className="text-xs text-[var(--color-text-secondary)]">{t('settings.agents.tools')}: <strong>{agent.tools.join(', ')}</strong></span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* System Prompt */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-[var(--color-text-primary)] mb-2">{t('settings.agents.systemPrompt')}</h3>
|
||||
{agent.systemPrompt ? (
|
||||
<div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-container-low)] p-4 max-h-[60vh] overflow-y-auto">
|
||||
<MarkdownRenderer content={agent.systemPrompt} />
|
||||
<div className="min-h-0 flex-1 overflow-y-auto bg-[var(--color-surface-container-lowest)]">
|
||||
{agent.systemPrompt ? (
|
||||
<div className="px-6 py-5 lg:px-8">
|
||||
<MarkdownRenderer
|
||||
content={agent.systemPrompt}
|
||||
variant="document"
|
||||
className="mx-auto max-w-[72ch]"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-6 py-10 text-center">
|
||||
<span className="material-symbols-outlined text-[32px] text-[var(--color-text-tertiary)] mb-2 block">
|
||||
article
|
||||
</span>
|
||||
<p className="text-sm text-[var(--color-text-tertiary)]">
|
||||
{t('settings.agents.noSystemPrompt')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-[var(--color-text-tertiary)]">{t('settings.agents.noSystemPrompt')}</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<span className="rounded-full border border-[var(--color-border)] bg-[var(--color-surface)] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-tertiary)]">
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function SummaryCard({
|
||||
label,
|
||||
value,
|
||||
icon,
|
||||
className = '',
|
||||
}: {
|
||||
label: string
|
||||
value: string
|
||||
icon: string
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<div className={`rounded-xl border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-3 ${className}`}>
|
||||
<div className="flex items-center gap-2 text-[11px] uppercase tracking-[0.16em] text-[var(--color-text-tertiary)]">
|
||||
<span className="material-symbols-outlined text-[14px]">{icon}</span>
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
<div className="mt-2 text-lg font-semibold text-[var(--color-text-primary)]">
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DetailStat({
|
||||
label,
|
||||
value,
|
||||
icon,
|
||||
}: {
|
||||
label: string
|
||||
value: string
|
||||
icon: string
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-3">
|
||||
<div className="flex items-center gap-2 text-[11px] uppercase tracking-[0.16em] text-[var(--color-text-tertiary)]">
|
||||
<span className="material-symbols-outlined text-[14px]">{icon}</span>
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
<div className="mt-2 text-base font-semibold text-[var(--color-text-primary)] break-all">
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
// ─── Skill Settings ──────────────────────────────────────
|
||||
|
||||
function SkillSettings() {
|
||||
@ -769,14 +1098,14 @@ function SkillSettings() {
|
||||
|
||||
if (selectedSkill) {
|
||||
return (
|
||||
<div className="w-full min-w-0 max-w-[1400px]">
|
||||
<div className="w-full min-w-0">
|
||||
<SkillDetail />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full min-w-0 max-w-[1280px]">
|
||||
<div className="w-full min-w-0">
|
||||
<h2 className="text-base font-semibold text-[var(--color-text-primary)] mb-1">
|
||||
{t('settings.skills.title')}
|
||||
</h2>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user