Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/web-ui/src/app/scenes/agents/AgentsScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
GalleryZone,
} from '@/app/components';
import AgentCard from './components/AgentCard';
import CoreAgentCard, { type CoreAgentMeta } from './components/CoreAgentCard';
import AgentTeamCard from './components/AgentTeamCard';
import AgentTeamTabBar from './components/AgentTeamTabBar';
import AgentGallery from './components/AgentGallery';
Expand All @@ -44,6 +45,14 @@ import './AgentsScene.scss';

const EXAMPLE_TEAM_IDS = new Set(MOCK_AGENT_TEAMS.map((team) => team.id));

const CORE_AGENT_IDS = new Set(['Claw', 'agentic', 'Cowork']);

const CORE_AGENT_META: Record<string, CoreAgentMeta> = {
Claw: { role: '个人助理', accentColor: '#f59e0b', accentBg: 'rgba(245,158,11,0.10)' },
agentic: { role: '编码专业智能体', accentColor: '#6366f1', accentBg: 'rgba(99,102,241,0.10)' },
Cowork: { role: '办公智能体', accentColor: '#14b8a6', accentBg: 'rgba(20,184,166,0.10)' },
};

const AgentTeamEditorView: React.FC = () => {
const { t } = useTranslation('scenes/agents');
const { openHome } = useAgentsStore();
Expand Down Expand Up @@ -121,6 +130,8 @@ const AgentsHomeView: React.FC = () => {
return team.name.toLowerCase().includes(query) || team.description.toLowerCase().includes(query);
}), [agentTeams, searchQuery]);

const coreAgents = useMemo(() => allAgents.filter((agent) => CORE_AGENT_IDS.has(agent.id)), [allAgents]);

const handleCreateTeam = useCallback(() => {
const id = `agent-team-${Date.now()}`;
addAgentTeam({
Expand Down Expand Up @@ -214,6 +225,13 @@ const AgentsHomeView: React.FC = () => {
subtitle={t('page.subtitle')}
extraContent={(
<div className="gallery-anchor-bar">
<button
type="button"
className="gallery-anchor-btn"
onClick={() => scrollToZone('core-agents-zone')}
>
{t('nav.coreAgents')}
</button>
<button
type="button"
className="gallery-anchor-btn"
Expand Down Expand Up @@ -267,6 +285,37 @@ const AgentsHomeView: React.FC = () => {
/>

<div className="gallery-zones">
<GalleryZone
id="core-agents-zone"
title={t('coreAgentsZone.title')}
subtitle={t('coreAgentsZone.subtitle')}
tools={(
<span className="gallery-zone-count">{coreAgents.length}</span>
)}
>
{loading ? (
<GallerySkeleton count={3} cardHeight={160} className="core-agent-skeleton" />
) : coreAgents.length === 0 ? (
<GalleryEmpty
icon={<Cpu size={32} strokeWidth={1.5} />}
message={t('coreAgentsZone.empty')}
/>
) : (
<div className="core-agents-grid">
{coreAgents.map((agent, index) => (
<CoreAgentCard
key={agent.id}
agent={agent}
index={index}
meta={CORE_AGENT_META[agent.id] ?? { role: agent.name, accentColor: '#6366f1', accentBg: 'rgba(99,102,241,0.10)' }}
skillCount={agent.agentKind === 'mode' ? (getModeConfig(agent.id)?.available_skills?.length ?? 0) : 0}
onOpenDetails={openAgentDetails}
/>
))}
</div>
)}
</GalleryZone>

<GalleryZone
id="agents-zone"
title={t('agentsZone.title')}
Expand Down
213 changes: 213 additions & 0 deletions src/web-ui/src/app/scenes/agents/components/CoreAgentCard.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
@use '../../../../component-library/styles/tokens' as *;

.core-agent-card {
display: flex;
flex-direction: column;
border-radius: $size-radius-xl;
background: var(--element-bg-soft);
border: 1px solid transparent;
cursor: pointer;
position: relative;
overflow: hidden;
animation: core-card-in 0.28s $easing-decelerate both;
animation-delay: calc(var(--card-index, 0) * 60ms);
transition:
background $motion-fast $easing-standard,
border-color $motion-fast $easing-standard,
box-shadow $motion-fast $easing-standard,
transform $motion-fast $easing-standard;

// Subtle top accent glow
&::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(
ellipse 80% 40% at 50% 0%,
color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 10%, transparent) 0%,
transparent 70%
);
pointer-events: none;
}

&:hover {
background: var(--element-bg-medium);
border-color: color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 30%, transparent);
transform: translateY(-2px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.14),
0 0 0 1px color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 20%, transparent);

.core-agent-card__icon-wrap {
transform: scale(1.08);
}
}

&:focus-visible {
outline: 2px solid var(--core-accent, var(--color-accent-500));
outline-offset: 2px;
}

&--disabled {
.core-agent-card__name,
.core-agent-card__desc,
.core-agent-card__footer {
opacity: 0.65;
}
}

// ── Top section ──────────────────────────────────────────────────

&__top {
display: flex;
align-items: center;
gap: $size-gap-3;
padding: $size-gap-4 $size-gap-4 $size-gap-3 $size-gap-4;
background: var(--core-accent-bg, rgba(99, 102, 241, 0.08));
border-bottom: 1px solid color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 15%, transparent);
}

&__icon-wrap {
flex-shrink: 0;
width: 44px;
height: 44px;
border-radius: $size-radius-lg;
background: color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 18%, var(--element-bg-soft));
border: 1px solid color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 30%, transparent);
display: flex;
align-items: center;
justify-content: center;
color: var(--core-accent, var(--color-accent-500));
transition: transform $motion-fast $easing-decelerate;
}

&__top-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}

&__name {
font-size: $font-size-base;
font-weight: $font-weight-semibold;
color: var(--color-text-primary);
line-height: $line-height-tight;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

&__role {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: $font-size-xs;
font-weight: $font-weight-medium;
color: var(--core-accent, var(--color-accent-500));
opacity: 0.9;
}

// ── Body ─────────────────────────────────────────────────────────

&__body {
flex: 1;
padding: $size-gap-3 $size-gap-4 $size-gap-2 $size-gap-4;
}

&__desc {
margin: 0;
font-size: $font-size-xs;
color: var(--color-text-secondary);
line-height: $line-height-relaxed;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
word-break: break-word;
}

// ── Footer ───────────────────────────────────────────────────────

&__footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: $size-gap-2;
padding: $size-gap-2 $size-gap-4 $size-gap-3 $size-gap-4;
border-top: 1px dashed color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 18%, var(--border-subtle));
}

&__tag {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 10px;
color: var(--color-text-muted);

strong {
font-weight: $font-weight-semibold;
color: var(--core-accent, var(--color-accent-500));
opacity: 0.9;
}
}

&__meta {
display: inline-flex;
align-items: center;
gap: $size-gap-2;
}

&__meta-item {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 10px;
color: var(--color-text-muted);
}
}

// ── Grid wrapper (3-col max for core section) ─────────────────────

.core-agents-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: $size-gap-4;
width: 100%;
}

// ── Animation ─────────────────────────────────────────────────────

@keyframes core-card-in {
from {
opacity: 0;
transform: translateY(14px) scale(0.97);
}

to {
opacity: 1;
transform: translateY(0) scale(1);
}
}

@media (prefers-reduced-motion: reduce) {
.core-agent-card {
animation: none;
transition: none;
}
}

// ── Responsive ────────────────────────────────────────────────────

@media (max-width: 1080px) {
.core-agents-grid {
grid-template-columns: repeat(2, 1fr);
}
}

@media (max-width: 640px) {
.core-agents-grid {
grid-template-columns: 1fr;
}
}
Loading
Loading