546 lines
15 KiB
TypeScript
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 }
|
|
]
|