Skip to content

Commit f1881a7

Browse files
authored
Merge pull request #155 from GCWing/gcwing/dev
feat: add CoreAgentCard component and core agents zone
2 parents 38fb7de + 09a5cf3 commit f1881a7

File tree

12 files changed

+543
-129
lines changed

12 files changed

+543
-129
lines changed

src/web-ui/src/app/scenes/agents/AgentsScene.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
GalleryZone,
2323
} from '@/app/components';
2424
import AgentCard from './components/AgentCard';
25+
import CoreAgentCard, { type CoreAgentMeta } from './components/CoreAgentCard';
2526
import AgentTeamCard from './components/AgentTeamCard';
2627
import AgentTeamTabBar from './components/AgentTeamTabBar';
2728
import AgentGallery from './components/AgentGallery';
@@ -44,6 +45,14 @@ import './AgentsScene.scss';
4445

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

48+
const CORE_AGENT_IDS = new Set(['Claw', 'agentic', 'Cowork']);
49+
50+
const CORE_AGENT_META: Record<string, CoreAgentMeta> = {
51+
Claw: { role: '个人助理', accentColor: '#f59e0b', accentBg: 'rgba(245,158,11,0.10)' },
52+
agentic: { role: '编码专业智能体', accentColor: '#6366f1', accentBg: 'rgba(99,102,241,0.10)' },
53+
Cowork: { role: '办公智能体', accentColor: '#14b8a6', accentBg: 'rgba(20,184,166,0.10)' },
54+
};
55+
4756
const AgentTeamEditorView: React.FC = () => {
4857
const { t } = useTranslation('scenes/agents');
4958
const { openHome } = useAgentsStore();
@@ -121,6 +130,8 @@ const AgentsHomeView: React.FC = () => {
121130
return team.name.toLowerCase().includes(query) || team.description.toLowerCase().includes(query);
122131
}), [agentTeams, searchQuery]);
123132

133+
const coreAgents = useMemo(() => allAgents.filter((agent) => CORE_AGENT_IDS.has(agent.id)), [allAgents]);
134+
124135
const handleCreateTeam = useCallback(() => {
125136
const id = `agent-team-${Date.now()}`;
126137
addAgentTeam({
@@ -214,6 +225,13 @@ const AgentsHomeView: React.FC = () => {
214225
subtitle={t('page.subtitle')}
215226
extraContent={(
216227
<div className="gallery-anchor-bar">
228+
<button
229+
type="button"
230+
className="gallery-anchor-btn"
231+
onClick={() => scrollToZone('core-agents-zone')}
232+
>
233+
{t('nav.coreAgents')}
234+
</button>
217235
<button
218236
type="button"
219237
className="gallery-anchor-btn"
@@ -267,6 +285,37 @@ const AgentsHomeView: React.FC = () => {
267285
/>
268286

269287
<div className="gallery-zones">
288+
<GalleryZone
289+
id="core-agents-zone"
290+
title={t('coreAgentsZone.title')}
291+
subtitle={t('coreAgentsZone.subtitle')}
292+
tools={(
293+
<span className="gallery-zone-count">{coreAgents.length}</span>
294+
)}
295+
>
296+
{loading ? (
297+
<GallerySkeleton count={3} cardHeight={160} className="core-agent-skeleton" />
298+
) : coreAgents.length === 0 ? (
299+
<GalleryEmpty
300+
icon={<Cpu size={32} strokeWidth={1.5} />}
301+
message={t('coreAgentsZone.empty')}
302+
/>
303+
) : (
304+
<div className="core-agents-grid">
305+
{coreAgents.map((agent, index) => (
306+
<CoreAgentCard
307+
key={agent.id}
308+
agent={agent}
309+
index={index}
310+
meta={CORE_AGENT_META[agent.id] ?? { role: agent.name, accentColor: '#6366f1', accentBg: 'rgba(99,102,241,0.10)' }}
311+
skillCount={agent.agentKind === 'mode' ? (getModeConfig(agent.id)?.available_skills?.length ?? 0) : 0}
312+
onOpenDetails={openAgentDetails}
313+
/>
314+
))}
315+
</div>
316+
)}
317+
</GalleryZone>
318+
270319
<GalleryZone
271320
id="agents-zone"
272321
title={t('agentsZone.title')}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
@use '../../../../component-library/styles/tokens' as *;
2+
3+
.core-agent-card {
4+
display: flex;
5+
flex-direction: column;
6+
border-radius: $size-radius-xl;
7+
background: var(--element-bg-soft);
8+
border: 1px solid transparent;
9+
cursor: pointer;
10+
position: relative;
11+
overflow: hidden;
12+
animation: core-card-in 0.28s $easing-decelerate both;
13+
animation-delay: calc(var(--card-index, 0) * 60ms);
14+
transition:
15+
background $motion-fast $easing-standard,
16+
border-color $motion-fast $easing-standard,
17+
box-shadow $motion-fast $easing-standard,
18+
transform $motion-fast $easing-standard;
19+
20+
// Subtle top accent glow
21+
&::after {
22+
content: '';
23+
position: absolute;
24+
inset: 0;
25+
background: radial-gradient(
26+
ellipse 80% 40% at 50% 0%,
27+
color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 10%, transparent) 0%,
28+
transparent 70%
29+
);
30+
pointer-events: none;
31+
}
32+
33+
&:hover {
34+
background: var(--element-bg-medium);
35+
border-color: color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 30%, transparent);
36+
transform: translateY(-2px);
37+
box-shadow:
38+
0 8px 24px rgba(0, 0, 0, 0.14),
39+
0 0 0 1px color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 20%, transparent);
40+
41+
.core-agent-card__icon-wrap {
42+
transform: scale(1.08);
43+
}
44+
}
45+
46+
&:focus-visible {
47+
outline: 2px solid var(--core-accent, var(--color-accent-500));
48+
outline-offset: 2px;
49+
}
50+
51+
&--disabled {
52+
.core-agent-card__name,
53+
.core-agent-card__desc,
54+
.core-agent-card__footer {
55+
opacity: 0.65;
56+
}
57+
}
58+
59+
// ── Top section ──────────────────────────────────────────────────
60+
61+
&__top {
62+
display: flex;
63+
align-items: center;
64+
gap: $size-gap-3;
65+
padding: $size-gap-4 $size-gap-4 $size-gap-3 $size-gap-4;
66+
background: var(--core-accent-bg, rgba(99, 102, 241, 0.08));
67+
border-bottom: 1px solid color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 15%, transparent);
68+
}
69+
70+
&__icon-wrap {
71+
flex-shrink: 0;
72+
width: 44px;
73+
height: 44px;
74+
border-radius: $size-radius-lg;
75+
background: color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 18%, var(--element-bg-soft));
76+
border: 1px solid color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 30%, transparent);
77+
display: flex;
78+
align-items: center;
79+
justify-content: center;
80+
color: var(--core-accent, var(--color-accent-500));
81+
transition: transform $motion-fast $easing-decelerate;
82+
}
83+
84+
&__top-info {
85+
flex: 1;
86+
min-width: 0;
87+
display: flex;
88+
flex-direction: column;
89+
gap: 4px;
90+
}
91+
92+
&__name {
93+
font-size: $font-size-base;
94+
font-weight: $font-weight-semibold;
95+
color: var(--color-text-primary);
96+
line-height: $line-height-tight;
97+
white-space: nowrap;
98+
overflow: hidden;
99+
text-overflow: ellipsis;
100+
}
101+
102+
&__role {
103+
display: inline-flex;
104+
align-items: center;
105+
gap: 4px;
106+
font-size: $font-size-xs;
107+
font-weight: $font-weight-medium;
108+
color: var(--core-accent, var(--color-accent-500));
109+
opacity: 0.9;
110+
}
111+
112+
// ── Body ─────────────────────────────────────────────────────────
113+
114+
&__body {
115+
flex: 1;
116+
padding: $size-gap-3 $size-gap-4 $size-gap-2 $size-gap-4;
117+
}
118+
119+
&__desc {
120+
margin: 0;
121+
font-size: $font-size-xs;
122+
color: var(--color-text-secondary);
123+
line-height: $line-height-relaxed;
124+
display: -webkit-box;
125+
-webkit-line-clamp: 3;
126+
-webkit-box-orient: vertical;
127+
overflow: hidden;
128+
word-break: break-word;
129+
}
130+
131+
// ── Footer ───────────────────────────────────────────────────────
132+
133+
&__footer {
134+
display: flex;
135+
align-items: center;
136+
justify-content: space-between;
137+
gap: $size-gap-2;
138+
padding: $size-gap-2 $size-gap-4 $size-gap-3 $size-gap-4;
139+
border-top: 1px dashed color-mix(in srgb, var(--core-accent, var(--color-accent-500)) 18%, var(--border-subtle));
140+
}
141+
142+
&__tag {
143+
display: inline-flex;
144+
align-items: center;
145+
gap: 5px;
146+
font-size: 10px;
147+
color: var(--color-text-muted);
148+
149+
strong {
150+
font-weight: $font-weight-semibold;
151+
color: var(--core-accent, var(--color-accent-500));
152+
opacity: 0.9;
153+
}
154+
}
155+
156+
&__meta {
157+
display: inline-flex;
158+
align-items: center;
159+
gap: $size-gap-2;
160+
}
161+
162+
&__meta-item {
163+
display: inline-flex;
164+
align-items: center;
165+
gap: 3px;
166+
font-size: 10px;
167+
color: var(--color-text-muted);
168+
}
169+
}
170+
171+
// ── Grid wrapper (3-col max for core section) ─────────────────────
172+
173+
.core-agents-grid {
174+
display: grid;
175+
grid-template-columns: repeat(3, 1fr);
176+
gap: $size-gap-4;
177+
width: 100%;
178+
}
179+
180+
// ── Animation ─────────────────────────────────────────────────────
181+
182+
@keyframes core-card-in {
183+
from {
184+
opacity: 0;
185+
transform: translateY(14px) scale(0.97);
186+
}
187+
188+
to {
189+
opacity: 1;
190+
transform: translateY(0) scale(1);
191+
}
192+
}
193+
194+
@media (prefers-reduced-motion: reduce) {
195+
.core-agent-card {
196+
animation: none;
197+
transition: none;
198+
}
199+
}
200+
201+
// ── Responsive ────────────────────────────────────────────────────
202+
203+
@media (max-width: 1080px) {
204+
.core-agents-grid {
205+
grid-template-columns: repeat(2, 1fr);
206+
}
207+
}
208+
209+
@media (max-width: 640px) {
210+
.core-agents-grid {
211+
grid-template-columns: 1fr;
212+
}
213+
}

0 commit comments

Comments
 (0)