fix(ui): thème, textes et lisibilité restants de la revue
Les boutons suivent la couleur d’apparence, les libellés trop petits ou trop techniques sont clarifiés, et le catalogue des fournisseurs se met à jour tout seul. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -55,6 +55,16 @@ const typeConfig: Record<string, { icon: typeof Globe }> = {
|
||||
'task-extractor': { icon: ListChecks },
|
||||
}
|
||||
|
||||
function kebabToCamel(value: string) {
|
||||
return value.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase())
|
||||
}
|
||||
|
||||
function resolveStoredLabel(t: (key: string) => string, value: string) {
|
||||
if (!value.startsWith('agents.')) return value
|
||||
const translated = t(value)
|
||||
return translated !== value ? translated : value
|
||||
}
|
||||
|
||||
const frequencyKeys: Record<string, string> = {
|
||||
manual: 'agents.frequencies.manual',
|
||||
hourly: 'agents.frequencies.hourly',
|
||||
@@ -209,9 +219,9 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
|
||||
<Icon className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-[13px] font-bold text-foreground">{agent.name}</h4>
|
||||
<h4 className="text-[13px] font-bold text-foreground">{resolveStoredLabel(t, agent.name)}</h4>
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground opacity-60">
|
||||
{t(`agents.types.${agent.type || 'custom'}`)}
|
||||
{t(`agents.types.${kebabToCamel(agent.type || 'custom')}`)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -237,7 +247,7 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
|
||||
|
||||
{agent.description && (
|
||||
<p className="text-xs text-muted-foreground leading-relaxed line-clamp-3">
|
||||
{agent.description}
|
||||
{resolveStoredLabel(t, agent.description)}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@@ -63,14 +63,26 @@ const templateConfig = [
|
||||
type TemplateId = typeof templateConfig[number]['id']
|
||||
type CategoryId = 'all' | 'veille' | 'digest' | 'tools' | 'generate'
|
||||
|
||||
const CATEGORIES: { id: CategoryId; label: string }[] = [
|
||||
{ id: 'all', label: 'Tous' },
|
||||
{ id: 'veille', label: '📡 Veille' },
|
||||
{ id: 'digest', label: '📰 Digest' },
|
||||
{ id: 'tools', label: '🔧 Outils' },
|
||||
{ id: 'generate', label: '✨ Génération' },
|
||||
const CATEGORIES: { id: CategoryId; labelKey: string }[] = [
|
||||
{ id: 'all', labelKey: 'agents.templates.categoryAll' },
|
||||
{ id: 'veille', labelKey: 'agents.templates.categoryWatch' },
|
||||
{ id: 'digest', labelKey: 'agents.templates.categoryDigest' },
|
||||
{ id: 'tools', labelKey: 'agents.templates.categoryTools' },
|
||||
{ id: 'generate', labelKey: 'agents.templates.categoryGenerate' },
|
||||
]
|
||||
|
||||
const PREVIEW_COUNT = 3
|
||||
|
||||
function templateNameKey(id: string) {
|
||||
return `agents.templates.${id}.name`
|
||||
}
|
||||
|
||||
function hasTranslatedName(t: (key: string) => string, id: string) {
|
||||
const key = templateNameKey(id)
|
||||
const name = t(key)
|
||||
return Boolean(name.trim()) && name !== key
|
||||
}
|
||||
|
||||
const typeIcons: Record<string, typeof Globe> = {
|
||||
scraper: Globe,
|
||||
researcher: Search,
|
||||
@@ -98,6 +110,7 @@ export function AgentTemplates({ onInstalled, existingAgentNames }: AgentTemplat
|
||||
const { t } = useLanguage()
|
||||
const [installingId, setInstallingId] = useState<string | null>(null)
|
||||
const [activeCategory, setActiveCategory] = useState<CategoryId>('all')
|
||||
const [showAll, setShowAll] = useState(false)
|
||||
|
||||
const handleInstall = async (tpl: typeof templateConfig[number]) => {
|
||||
setInstallingId(tpl.id)
|
||||
@@ -142,55 +155,62 @@ export function AgentTemplates({ onInstalled, existingAgentNames }: AgentTemplat
|
||||
}
|
||||
}
|
||||
|
||||
const filtered = activeCategory === 'all'
|
||||
const filtered = (activeCategory === 'all'
|
||||
? templateConfig
|
||||
: templateConfig.filter((tpl) => tpl.category === activeCategory)
|
||||
).filter((tpl) => hasTranslatedName(t, tpl.id))
|
||||
|
||||
const visible = showAll ? filtered : filtered.slice(0, PREVIEW_COUNT)
|
||||
const hiddenCount = filtered.length - visible.length
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
{/* Category filter */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{CATEGORIES.map((cat) => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setActiveCategory(cat.id)}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setActiveCategory(cat.id)
|
||||
setShowAll(false)
|
||||
}}
|
||||
className={`px-3 py-1.5 rounded-full text-xs font-semibold transition-all border ${
|
||||
activeCategory === cat.id
|
||||
? 'bg-ink text-paper border-ink'
|
||||
: 'bg-paper text-muted-ink border-border/40 hover:border-ink/30'
|
||||
}`}
|
||||
>
|
||||
{cat.label}
|
||||
{t(cat.labelKey)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Template grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filtered.map(tpl => {
|
||||
{visible.map(tpl => {
|
||||
const Icon = templateIcons[tpl.id as TemplateId] ?? typeIcons[tpl.type] ?? Settings
|
||||
const isInstalling = installingId === tpl.id
|
||||
const nameKey = `agents.templates.${tpl.id}.name`
|
||||
const nameKey = templateNameKey(tpl.id)
|
||||
const descKey = `agents.templates.${tpl.id}.description`
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tpl.id}
|
||||
className="bg-card/40 border border-dashed border-border rounded-2xl p-6 group cursor-pointer hover:bg-card hover:border-foreground/20 transition-all"
|
||||
className="bg-card/40 border border-dashed border-border rounded-2xl p-6 hover:bg-card hover:border-foreground/20 transition-all"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-lg bg-muted flex items-center justify-center text-muted-foreground group-hover:bg-foreground group-hover:text-background mb-4 transition-all">
|
||||
<div className="w-8 h-8 rounded-lg bg-muted flex items-center justify-center text-muted-foreground mb-4">
|
||||
<Icon className="w-4 h-4" />
|
||||
</div>
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<h4 className="text-[13px] font-bold text-foreground">{t(nameKey)}</h4>
|
||||
{tpl.frequency !== 'manual' && (
|
||||
<span className="text-[10px] font-semibold text-concrete bg-border/20 rounded-full px-2 py-0.5 shrink-0">
|
||||
{tpl.frequency === 'daily' ? '📅 Quotidien' : '📆 Hebdo'}
|
||||
{t(`agents.frequencies.${tpl.frequency}`)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground leading-relaxed mb-4">{t(descKey)}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleInstall(tpl)}
|
||||
disabled={isInstalling}
|
||||
className="text-[11px] font-bold uppercase tracking-widest text-foreground hover:opacity-60 transition-opacity flex items-center gap-2 disabled:opacity-50"
|
||||
@@ -211,6 +231,25 @@ export function AgentTemplates({ onInstalled, existingAgentNames }: AgentTemplat
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{hiddenCount > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAll(true)}
|
||||
className="text-[12px] font-semibold text-foreground hover:opacity-70 transition-opacity"
|
||||
>
|
||||
{t('agents.templates.seeAll')}
|
||||
</button>
|
||||
)}
|
||||
{showAll && filtered.length > PREVIEW_COUNT && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAll(false)}
|
||||
className="text-[12px] font-semibold text-foreground hover:opacity-70 transition-opacity"
|
||||
>
|
||||
{t('agents.templates.showLess')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user