|
1 | 1 | import { useState, useMemo, useCallback } from 'react'; |
2 | 2 | import { useTranslation } from 'react-i18next'; |
3 | | -import { Bot, ChevronDown, ChevronUp, Globe, Wrench } from 'lucide-react'; |
| 3 | +import { Bot, ChevronDown, ChevronUp } from 'lucide-react'; |
4 | 4 | import type { AgentInfo } from '@shared/types'; |
5 | 5 | import type { ResolvedRunModelSettings } from '@/stores/modelSettingsStore'; |
6 | 6 | import { |
@@ -45,6 +45,8 @@ interface LocalAgentProfileCardProps { |
45 | 45 | edgeOnline: boolean; |
46 | 46 | } |
47 | 47 |
|
| 48 | +type SelectOption = readonly [string, string]; |
| 49 | + |
48 | 50 | // ── Tool catalog ──────────────────────────────────── |
49 | 51 |
|
50 | 52 | const TOOL_CATALOG: Array<{ name: string; descriptionKey: string }> = [ |
@@ -158,6 +160,21 @@ function estimateTokens(text: string): number { |
158 | 160 | return Math.max(1, Math.round(cjk + ascii / 4)); |
159 | 161 | } |
160 | 162 |
|
| 163 | +function withCurrentOptions(options: readonly SelectOption[], currentValues: Array<string | undefined>): SelectOption[] { |
| 164 | + const next = [...options]; |
| 165 | + for (const value of currentValues) { |
| 166 | + const trimmed = value?.trim(); |
| 167 | + if (trimmed && !next.some(([optionValue]) => optionValue === trimmed)) { |
| 168 | + next.unshift([trimmed, trimmed]); |
| 169 | + } |
| 170 | + } |
| 171 | + return next; |
| 172 | +} |
| 173 | + |
| 174 | +function labelForOption(options: readonly SelectOption[], value: string): string { |
| 175 | + return options.find(([optionValue]) => optionValue === value)?.[1] ?? value; |
| 176 | +} |
| 177 | + |
161 | 178 | // ── Component ─────────────────────────────────────── |
162 | 179 |
|
163 | 180 | export default function LocalAgentProfileCard({ |
@@ -245,6 +262,24 @@ export default function LocalAgentProfileCard({ |
245 | 262 | }, []); |
246 | 263 |
|
247 | 264 | const enabledToolCount = profile.tools.filter((t) => t.enabled).length; |
| 265 | + const modelOptions = useMemo( |
| 266 | + () => withCurrentOptions(MODEL_OPTIONS.filter(([value]) => value !== 'auto'), [profile.modelOverride, route.model]), |
| 267 | + [profile.modelOverride, route.model], |
| 268 | + ); |
| 269 | + const providerOptions = useMemo( |
| 270 | + () => withCurrentOptions(PROVIDER_OPTIONS, [profile.providerOverride, route.provider]), |
| 271 | + [profile.providerOverride, route.provider], |
| 272 | + ); |
| 273 | + const reasoningOptions = useMemo( |
| 274 | + () => withCurrentOptions(REASONING_OPTIONS, [profile.reasoningOverride, route.reasoningEffort]), |
| 275 | + [profile.reasoningOverride, route.reasoningEffort], |
| 276 | + ); |
| 277 | + const activeModel = profile.modelOverride || route.model || ''; |
| 278 | + const activeProvider = profile.providerOverride || route.provider || ''; |
| 279 | + const hasProfileModelChoice = Boolean(profile.modelOverride || profile.providerOverride || profile.reasoningOverride); |
| 280 | + const autoLabel = t('prompt.routeAuto'); |
| 281 | + const modelLabel = activeModel ? labelForOption(modelOptions, activeModel) : autoLabel; |
| 282 | + const providerLabel = activeProvider ? labelForOption(providerOptions, activeProvider) : autoLabel; |
248 | 283 |
|
249 | 284 | return ( |
250 | 285 | <div className={styles.profileCard}> |
@@ -272,11 +307,10 @@ export default function LocalAgentProfileCard({ |
272 | 307 | </em> |
273 | 308 | </div> |
274 | 309 | <div className={styles.profileMeta}> |
275 | | - <span>{t('settings.profileRuntime')}: {agent.id}</span> |
276 | | - <span>{t('settings.profileModel')}: {route.model ?? t('prompt.routeAuto')}</span> |
277 | | - <span>{t('settings.modelAliasProvider')}: {route.provider ?? t('prompt.routeAuto')}</span> |
278 | | - {alias ? <span>{t('settings.profileAlias')}: {alias}</span> : null} |
279 | | - <span>{t('settings.executionTargets')}: {t('settings.targetLocalEdge')}</span> |
| 310 | + <span>{t('settings.agentProfileSummaryModel', { model: modelLabel })}</span> |
| 311 | + <span>{t('settings.agentProfileSummaryProvider', { provider: providerLabel })}</span> |
| 312 | + <span>{hasProfileModelChoice ? t('settings.agentProfileSummaryCustom') : t('settings.agentProfileSummaryDefault')}</span> |
| 313 | + <span>{t('settings.agentProfileSummaryTarget')}</span> |
280 | 314 | </div> |
281 | 315 | <div className={styles.profileExpandArrow}> |
282 | 316 | {expanded ? <ChevronUp size={15} /> : <ChevronDown size={15} />} |
@@ -372,33 +406,33 @@ export default function LocalAgentProfileCard({ |
372 | 406 | onChange={(e) => setProfile((prev) => ({ ...prev, modelOverride: e.target.value }))} |
373 | 407 | > |
374 | 408 | <option value="">{t('settings.agentProfileUseDefault', { defaultValue: 'Use default' })}</option> |
375 | | - {MODEL_OPTIONS.filter(([v]) => v !== 'auto').map(([value, label]) => ( |
| 409 | + {modelOptions.map(([value, label]) => ( |
376 | 410 | <option key={value} value={value}>{label}</option> |
377 | 411 | ))} |
378 | 412 | </select> |
379 | 413 | </label> |
380 | 414 | <label className={styles.profileEditorMiniLabel}> |
381 | | - <span>{t('settings.modelAliasProvider')}</span> |
| 415 | + <span>{t('settings.agentProfileProvider')}</span> |
382 | 416 | <select |
383 | 417 | className={styles.select} |
384 | 418 | value={profile.providerOverride} |
385 | 419 | onChange={(e) => setProfile((prev) => ({ ...prev, providerOverride: e.target.value }))} |
386 | 420 | > |
387 | 421 | <option value="">{t('settings.agentProfileUseDefault', { defaultValue: 'Use default' })}</option> |
388 | | - {PROVIDER_OPTIONS.map(([value, label]) => ( |
| 422 | + {providerOptions.map(([value, label]) => ( |
389 | 423 | <option key={value} value={value}>{label}</option> |
390 | 424 | ))} |
391 | 425 | </select> |
392 | 426 | </label> |
393 | 427 | <label className={styles.profileEditorMiniLabel}> |
394 | | - <span>{t('settings.modelAliasReasoning')}</span> |
| 428 | + <span>{t('settings.agentProfileReasoning')}</span> |
395 | 429 | <select |
396 | 430 | className={styles.select} |
397 | 431 | value={profile.reasoningOverride} |
398 | 432 | onChange={(e) => setProfile((prev) => ({ ...prev, reasoningOverride: e.target.value }))} |
399 | 433 | > |
400 | 434 | <option value="">{t('settings.agentProfileUseDefault', { defaultValue: 'Use default' })}</option> |
401 | | - {REASONING_OPTIONS.map(([value, label]) => ( |
| 435 | + {reasoningOptions.map(([value, label]) => ( |
402 | 436 | <option key={value} value={value}>{label}</option> |
403 | 437 | ))} |
404 | 438 | </select> |
@@ -555,7 +589,7 @@ export default function LocalAgentProfileCard({ |
555 | 589 | <div className={styles.profileEditorActions}> |
556 | 590 | <div className={styles.profileEditorActionsLeft}> |
557 | 591 | <span className={styles.profileEditorSource}> |
558 | | - {t('settings.agentProfileStorage', { defaultValue: 'Saved to localStorage' })}: {agentProfileKey(agent.id)} |
| 592 | + {t('settings.agentProfileStorage', { defaultValue: 'Saved on this device' })} |
559 | 593 | </span> |
560 | 594 | <button |
561 | 595 | type="button" |
|
0 commit comments