546 lines
15 KiB
TypeScript

import {
Brain,
type IconComponent,
Lock,
MessageCircle,
Mic,
Monitor,
Moon,
Palette,
Sparkles,
Sun,
Wrench
} from '@/lib/icons'
import type { ThemeMode } from '@/themes/context'
import type { DesktopConfigSection } from './types'
import { defineFieldCopy } from './field-copy'
// Provider group definitions used to fold raw env-var names like
// ``XAI_API_KEY`` into a single "xAI" card with a friendly label, short
// description, and signup URL. Membership is determined by longest
// prefix match (see ``providerGroup`` in helpers.ts) so more specific
// prefixes (``MINIMAX_CN_``) correctly beat their general parents
// (``MINIMAX_``). New providers should be added here so they get their
// own card in Settings → Keys instead of being lumped into "Other".
interface ProviderPrefix {
prefix: string
name: string
/** Optional one-line tagline shown beneath the group name. */
description?: string
/** Optional canonical signup/console URL surfaced from the card header. */
docsUrl?: string
/** Lower numbers float to the top of the providers list. */
priority: number
}
export const EMPTY_SELECT_VALUE = '__hermes_empty__'
export const CONTROL_TEXT = 'text-xs'
export const PROVIDER_GROUPS: ProviderPrefix[] = [
{
prefix: 'NOUS_',
name: 'Nous Portal',
description: 'Hosted Hermes & Nous-trained models',
docsUrl: 'https://portal.nousresearch.com',
priority: 0
},
{
prefix: 'OPENROUTER_',
name: 'OpenRouter',
description: 'Aggregator for hundreds of frontier models',
docsUrl: 'https://openrouter.ai/keys',
priority: 1
},
{
prefix: 'ANTHROPIC_',
name: 'Anthropic',
description: 'Claude API access (Sonnet, Opus, Haiku)',
docsUrl: 'https://console.anthropic.com/settings/keys',
priority: 2
},
{
prefix: 'XAI_',
name: 'xAI',
description: 'Grok models (use OAuth for SuperGrok / Premium+)',
docsUrl: 'https://console.x.ai/',
priority: 3
},
{
prefix: 'GOOGLE_',
name: 'Gemini',
description: 'Google AI Studio (Gemini 1.5 / 2.0 / 2.5)',
docsUrl: 'https://aistudio.google.com/app/apikey',
priority: 4
},
{ prefix: 'GEMINI_', name: 'Gemini', priority: 4 },
{ prefix: 'HERMES_GEMINI_', name: 'Gemini', priority: 4 },
{
prefix: 'DEEPSEEK_',
name: 'DeepSeek',
description: 'Direct DeepSeek API (V3.x, R1)',
docsUrl: 'https://platform.deepseek.com/api_keys',
priority: 5
},
{
prefix: 'DASHSCOPE_',
name: 'DashScope (Qwen)',
description: 'Alibaba Cloud DashScope — Qwen and multi-vendor models',
docsUrl: 'https://modelstudio.console.alibabacloud.com/',
priority: 6
},
{ prefix: 'HERMES_QWEN_', name: 'DashScope (Qwen)', priority: 6 },
{
prefix: 'GLM_',
name: 'GLM / Z.AI',
description: 'Zhipu GLM-4.6 and Z.AI hosted endpoints',
docsUrl: 'https://z.ai/',
priority: 7
},
{ prefix: 'ZAI_', name: 'GLM / Z.AI', priority: 7 },
{ prefix: 'Z_AI_', name: 'GLM / Z.AI', priority: 7 },
{
prefix: 'KIMI_',
name: 'Kimi / Moonshot',
description: 'Moonshot Kimi K2 / coding endpoints',
docsUrl: 'https://platform.moonshot.cn/',
priority: 8
},
{
prefix: 'KIMI_CN_',
name: 'Kimi (China)',
description: 'Moonshot China endpoint',
docsUrl: 'https://platform.moonshot.cn/',
priority: 9
},
{
prefix: 'MINIMAX_',
name: 'MiniMax',
description: 'MiniMax-M2 and Hailuo international endpoints',
docsUrl: 'https://www.minimax.io/',
priority: 10
},
{
prefix: 'MINIMAX_CN_',
name: 'MiniMax (China)',
description: 'MiniMax mainland China endpoint',
docsUrl: 'https://www.minimaxi.com/',
priority: 11
},
{
prefix: 'HF_',
name: 'Hugging Face',
description: 'Inference Providers — 20+ open models via router.huggingface.co',
docsUrl: 'https://huggingface.co/settings/tokens',
priority: 12
},
{
prefix: 'OPENCODE_ZEN_',
name: 'OpenCode Zen',
description: 'Pay-as-you-go access to curated coding models',
docsUrl: 'https://opencode.ai/auth',
priority: 13
},
{
prefix: 'OPENCODE_GO_',
name: 'OpenCode Go',
description: '$10/month subscription for open coding models',
docsUrl: 'https://opencode.ai/auth',
priority: 14
},
{
prefix: 'NVIDIA_',
name: 'NVIDIA NIM',
description: 'build.nvidia.com or your own local NIM endpoint',
docsUrl: 'https://build.nvidia.com/',
priority: 15
},
{
prefix: 'OLLAMA_',
name: 'Ollama Cloud',
description: 'Cloud-hosted open models from ollama.com',
docsUrl: 'https://ollama.com/settings',
priority: 16
},
{
prefix: 'LM_',
name: 'LM Studio',
description: 'Local LM Studio server (OpenAI-compatible)',
docsUrl: 'https://lmstudio.ai/docs/local-server',
priority: 17
},
{
prefix: 'STEPFUN_',
name: 'StepFun',
description: 'StepFun Step Plan coding models',
docsUrl: 'https://platform.stepfun.com/',
priority: 18
},
{
prefix: 'XIAOMI_',
name: 'Xiaomi MiMo',
description: 'MiMo-V2.5 and Xiaomi proprietary models',
docsUrl: 'https://platform.xiaomimimo.com',
priority: 19
},
{
prefix: 'ARCEEAI_',
name: 'Arcee AI',
description: 'Arcee-hosted small + medium models',
docsUrl: 'https://chat.arcee.ai/',
priority: 20
},
{ prefix: 'ARCEE_', name: 'Arcee AI', priority: 20 },
{
prefix: 'GMI_',
name: 'GMI Cloud',
description: 'GMI Cloud GPU + model serving',
docsUrl: 'https://www.gmicloud.ai/',
priority: 21
},
{
prefix: 'AZURE_FOUNDRY_',
name: 'Azure Foundry',
description: 'Azure AI Foundry custom endpoints (OpenAI / Anthropic-compatible)',
docsUrl: 'https://ai.azure.com/',
priority: 22
},
{
prefix: 'AWS_',
name: 'AWS Bedrock',
description: 'Authenticate via AWS profile + region',
docsUrl: 'https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html',
priority: 23
}
]
export const BUILTIN_PERSONALITIES = [
'helpful',
'concise',
'technical',
'creative',
'teacher',
'kawaii',
'catgirl',
'pirate',
'shakespeare',
'surfer',
'noir',
'uwu',
'philosopher',
'hype'
]
// Schema-side select overrides for desktop-relevant enum fields whose
// backend schema only declares a string type.
export const ENUM_OPTIONS: Record<string, string[]> = {
'agent.image_input_mode': ['auto', 'native', 'text'],
'approvals.mode': ['manual', 'smart', 'off'],
'code_execution.mode': ['project', 'strict'],
'context.engine': ['compressor', 'default', 'custom'],
'delegation.reasoning_effort': ['', 'minimal', 'low', 'medium', 'high', 'xhigh'],
'memory.provider': ['', 'builtin', 'honcho'],
'stt.elevenlabs.model_id': ['scribe_v2', 'scribe_v1'],
'stt.local.model': ['tiny', 'base', 'small', 'medium', 'large-v3'],
'tts.openai.voice': ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'],
'updates.non_interactive_local_changes': ['stash', 'discard']
}
export const FIELD_LABELS: Record<string, string> = defineFieldCopy({
model: 'Default Model',
model_context_length: 'Context Window',
fallback_providers: 'Fallback Models',
toolsets: 'Enabled Toolsets',
timezone: 'Timezone',
display: {
personality: 'Personality',
show_reasoning: 'Reasoning Blocks'
},
agent: {
max_turns: 'Max Agent Steps',
image_input_mode: 'Image Attachments',
api_max_retries: 'API Retries',
service_tier: 'Service Tier',
tool_use_enforcement: 'Tool-Use Enforcement'
},
terminal: {
cwd: 'Working Directory',
backend: 'Execution Backend',
timeout: 'Command Timeout',
persistent_shell: 'Persistent Shell',
env_passthrough: 'Environment Passthrough'
},
file_read_max_chars: 'File Read Limit',
tool_output: {
max_bytes: 'Terminal Output Limit',
max_lines: 'File Page Limit',
max_line_length: 'Line Length Limit'
},
code_execution: {
mode: 'Code Execution Mode'
},
approvals: {
mode: 'Approval Mode',
timeout: 'Approval Timeout',
mcp_reload_confirm: 'Confirm MCP Reloads'
},
command_allowlist: 'Command Allowlist',
security: {
redact_secrets: 'Redact Secrets',
allow_private_urls: 'Allow Private URLs'
},
browser: {
allow_private_urls: 'Browser Private URLs',
auto_local_for_private_urls: 'Local Browser For Private URLs'
},
checkpoints: {
enabled: 'File Checkpoints',
max_snapshots: 'Checkpoint Limit'
},
voice: {
record_key: 'Voice Shortcut',
max_recording_seconds: 'Max Recording Length',
auto_tts: 'Read Responses Aloud'
},
stt: {
enabled: 'Speech To Text',
provider: 'Speech-To-Text Provider',
local: {
model: 'Local Transcription Model',
language: 'Transcription Language'
},
elevenlabs: {
model_id: 'ElevenLabs STT Model',
language_code: 'ElevenLabs Language',
tag_audio_events: 'Tag Audio Events',
diarize: 'Speaker Diarization'
}
},
tts: {
provider: 'Text-To-Speech Provider',
edge: {
voice: 'Edge Voice'
},
openai: {
model: 'OpenAI TTS Model',
voice: 'OpenAI Voice'
},
elevenlabs: {
voice_id: 'ElevenLabs Voice',
model_id: 'ElevenLabs Model'
}
},
memory: {
memory_enabled: 'Persistent Memory',
user_profile_enabled: 'User Profile',
memory_char_limit: 'Memory Budget',
user_char_limit: 'Profile Budget',
provider: 'Memory Provider'
},
context: {
engine: 'Context Engine'
},
compression: {
enabled: 'Auto-Compression',
threshold: 'Compression Threshold',
target_ratio: 'Compression Target',
protect_last_n: 'Protected Recent Messages'
},
delegation: {
model: 'Subagent Model',
provider: 'Subagent Provider',
max_iterations: 'Subagent Turn Limit',
max_concurrent_children: 'Parallel Subagents',
child_timeout_seconds: 'Subagent Timeout',
reasoning_effort: 'Subagent Reasoning Effort'
},
updates: {
non_interactive_local_changes: 'In-App Update Local Changes'
}
})
export const FIELD_DESCRIPTIONS: Record<string, string> = defineFieldCopy({
model: 'Used for new chats unless you pick a different model in the composer.',
model_context_length: "Leave at 0 to use the selected model's detected context window.",
fallback_providers: 'Backup provider:model entries to try if the default model fails.',
display: {
personality: 'Default assistant style for new sessions.',
show_reasoning: 'Show reasoning sections when the backend provides them.'
},
timezone: 'Used when Hermes needs local time context. Blank uses the system timezone.',
agent: {
image_input_mode: 'Controls how image attachments are sent to the model.',
max_turns: 'Upper bound for tool-calling turns before Hermes stops a run.'
},
terminal: {
cwd: 'Default project folder for tool and terminal work.',
persistent_shell: 'Keep shell state between commands when the backend supports it.',
env_passthrough: 'Environment variables to pass into tool execution.'
},
code_execution: {
mode: 'How strictly code execution is scoped to the current project.'
},
file_read_max_chars: 'Maximum characters Hermes can read from one file request.',
approvals: {
mode: 'How Hermes handles commands that need explicit approval.',
timeout: 'How long approval prompts wait before timing out.'
},
security: {
redact_secrets: 'Hide detected secrets from model-visible content when possible.'
},
checkpoints: {
enabled: 'Create rollback snapshots before file edits.'
},
memory: {
memory_enabled: 'Save durable memories that can help future sessions.',
user_profile_enabled: 'Maintain a compact profile of user preferences.'
},
context: {
engine: 'Strategy for managing long conversations near the context limit.'
},
compression: {
enabled: 'Summarize older context when conversations get large.'
},
voice: {
auto_tts: 'Automatically speak assistant responses.'
},
stt: {
enabled: 'Enable local or provider-backed speech transcription.',
elevenlabs: {
language_code: 'Optional ISO-639-3 language code. Blank lets ElevenLabs auto-detect.'
}
},
updates: {
non_interactive_local_changes:
'When Hermes updates itself from the app (no terminal prompt), keep local source edits (stash) or throw them away (discard). Terminal updates always ask.'
}
})
// Curated desktop config surface: only fields a user might tune from the app.
export const SECTIONS: DesktopConfigSection[] = [
{
id: 'model',
label: 'Model',
icon: Sparkles,
keys: ['model_context_length', 'fallback_providers']
},
{
id: 'chat',
label: 'Chat',
icon: MessageCircle,
keys: ['display.personality', 'timezone', 'display.show_reasoning', 'agent.image_input_mode']
},
{
id: 'appearance',
label: 'Appearance',
icon: Palette,
keys: []
},
{
id: 'workspace',
label: 'Workspace',
icon: Monitor,
keys: [
'terminal.cwd',
'code_execution.mode',
'terminal.persistent_shell',
'terminal.env_passthrough',
'file_read_max_chars'
]
},
{
id: 'safety',
label: 'Safety',
icon: Lock,
keys: [
'approvals.mode',
'approvals.timeout',
'approvals.mcp_reload_confirm',
'command_allowlist',
'security.redact_secrets',
'security.allow_private_urls',
'browser.allow_private_urls',
'browser.auto_local_for_private_urls',
'checkpoints.enabled'
]
},
{
id: 'memory',
label: 'Memory & Context',
icon: Brain,
keys: [
'memory.memory_enabled',
'memory.user_profile_enabled',
'memory.memory_char_limit',
'memory.user_char_limit',
'memory.provider',
'context.engine',
'compression.enabled',
'compression.threshold',
'compression.target_ratio',
'compression.protect_last_n'
]
},
{
id: 'voice',
label: 'Voice',
icon: Mic,
keys: [
'tts.provider',
'stt.enabled',
'stt.provider',
'voice.auto_tts',
'tts.edge.voice',
'tts.openai.model',
'tts.openai.voice',
'tts.elevenlabs.voice_id',
'tts.elevenlabs.model_id',
'stt.local.model',
'stt.local.language',
'stt.elevenlabs.model_id',
'stt.elevenlabs.language_code',
'stt.elevenlabs.tag_audio_events',
'stt.elevenlabs.diarize',
'voice.record_key',
'voice.max_recording_seconds'
]
},
{
id: 'advanced',
label: 'Advanced',
icon: Wrench,
keys: [
'toolsets',
'terminal.backend',
'terminal.timeout',
'tool_output.max_bytes',
'tool_output.max_lines',
'tool_output.max_line_length',
'checkpoints.max_snapshots',
'agent.max_turns',
'agent.api_max_retries',
'agent.service_tier',
'agent.tool_use_enforcement',
'delegation.model',
'delegation.provider',
'delegation.max_iterations',
'delegation.max_concurrent_children',
'delegation.child_timeout_seconds',
'delegation.reasoning_effort',
'updates.non_interactive_local_changes'
]
}
]
export interface ModeOption {
id: ThemeMode
label: string
icon: IconComponent
}
export const MODE_OPTIONS: ModeOption[] = [
{ id: 'light', label: 'Light', icon: Sun },
{ id: 'dark', label: 'Dark', icon: Moon },
{ id: 'system', label: 'System', icon: Monitor }
]