Skip to content
Closed
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
12 changes: 8 additions & 4 deletions app/desktop/src/components/settings/cards/AgentMarketCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Bot, Pencil, Trash2, Globe2 } from 'lucide-react';
import type { CustomAgentMarketItem } from '../sections/AgentMarketSection';
import { formatTimestamp } from '../utils';
import { formatMarketCapability, formatMarketSource, formatTimestamp } from '../utils';
import styles from '../primitives/primitives.module.css';

export default function AgentMarketCard({
Expand All @@ -17,6 +17,10 @@ export default function AgentMarketCard({
}) {
const { t } = useTranslation();
const isLocalDraft = agent.source === 'local';
const agentTypeLabel = t(
`settings.agentCreator.type${agent.agentType.charAt(0).toUpperCase()}${agent.agentType.slice(1)}`,
{ defaultValue: agent.agentType },
);
return (
<div className={styles.profileCard}>
<div className={styles.profileHeader}>
Expand All @@ -33,14 +37,14 @@ export default function AgentMarketCard({
</div>
<div className={styles.profileMeta}>
<span>{t('settings.marketCustomAgentId')}: {agent.id}</span>
<span>{t('settings.marketAgentType')}: {agent.agentType}</span>
<span>{t('settings.marketInstallSource')}: {agent.source}</span>
<span>{t('settings.marketAgentType')}: {agentTypeLabel}</span>
<span>{t('settings.marketInstallSource')}: {formatMarketSource(agent.source, t)}</span>
<span>{t('settings.marketPublishStatus')}: {t('settings.statusReady')}</span>
{agent.updatedAt ? <span>{t('settings.marketUpdatedAt')}: {formatTimestamp(agent.updatedAt)}</span> : null}
</div>
<div className={styles.profileMeta}>
{agent.capabilities.length > 0 ? (
agent.capabilities.map((name) => <span key={name}>{name}</span>)
agent.capabilities.map((name) => <span key={name}>{formatMarketCapability(name, t)}</span>)
) : (
<span>{t('settings.marketNoCapabilityTags')}</span>
)}
Expand Down
18 changes: 9 additions & 9 deletions app/desktop/src/components/settings/cards/CustomAgentCreator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ export interface CustomAgentDraft {
const STORAGE_KEY = 'agenthub-settings.customAgentDrafts';

const VARIABLES = [
{ key: '$user', label: 'Current user name' },
{ key: '$project', label: 'Project root path' },
{ key: '$date', label: 'Current date/time' },
{ key: '$files', label: 'Currently open files' },
{ key: '$language', label: 'Primary project language' },
{ key: '$os', label: 'Operating system' },
{ key: '$user', labelKey: 'settings.agentCreator.variableUser' },
{ key: '$project', labelKey: 'settings.agentCreator.variableProject' },
{ key: '$date', labelKey: 'settings.agentCreator.variableDate' },
{ key: '$files', labelKey: 'settings.agentCreator.variableFiles' },
{ key: '$language', labelKey: 'settings.agentCreator.variableLanguage' },
{ key: '$os', labelKey: 'settings.agentCreator.variableOs' },
] as const;

const AGENT_TYPES = [
Expand Down Expand Up @@ -68,7 +68,7 @@ const MODEL_CHOICES = [
{ model: 'deepseek-v4-pro', provider: 'tokendance-gateway', label: 'DeepSeek V4 Pro (Opus)' },
{ model: 'deepseek-v4-flash', provider: 'tokendance-gateway', label: 'DeepSeek V4 Flash (Sonnet)' },
{ model: 'glm-5.1', provider: 'tokendance-gateway', label: 'GLM 5.1 (Haiku)' },
{ model: 'custom', provider: '', label: 'Custom model' },
{ model: 'custom', provider: '', labelKey: 'settings.agentCreator.customModel' },
] as const;

interface WizardStep {
Expand Down Expand Up @@ -495,7 +495,7 @@ export default function CustomAgentCreator({
<label>{t('settings.agentCreator.promptTemplate')}</label>
<div className={styles.creatorVariableBar}>
{VARIABLES.map((v) => (
<button key={v.key} type="button" className={styles.creatorVariableChip} onClick={() => insertVariable(v.key)} title={v.label}>
<button key={v.key} type="button" className={styles.creatorVariableChip} onClick={() => insertVariable(v.key)} title={t(v.labelKey)}>
<Variable size={11} />{v.key}
</button>
))}
Expand Down Expand Up @@ -530,7 +530,7 @@ export default function CustomAgentCreator({
}}
>
{MODEL_CHOICES.map((m) => (
<option key={m.model} value={`${m.provider}:${m.model}`}>{m.label}</option>
<option key={m.model} value={`${m.provider}:${m.model}`}>{'labelKey' in m ? t(m.labelKey) : m.label}</option>
))}
</select>
</div>
Expand Down
3 changes: 2 additions & 1 deletion app/desktop/src/components/settings/cards/McpRuntimeCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Plug } from 'lucide-react';
import type { AgentInfo } from '@shared/types';
import { formatRuntimeDescription } from '../utils';
import styles from '../primitives/primitives.module.css';

export default function McpRuntimeCard({ agent }: { agent: AgentInfo }) {
Expand All @@ -14,7 +15,7 @@ export default function McpRuntimeCard({ agent }: { agent: AgentInfo }) {
</div>
<div>
<strong>{agent.name}</strong>
<span>{agent.description || t('settings.mcpRuntimeDefaultDesc')}</span>
<span>{formatRuntimeDescription(agent, t, 'settings.mcpRuntimeDefaultDesc')}</span>
</div>
<em className={`${styles.profileStatus} ${mcpIntegration ? styles.profileStatus_available : styles.profileStatus_configuring}`}>
{mcpIntegration ? t('settings.statusReady') : t('settings.notConfigured')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ export default function PublishAgentModal({
</div>
<div className={styles.marketAgentMeta}>
<span><Cpu size={11} />{agent.model}</span>
<span><Wrench size={11} />{agent.tools.length} tools</span>
<span><Wrench size={11} />{t('settings.marketPublish.toolCount', { count: agent.tools.length })}</span>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Bot } from 'lucide-react';
import type { AgentInfo } from '@shared/types';
import { formatRuntimeDescription } from '../utils';
import styles from '../primitives/primitives.module.css';

export default function RuntimeInventoryCard({ agent }: { agent: AgentInfo }) {
Expand All @@ -13,7 +14,7 @@ export default function RuntimeInventoryCard({ agent }: { agent: AgentInfo }) {
</div>
<div>
<strong>{agent.name}</strong>
<span>{agent.description || t('settings.runtimeDefaultDesc')}</span>
<span>{formatRuntimeDescription(agent, t)}</span>
</div>
<em className={`${styles.profileStatus} ${styles[`profileStatus_${agent.status}`]}`}>
{t(`agent.status.${agent.status}`)}
Expand Down
109 changes: 79 additions & 30 deletions app/desktop/src/components/settings/sections/AgentMarketSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@ import CustomAgentCreator, { readCustomAgentDrafts } from '../cards/CustomAgentC
import PublishAgentModal from '../cards/PublishAgentModal';
import type { PublishAgentPayload } from '../cards/PublishAgentModal';
import { deleteCustomAgent, loadCustomAgents } from '../agentCreation/agentStore';
import { agentTemplates, capabilityLabels } from '../agentCreation/agentTemplates';
import { agentTemplates } from '../agentCreation/agentTemplates';
import type { AgentTemplate } from '../agentCreation/agentCreationTypes';
import { countAgentCapabilities, statusLabelFromQuery, readUnknownString, readUnknownArray } from '../utils';
import {
countAgentCapabilities,
formatMarketCapability,
formatMarketTool,
statusLabelFromQuery,
readUnknownString,
readUnknownArray,
} from '../utils';
import styles from '../primitives/primitives.module.css';

export interface CustomAgentMarketItem {
Expand Down Expand Up @@ -54,6 +61,15 @@ export interface CommunityAgentItem {
iconUrl?: string;
}

type TranslateFn = (key: string, options?: Record<string, unknown>) => string;

interface CommunityAgentView extends CommunityAgentItem {
displayName: string;
displayDescription: string;
displaySystemPrompt: string;
displayCapabilities: Array<{ id: string; label: string }>;
}

function normalizeCustomAgent(raw: Record<string, unknown>): CustomAgentMarketItem {
const id = readUnknownString(raw.id) ?? readUnknownString(raw.agent_id) ?? readUnknownString(raw.custom_agent_id) ?? 'custom-agent';
const capabilityTags = readUnknownArray(raw.capability_tags);
Expand Down Expand Up @@ -184,6 +200,26 @@ const ALL_CAPABILITY_TAGS = Array.from(
new Set(MOCK_COMMUNITY_AGENTS.flatMap((a) => a.capabilities)),
).sort();

function localizeCommunityAgent(agent: CommunityAgentItem, t: TranslateFn): CommunityAgentView {
return {
...agent,
displayName: t(`settings.marketAgent.${agent.id}.name`, { defaultValue: agent.name }),
displayDescription: t(`settings.marketAgent.${agent.id}.description`, { defaultValue: agent.description }),
displaySystemPrompt: t(`settings.marketAgent.${agent.id}.systemPrompt`, { defaultValue: agent.systemPrompt }),
displayCapabilities: agent.capabilities.map((id) => ({ id, label: formatMarketCapability(id, t) })),
};
}

function localizeAgentTemplate(template: AgentTemplate, t: TranslateFn): AgentTemplate {
return {
...template,
name: t(`settings.agentTemplate.${template.id}.name`, { defaultValue: template.name }),
category: t(`settings.agentTemplateCategory.${template.category}`, { defaultValue: template.category }),
description: t(`settings.agentTemplate.${template.id}.description`, { defaultValue: template.description }),
systemPrompt: t(`settings.agentTemplate.${template.id}.systemPrompt`, { defaultValue: template.systemPrompt }),
};
}

const INSTALLED_AGENTS_STORAGE_KEY = 'agenthub-settings.installedCommunityAgents';

function readInstalledCommunityAgentIds(): string[] {
Expand Down Expand Up @@ -231,7 +267,7 @@ export default function AgentMarketSection({
const [searchQuery, setSearchQuery] = useState('');
const [selectedCapabilityTags, setSelectedCapabilityTags] = useState<string[]>([]);
const [showTagFilter, setShowTagFilter] = useState(false);
const [detailAgent, setDetailAgent] = useState<CommunityAgentItem | null>(null);
const [detailAgent, setDetailAgent] = useState<CommunityAgentView | null>(null);
const [installedIds, setInstalledIds] = useState<string[]>(readInstalledCommunityAgentIds);
const [showCreator, setShowCreator] = useState(false);
const [templateToUse, setTemplateToUse] = useState<AgentTemplate | null>(null);
Expand All @@ -248,6 +284,16 @@ export default function AgentMarketSection({
return readCustomAgentDrafts();
}, [localDraftsVersion]);

const communityAgentViews = useMemo(
() => MOCK_COMMUNITY_AGENTS.map((agent) => localizeCommunityAgent(agent, t)),
[t],
);

const localizedAgentTemplates = useMemo(
() => agentTemplates.map((template) => localizeAgentTemplate(template, t)),
[t],
);

const customAgents = rawAgents.map(normalizeCustomAgent);
const marketPublishReady = customAgents.length + localCustomAgents.length;
const marketCapabilityCount = countAgentCapabilities(agents as unknown as { capabilities: Record<string, boolean | undefined> }[]);
Expand All @@ -259,15 +305,18 @@ export default function AgentMarketSection({
// ── Browse tab: filter community agents ──
const filteredCommunityAgents = useMemo(() => {
if (!hubSessionActive) return [];
let results = MOCK_COMMUNITY_AGENTS;
let results = communityAgentViews;
const q = searchQuery.trim().toLowerCase();
if (q) {
results = results.filter((a) =>
a.name.toLowerCase().includes(q) ||
a.displayName.toLowerCase().includes(q) ||
a.description.toLowerCase().includes(q) ||
a.displayDescription.toLowerCase().includes(q) ||
a.author.toLowerCase().includes(q) ||
a.agentType.toLowerCase().includes(q) ||
a.capabilities.some((c) => c.toLowerCase().includes(q)),
a.capabilities.some((c) => c.toLowerCase().includes(q)) ||
a.displayCapabilities.some((c) => c.label.toLowerCase().includes(q)),
);
}
if (selectedCapabilityTags.length > 0) {
Expand All @@ -276,13 +325,13 @@ export default function AgentMarketSection({
);
}
return results;
}, [hubSessionActive, searchQuery, selectedCapabilityTags]);
}, [communityAgentViews, hubSessionActive, searchQuery, selectedCapabilityTags]);

const installedCommunityAgents = useMemo(() => {
return MOCK_COMMUNITY_AGENTS.filter((a) => installedIds.includes(a.id));
}, [installedIds]);
return communityAgentViews.filter((a) => installedIds.includes(a.id));
}, [communityAgentViews, installedIds]);

const handleInstall = useCallback((agent: CommunityAgentItem) => {
const handleInstall = useCallback((agent: CommunityAgentView) => {
const added = toggleInstalledCommunityAgent(agent.id);
setInstalledIds((prev) => {
if (added) return [...prev, agent.id];
Expand All @@ -296,9 +345,9 @@ export default function AgentMarketSection({
if (existingIdx < 0) {
const item: CustomAgentMarketItem = {
id: agent.id,
name: agent.name,
name: agent.displayName,
agentType: agent.agentType,
systemPrompt: agent.systemPrompt,
systemPrompt: agent.displaySystemPrompt,
capabilities: agent.capabilities,
source: 'hub-community',
updatedAt: now,
Expand Down Expand Up @@ -538,7 +587,7 @@ export default function AgentMarketSection({
onClick={() => handleToggleTag(tag)}
>
<Tag size={11} />
{tag}
{formatMarketCapability(tag, t)}
</button>
))}
</div>
Expand All @@ -561,7 +610,7 @@ export default function AgentMarketSection({
</button>
</div>
<div className={styles.templateGrid}>
{agentTemplates.map((tmpl) => {
{localizedAgentTemplates.map((tmpl) => {
const toolCount = Object.entries(tmpl.capabilities).filter(([, v]) => v).length;
return (
<div
Expand All @@ -580,7 +629,7 @@ export default function AgentMarketSection({
<div className={styles.templateCardBottom}>
<div className={styles.templateCardTools}>
{Object.entries(tmpl.capabilities).filter(([, v]) => v).slice(0, 3).map(([key]) => (
<span key={key} className={styles.templateCardToolBadge}>{capabilityLabels[key] ?? key}</span>
<span key={key} className={styles.templateCardToolBadge}>{formatMarketCapability(key, t)}</span>
))}
{toolCount > 3 && (
<span className={styles.templateCardToolBadge}>+{toolCount - 3}</span>
Expand Down Expand Up @@ -622,7 +671,7 @@ export default function AgentMarketSection({
<Bot size={20} />
</div>
<div className={styles.marketAgentTitle}>
<strong>{agent.name}</strong>
<strong>{agent.displayName}</strong>
<span>{agent.author}</span>
</div>
<div className={styles.marketAgentRating}>
Expand All @@ -631,20 +680,20 @@ export default function AgentMarketSection({
<small>{agent.installs.toLocaleString()}</small>
</div>
</div>
<p className={styles.marketAgentDesc}>{agent.description}</p>
<p className={styles.marketAgentDesc}>{agent.displayDescription}</p>
<div className={styles.marketAgentTags}>
<span className={styles.marketAgentTypeTag}>
<SlidersHorizontal size={10} />
{t(`settings.agentCreator.type${agent.agentType.charAt(0).toUpperCase()}${agent.agentType.slice(1)}`, { defaultValue: agent.agentType })}
</span>
{agent.capabilities.slice(0, 3).map((cap) => (
<span key={cap} className={styles.marketAgentCapTag}>
{agent.displayCapabilities.slice(0, 3).map((cap) => (
<span key={cap.id} className={styles.marketAgentCapTag}>
<Tag size={10} />
{cap}
{cap.label}
</span>
))}
{agent.capabilities.length > 3 && (
<span className={styles.marketAgentCapTag}>+{agent.capabilities.length - 3}</span>
{agent.displayCapabilities.length > 3 && (
<span className={styles.marketAgentCapTag}>+{agent.displayCapabilities.length - 3}</span>
)}
</div>
<div className={styles.marketAgentMeta}>
Expand Down Expand Up @@ -740,11 +789,11 @@ export default function AgentMarketSection({
<Download size={20} />
</div>
<div className={styles.marketAgentTitle}>
<strong>{agent.name}</strong>
<strong>{agent.displayName}</strong>
<span>{agent.author}</span>
</div>
</div>
<p className={styles.marketAgentDesc}>{agent.description}</p>
<p className={styles.marketAgentDesc}>{agent.displayDescription}</p>
<div className={styles.marketAgentActions}>
<button
type="button"
Expand Down Expand Up @@ -833,7 +882,7 @@ export default function AgentMarketSection({
<Bot size={20} />
</div>
<div>
<h2>{detailAgent.name}</h2>
<h2>{detailAgent.displayName}</h2>
<span>{t('settings.authorsBy', { author: detailAgent.author })}</span>
</div>
</div>
Expand Down Expand Up @@ -868,12 +917,12 @@ export default function AgentMarketSection({

<div className={styles.marketDetailSection}>
<strong>{t('settings.marketDescription')}</strong>
<p>{detailAgent.description}</p>
<p>{detailAgent.displayDescription}</p>
</div>

<div className={styles.marketDetailSection}>
<strong>{t('settings.agentCreator.promptTemplate')}</strong>
<pre className={styles.marketDetailPrompt}>{detailAgent.systemPrompt}</pre>
<pre className={styles.marketDetailPrompt}>{detailAgent.displaySystemPrompt}</pre>
</div>

<div className={styles.marketDetailSection}>
Expand All @@ -882,7 +931,7 @@ export default function AgentMarketSection({
{detailAgent.tools.map((tool) => (
<span key={tool} className={styles.marketDetailChip}>
<Wrench size={11} />
{tool}
{formatMarketTool(tool, t)}
</span>
))}
</div>
Expand All @@ -891,10 +940,10 @@ export default function AgentMarketSection({
<div className={styles.marketDetailSection}>
<strong>{t('settings.agentCapabilityTags')}</strong>
<div className={styles.marketDetailTags}>
{detailAgent.capabilities.map((cap) => (
<span key={cap} className={styles.marketDetailChip}>
{detailAgent.displayCapabilities.map((cap) => (
<span key={cap.id} className={styles.marketDetailChip}>
<Tag size={11} />
{cap}
{cap.label}
</span>
))}
</div>
Expand Down
Loading
Loading