Skip to content

Commit 397836d

Browse files
feat(shared): 提取 Trump 共享模块 + SettingsPage 拆分 (#219)
feat(shared): 提取 Trump 共享模块 + SettingsPage 拆分
2 parents e3fa099 + a732949 commit 397836d

66 files changed

Lines changed: 6012 additions & 2272 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/desktop/src/components/SettingsPage.tsx

Lines changed: 212 additions & 2272 deletions
Large diffs are not rendered by default.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { Bot } from 'lucide-react';
3+
import type { CustomAgentMarketItem } from '../sections/AgentMarketSection';
4+
import { formatTimestamp } from '../utils';
5+
import styles from '../../SettingsPage.module.css';
6+
7+
export default function AgentMarketCard({ agent }: { agent: CustomAgentMarketItem }) {
8+
const { t } = useTranslation();
9+
return (
10+
<div className={styles.profileCard}>
11+
<div className={styles.profileHeader}>
12+
<div className={styles.profileIcon}>
13+
<Bot size={17} />
14+
</div>
15+
<div>
16+
<strong>{agent.name}</strong>
17+
<span>{agent.systemPrompt || t('settings.marketProfileDefaultDesc')}</span>
18+
</div>
19+
<em className={`${styles.profileStatus} ${styles.profileStatus_available}`}>
20+
{t('settings.statusReady')}
21+
</em>
22+
</div>
23+
<div className={styles.profileMeta}>
24+
<span>{t('settings.marketCustomAgentId')}: {agent.id}</span>
25+
<span>{t('settings.marketAgentType')}: {agent.agentType}</span>
26+
<span>{t('settings.marketInstallSource')}: {agent.source}</span>
27+
<span>{t('settings.marketPublishStatus')}: {t('settings.statusReady')}</span>
28+
{agent.updatedAt ? <span>{t('settings.marketUpdatedAt')}: {formatTimestamp(agent.updatedAt)}</span> : null}
29+
</div>
30+
<div className={styles.profileMeta}>
31+
{agent.capabilities.length > 0 ? (
32+
agent.capabilities.map((name) => <span key={name}>{name}</span>)
33+
) : (
34+
<span>{t('settings.marketNoCapabilityTags')}</span>
35+
)}
36+
</div>
37+
</div>
38+
);
39+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { useTranslation } from 'react-i18next';
2+
import Switch from '../primitives/Switch';
3+
import SelectControl from '../primitives/SelectControl';
4+
import type { ReasoningEffortPreference } from '@/stores/modelSettingsStore';
5+
import styles from '../../SettingsPage.module.css';
6+
7+
const MODEL_OPTIONS = [
8+
['auto', 'Auto'],
9+
['claude-opus-4-7', 'claude-opus-4-7'],
10+
['claude-sonnet-4-6', 'claude-sonnet-4-6'],
11+
['claude-haiku-4-5', 'claude-haiku-4-5'],
12+
['gpt-5.5', 'gpt-5.5'],
13+
['glm-5.1', 'glm-5.1'],
14+
] as const;
15+
16+
const PROVIDER_OPTIONS = [
17+
['tokendance-relay', 'TokenDance Relay'],
18+
['anthropic', 'Anthropic'],
19+
['openai', 'OpenAI'],
20+
['cc-switch-local', 'cc-switch local'],
21+
] as const;
22+
23+
const REASONING_OPTIONS = [
24+
['low', 'Low'],
25+
['medium', 'Medium'],
26+
['high', 'High'],
27+
['max', 'Max'],
28+
] as const;
29+
30+
interface AliasMappingRowProps {
31+
alias: string;
32+
model: string;
33+
provider: string;
34+
reasoningEffort: ReasoningEffortPreference;
35+
enabled: boolean;
36+
onToggle: () => void;
37+
onModelChange: (model: string) => void;
38+
onProviderChange: (provider: string) => void;
39+
onReasoningChange: (reasoningEffort: ReasoningEffortPreference) => void;
40+
}
41+
42+
export default function AliasMappingRow({
43+
alias,
44+
model,
45+
provider,
46+
reasoningEffort,
47+
enabled,
48+
onToggle,
49+
onModelChange,
50+
onProviderChange,
51+
onReasoningChange,
52+
}: AliasMappingRowProps) {
53+
const { t } = useTranslation();
54+
return (
55+
<div className={styles.modelAliasRow}>
56+
<div className={styles.modelAliasHead}>
57+
<div>
58+
<strong>{alias}</strong>
59+
<span>{t('settings.modelAliasRoute', { model, provider })}</span>
60+
</div>
61+
<Switch checked={enabled} onChange={onToggle} />
62+
</div>
63+
<div className={styles.modelAliasControls}>
64+
<label>
65+
<span>{t('settings.modelAliasModel')}</span>
66+
<SelectControl
67+
value={model}
68+
options={MODEL_OPTIONS.filter(([value]) => value !== 'auto').map(([value, label]) => [value, label])}
69+
onChange={onModelChange}
70+
/>
71+
</label>
72+
<label>
73+
<span>{t('settings.modelAliasProvider')}</span>
74+
<SelectControl
75+
value={provider}
76+
options={PROVIDER_OPTIONS.map(([value, label]) => [value, label])}
77+
onChange={onProviderChange}
78+
/>
79+
</label>
80+
<label>
81+
<span>{t('settings.modelAliasReasoning')}</span>
82+
<SelectControl
83+
value={reasoningEffort}
84+
options={REASONING_OPTIONS.map(([value, label]) => [value, label])}
85+
onChange={(value) => onReasoningChange(value as ReasoningEffortPreference)}
86+
/>
87+
</label>
88+
</div>
89+
</div>
90+
);
91+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { UserCircle } from 'lucide-react';
3+
import type { FriendRequestInfo } from '@/api/hubClient';
4+
import { formatTimestamp } from '../utils';
5+
import styles from '../../SettingsPage.module.css';
6+
7+
export default function HubFriendRequestRow({ request }: { request: FriendRequestInfo }) {
8+
const { t } = useTranslation();
9+
return (
10+
<div className={styles.taskRow}>
11+
<div className={styles.connectionIcon}>
12+
<UserCircle size={17} />
13+
</div>
14+
<div className={styles.settingCopy}>
15+
<strong>{request.nickname || request.username || request.user_id}</strong>
16+
<span>{request.message || t('settings.onlineImFriendRequestNoMessage')}</span>
17+
<div className={styles.taskMeta}>
18+
<span>{t('settings.onlineImFriendRequests')}</span>
19+
<span>{request.user_id}</span>
20+
<span>{formatTimestamp(request.created_at)}</span>
21+
</div>
22+
</div>
23+
<span className={styles.statusPill}>{t('settings.readOnly')}</span>
24+
</div>
25+
);
26+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { Globe2 } from 'lucide-react';
3+
import type { HubNotification } from '@/api/hubClient';
4+
import { formatTimestamp, parseNotificationPayload } from '../utils';
5+
import styles from '../../SettingsPage.module.css';
6+
7+
export default function HubNotificationRow({ notification }: { notification: HubNotification }) {
8+
const { t } = useTranslation();
9+
const payload = parseNotificationPayload(notification.payload);
10+
const title = payload.title || payload.subject || notification.type || notification.id;
11+
const body =
12+
payload.content ||
13+
payload.message ||
14+
payload.text ||
15+
payload.body ||
16+
t('settings.onlineImNotificationNoBody');
17+
return (
18+
<div className={styles.taskRow}>
19+
<div className={styles.connectionIcon}>
20+
<Globe2 size={17} />
21+
</div>
22+
<div className={styles.settingCopy}>
23+
<strong>{title}</strong>
24+
<span>{body}</span>
25+
<div className={styles.taskMeta}>
26+
<span>{notification.read ? t('settings.onlineImNotificationRead') : t('settings.onlineImNotificationUnread')}</span>
27+
<span>{notification.type || t('settings.onlineImNotifications')}</span>
28+
<span>{formatTimestamp(notification.created_at)}</span>
29+
</div>
30+
</div>
31+
<span className={styles.statusPill}>{t('settings.readOnly')}</span>
32+
</div>
33+
);
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { MessageSquareText } from 'lucide-react';
3+
import type { Session } from '@/api/hubClient';
4+
import { shortId, formatTimestamp } from '../utils';
5+
import styles from '../../SettingsPage.module.css';
6+
7+
function sessionIdOfSettings(session: Session) {
8+
return session.session_id ?? session.id ?? 'session';
9+
}
10+
11+
export default function HubSessionRow({ session }: { session: Session }) {
12+
const { t } = useTranslation();
13+
const sessionId = sessionIdOfSettings(session);
14+
const timestamp = session.last_message_at ?? session.updated_at ?? session.created_at;
15+
return (
16+
<div className={styles.taskRow}>
17+
<div className={styles.connectionIcon}>
18+
<MessageSquareText size={17} />
19+
</div>
20+
<div className={styles.settingCopy}>
21+
<strong>{session.name || shortId(sessionId)}</strong>
22+
<span>{sessionId}</span>
23+
<div className={styles.taskMeta}>
24+
<span>{session.type}</span>
25+
<span>{t('settings.memberCount', { count: session.member_count ?? session.members?.length ?? 0 })}</span>
26+
<span>{formatTimestamp(timestamp)}</span>
27+
</div>
28+
</div>
29+
<span className={`${styles.statusPill} ${styles.statusPillOn}`}>
30+
{t('settings.enabled')}
31+
</span>
32+
</div>
33+
);
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { ClipboardList } from 'lucide-react';
3+
import type { AgentTask } from '@/stores/taskBridgeStore';
4+
import { isActiveBridgeTask, shortId } from '../utils';
5+
import styles from '../../SettingsPage.module.css';
6+
7+
export default function HubTaskRow({ task }: { task: AgentTask }) {
8+
const { t } = useTranslation();
9+
return (
10+
<div className={styles.taskRow}>
11+
<div className={styles.connectionIcon}>
12+
<ClipboardList size={17} />
13+
</div>
14+
<div className={styles.settingCopy}>
15+
<strong>{shortId(task.taskId)}</strong>
16+
<span>{task.prompt}</span>
17+
<div className={styles.taskMeta}>
18+
<span>{task.agentId}</span>
19+
<span>{task.runId ? shortId(task.runId) : t('settings.taskUnbound')}</span>
20+
</div>
21+
</div>
22+
<span className={`${styles.statusPill} ${isActiveBridgeTask(task) ? styles.statusPillOn : ''}`}>
23+
{t(`settings.taskStatus.${task.status}`, { defaultValue: task.status })}
24+
</span>
25+
</div>
26+
);
27+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { Bot } from 'lucide-react';
3+
import type { AgentInfo } from '@shared/types';
4+
import type { ResolvedRunModelSettings } from '@/stores/modelSettingsStore';
5+
import styles from '../../SettingsPage.module.css';
6+
7+
interface LocalAgentProfileCardProps {
8+
agent: AgentInfo;
9+
alias?: string;
10+
route: ResolvedRunModelSettings;
11+
edgeOnline: boolean;
12+
}
13+
14+
export default function LocalAgentProfileCard({ agent, alias, route, edgeOnline }: LocalAgentProfileCardProps) {
15+
const { t } = useTranslation();
16+
const profileReady = edgeOnline && agent.status === 'available';
17+
return (
18+
<div className={styles.profileCard}>
19+
<div className={styles.profileHeader}>
20+
<div className={styles.profileIcon}>
21+
<Bot size={17} />
22+
</div>
23+
<div>
24+
<strong>{t('settings.localProfileName', { runtime: agent.name })}</strong>
25+
<span>{t('settings.localProfileDesc')}</span>
26+
</div>
27+
<em className={`${styles.profileStatus} ${profileReady ? styles.profileStatus_available : styles.profileStatus_configuring}`}>
28+
{profileReady ? t('settings.enabled') : t('settings.notConfigured')}
29+
</em>
30+
</div>
31+
<div className={styles.profileMeta}>
32+
<span>{t('settings.profileRuntime')}: {agent.id}</span>
33+
<span>{t('settings.profileModel')}: {route.model ?? t('prompt.routeAuto')}</span>
34+
<span>{t('settings.modelAliasProvider')}: {route.provider ?? t('prompt.routeAuto')}</span>
35+
<span>{t('settings.modelAliasReasoning')}: {route.reasoningEffort ?? t('prompt.routeAuto')}</span>
36+
{alias ? <span>{t('settings.profileAlias')}: {alias}</span> : null}
37+
<span>{t('settings.executionTargets')}: {t('settings.targetLocalEdge')}</span>
38+
<span>{t('settings.profileConfigSource')}: AGENTS.md / memory / skills</span>
39+
</div>
40+
</div>
41+
);
42+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { Plug } from 'lucide-react';
3+
import type { AgentInfo } from '@shared/types';
4+
import styles from '../../SettingsPage.module.css';
5+
6+
export default function McpRuntimeCard({ agent }: { agent: AgentInfo }) {
7+
const { t } = useTranslation();
8+
const { mcpIntegration, permissionHooks, subAgentSpawn } = agent.capabilities;
9+
return (
10+
<div className={styles.profileCard}>
11+
<div className={styles.profileHeader}>
12+
<div className={styles.profileIcon}>
13+
<Plug size={17} />
14+
</div>
15+
<div>
16+
<strong>{agent.name}</strong>
17+
<span>{agent.description || t('settings.mcpRuntimeDefaultDesc')}</span>
18+
</div>
19+
<em className={`${styles.profileStatus} ${mcpIntegration ? styles.profileStatus_available : styles.profileStatus_configuring}`}>
20+
{mcpIntegration ? t('settings.statusReady') : t('settings.notConfigured')}
21+
</em>
22+
</div>
23+
<div className={styles.profileMeta}>
24+
<span>{t('settings.profileRuntime')}: {agent.id}</span>
25+
<span>{t('settings.mcpIntegration')}: {mcpIntegration ? t('settings.enabled') : t('settings.notConfigured')}</span>
26+
<span>{t('settings.mcpPermissionHooks')}: {permissionHooks ? t('settings.enabled') : t('settings.notConfigured')}</span>
27+
<span>{t('settings.mcpSubAgentSpawn')}: {subAgentSpawn ? t('settings.enabled') : t('settings.notConfigured')}</span>
28+
</div>
29+
</div>
30+
);
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { Code2 } from 'lucide-react';
3+
import type { ProjectSkill } from '../sections/SkillsSection';
4+
import styles from '../../SettingsPage.module.css';
5+
6+
export default function ProjectSkillCard({ skill }: { skill: ProjectSkill }) {
7+
const { t } = useTranslation();
8+
return (
9+
<div className={styles.profileCard}>
10+
<div className={styles.profileHeader}>
11+
<div className={styles.profileIcon}>
12+
<Code2 size={17} />
13+
</div>
14+
<div>
15+
<strong>{skill.title}</strong>
16+
<span>{t(skill.descriptionKey)}</span>
17+
</div>
18+
<em className={`${styles.profileStatus} ${skill.status === 'ready' ? styles.profileStatus_available : styles.profileStatus_configuring}`}>
19+
{skill.status === 'ready' ? t('settings.statusReady') : t('settings.statusInProgress')}
20+
</em>
21+
</div>
22+
<div className={styles.profileMeta}>
23+
<span>{t('settings.skillLocalRegistry')}: .agents/skills/{skill.id}</span>
24+
<span>{t('settings.skillScripts')}: {skill.hasScripts ? t('settings.enabled') : t('settings.notConfigured')}</span>
25+
<span>{t('settings.skillReferences')}: {skill.hasReferences ? t('settings.enabled') : t('settings.notConfigured')}</span>
26+
</div>
27+
</div>
28+
);
29+
}

0 commit comments

Comments
 (0)