feat: added i18n support; added Simplified Chinese support
This commit is contained in:
@@ -2,6 +2,8 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { ConfigManager } from '../../../core/config/ConfigManager';
|
||||
import { LocalLLMModelManager, type LocalLLMModelState } from '../../../util/localLLMModelManager';
|
||||
import { LocalSeparatorModelCache } from '../../../util/local-separator/modelCache';
|
||||
import { useI18n } from '../../../i18n/useI18n';
|
||||
import type { LanguageSetting } from '../../../i18n/types';
|
||||
import {
|
||||
formatLocalLLMContextLength,
|
||||
LOCAL_LLM_CONTEXT_LENGTH_OPTIONS,
|
||||
@@ -18,6 +20,8 @@ import {
|
||||
} from '../../../util/local-separator/config';
|
||||
|
||||
const GeneralSettings: React.FC = () => {
|
||||
const { t, setLanguageSetting } = useI18n();
|
||||
const [language, setLanguage] = useState<LanguageSetting>('auto');
|
||||
const [llmProvider, setLlmProvider] = useState<string>(LOCAL_LLM_PROVIDER_KEY);
|
||||
const [openaiKey, setOpenaiKey] = useState<string>('');
|
||||
const [openaiModel, setOpenaiModel] = useState<string>('');
|
||||
@@ -90,6 +94,7 @@ const GeneralSettings: React.FC = () => {
|
||||
await configManager.initialize();
|
||||
}
|
||||
|
||||
setLanguage(((configManager.get('general.language') as LanguageSetting | undefined) ?? 'auto'));
|
||||
setLlmProvider((configManager.get('general.llm_provider') as string) || LOCAL_LLM_PROVIDER_KEY);
|
||||
setOpenaiKey((configManager.get('general.openai.api_key') as string) || '');
|
||||
setOpenaiModel((configManager.get('general.openai.model') as string) || '');
|
||||
@@ -153,6 +158,15 @@ const GeneralSettings: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleLanguageChange = async (value: LanguageSetting) => {
|
||||
setLanguage(value);
|
||||
try {
|
||||
await setLanguageSetting(value);
|
||||
} catch (error) {
|
||||
console.error('Failed to save language:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenaiKeyChange = (value: string) => {
|
||||
setOpenaiKey(value);
|
||||
debouncedSave('general.openai.api_key', value);
|
||||
@@ -322,45 +336,66 @@ const GeneralSettings: React.FC = () => {
|
||||
return (
|
||||
<div className="settings-section">
|
||||
<div className="settings-section-header">
|
||||
<h3>General</h3>
|
||||
<h3>{t('settings.general.title')}</h3>
|
||||
</div>
|
||||
|
||||
<div className="settings-section-content">
|
||||
<div className="settings-group">
|
||||
<h4>LLM Provider</h4>
|
||||
<div className="settings-item">
|
||||
<label className="settings-label" htmlFor="general-language-select">
|
||||
{t('settings.general.language.label')}
|
||||
</label>
|
||||
<select
|
||||
id="general-language-select"
|
||||
className="settings-select"
|
||||
value={language}
|
||||
onChange={(e) => void handleLanguageChange(e.target.value as LanguageSetting)}
|
||||
>
|
||||
<option value="auto">{t('settings.general.language.auto')}</option>
|
||||
<option value="en_us">{t('settings.general.language.en_us')}</option>
|
||||
<option value="zh_cn">{t('settings.general.language.zh_cn')}</option>
|
||||
</select>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
{t('settings.general.language.help')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-group">
|
||||
<h4>{t('settings.general.llmProvider.section')}</h4>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
LLM Provider
|
||||
{t('settings.general.llmProvider.label')}
|
||||
</label>
|
||||
<select
|
||||
className="settings-select"
|
||||
value={llmProvider}
|
||||
onChange={(e) => handleLlmProviderChange(e.target.value)}
|
||||
>
|
||||
<option value={LOCAL_LLM_PROVIDER_KEY}>Local LLM (Browser)</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value={LOCAL_LLM_PROVIDER_KEY}>{t('settings.general.llmProvider.local')}</option>
|
||||
<option value="openai">{t('settings.general.llmProvider.openai')}</option>
|
||||
{/* <option value="gemini">Gemini</option>
|
||||
<option value="claude">Claude</option> */}
|
||||
<option value="claude_openrouter">Claude (via OpenRouter)</option>
|
||||
<option value="openai_compatible">OpenAI Compatible (e.g. OpenRouter, Ollama)</option>
|
||||
<option value="claude_openrouter">{t('settings.general.llmProvider.claudeOpenRouter')}</option>
|
||||
<option value="openai_compatible">{t('settings.general.llmProvider.openaiCompatible')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Persist API Keys on Non-Localhost
|
||||
{t('settings.general.persistKeys.label')}
|
||||
</label>
|
||||
<select
|
||||
className="settings-select"
|
||||
value={persistApiKeysNonLocalhost ? 'yes' : 'no'}
|
||||
onChange={(e) => handlePersistApiKeysNonLocalhostChange(e.target.value)}
|
||||
>
|
||||
<option value="no">No</option>
|
||||
<option value="yes">Yes</option>
|
||||
<option value="no">{t('settings.no')}</option>
|
||||
<option value="yes">{t('settings.yes')}</option>
|
||||
</select>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
When enabled, API keys will be saved to browser storage even on non-localhost environments. Warning: This may increase security vulnerability to XSS attacks.
|
||||
{t('settings.general.persistKeys.help')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -376,20 +411,20 @@ const GeneralSettings: React.FC = () => {
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Cached Model Status
|
||||
{t('settings.general.localRuntime.cachedStatus')}
|
||||
</label>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
{localModelState.isChecking
|
||||
? 'Checking local model cache...'
|
||||
? t('settings.general.localRuntime.cacheChecking')
|
||||
: localModelState.isCached
|
||||
? 'Downloaded in browser cache.'
|
||||
: 'Not downloaded yet.'}
|
||||
? t('settings.general.localRuntime.cacheDownloaded')
|
||||
: t('settings.general.localRuntime.cacheMissing')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label" htmlFor="local-llm-context-length">
|
||||
Context Length
|
||||
{t('settings.general.localRuntime.contextLength')}
|
||||
</label>
|
||||
<select
|
||||
id="local-llm-context-length"
|
||||
@@ -404,13 +439,13 @@ const GeneralSettings: React.FC = () => {
|
||||
))}
|
||||
</select>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
Larger context lengths require more VRAM and may also reduce performance as conversations become longer.
|
||||
{t('settings.general.localRuntime.contextHelp')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Download URL
|
||||
{t('settings.general.localRuntime.downloadUrl')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -420,7 +455,7 @@ const GeneralSettings: React.FC = () => {
|
||||
onChange={(e) => handleLocalModelUrlChange(e.target.value)}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
Changing this URL may break downloads or point to an incompatible model file.{' '}
|
||||
{t('settings.general.localRuntime.downloadHelp')}{' '}
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
@@ -429,14 +464,14 @@ const GeneralSettings: React.FC = () => {
|
||||
}}
|
||||
style={{ color: '#5a9fd4', textDecoration: 'underline', cursor: 'pointer' }}
|
||||
>
|
||||
Restore default
|
||||
{t('settings.restoreDefault')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!localModelState.isCached && !localModelState.isDownloading && localModelState.runtimeSupport.supported && (
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px', marginBottom: '8px' }}>
|
||||
The local model downloads automatically the next time you chat with `Local LLM (Browser)`.
|
||||
{t('settings.general.localRuntime.autoDownload')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -470,17 +505,17 @@ const GeneralSettings: React.FC = () => {
|
||||
onClick={() => void handleDeleteLocalModel()}
|
||||
disabled={localModelState.isDeleting || localModelState.isDownloading || !localModelState.isCached}
|
||||
>
|
||||
{localModelState.isDeleting ? 'Deleting...' : 'Delete Cached Model'}
|
||||
{localModelState.isDeleting ? t('settings.deleting') : t('settings.deleteCachedModel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-group">
|
||||
<h4>UVR5 Web Runtime</h4>
|
||||
<h4>{t('settings.general.uvr5.section')}</h4>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
UVR-MDX-NET-Inst_HQ_3 Download URL
|
||||
{t('settings.general.uvr5.downloadUrl')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -490,7 +525,7 @@ const GeneralSettings: React.FC = () => {
|
||||
onChange={(e) => handleUvr5ModelUrlChange(e.target.value)}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
Changing this URL may break downloads or point to an incompatible model file.{' '}
|
||||
{t('settings.general.modelUrl.help')}{' '}
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
@@ -501,7 +536,7 @@ const GeneralSettings: React.FC = () => {
|
||||
}}
|
||||
style={{ color: '#5a9fd4', textDecoration: 'underline', cursor: 'pointer' }}
|
||||
>
|
||||
Restore default
|
||||
{t('settings.restoreDefault')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -513,13 +548,13 @@ const GeneralSettings: React.FC = () => {
|
||||
onClick={() => void handleDeleteUvr5Model()}
|
||||
disabled={isCheckingUvr5ModelCache || isDeletingUvr5Model || !isUvr5ModelCached}
|
||||
>
|
||||
{isDeletingUvr5Model ? 'Deleting...' : 'Delete Cached Model'}
|
||||
{isDeletingUvr5Model ? t('settings.deleting') : t('settings.deleteCachedModel')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
htdemucs_4s Download URL
|
||||
{t('settings.general.htdemucs.downloadUrl')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -529,7 +564,7 @@ const GeneralSettings: React.FC = () => {
|
||||
onChange={(e) => handleHtdemucsModelUrlChange(e.target.value)}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
Changing this URL may break downloads or point to an incompatible model file.{' '}
|
||||
{t('settings.general.modelUrl.help')}{' '}
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
@@ -540,7 +575,7 @@ const GeneralSettings: React.FC = () => {
|
||||
}}
|
||||
style={{ color: '#5a9fd4', textDecoration: 'underline', cursor: 'pointer' }}
|
||||
>
|
||||
Restore default
|
||||
{t('settings.restoreDefault')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -552,37 +587,37 @@ const GeneralSettings: React.FC = () => {
|
||||
onClick={() => void handleDeleteHtdemucsModel()}
|
||||
disabled={isCheckingUvr5ModelCache || isDeletingHtdemucsModel || !isHtdemucsModelCached}
|
||||
>
|
||||
{isDeletingHtdemucsModel ? 'Deleting...' : 'Delete Cached Model'}
|
||||
{isDeletingHtdemucsModel ? t('settings.deleting') : t('settings.deleteCachedModel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-group">
|
||||
<h4>OpenAI</h4>
|
||||
<h4>{t('settings.general.openai.section')}</h4>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Key
|
||||
{t('settings.general.openai.key')}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="settings-input"
|
||||
placeholder="Enter your OpenAI API key"
|
||||
placeholder={t('settings.general.openai.keyPlaceholder')}
|
||||
value={openaiKey}
|
||||
onChange={(e) => handleOpenaiKeyChange(e.target.value)}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
{isLocalEnvironment
|
||||
? 'Keys are persisted locally (the IndexedDB in your browser).'
|
||||
? t('settings.general.keys.persisted')
|
||||
: persistApiKeysNonLocalhost
|
||||
? 'Keys are persisted locally (the IndexedDB in your browser).'
|
||||
: 'For security, keys are not persisted on non-local hosts and are kept in-memory for this session.'}
|
||||
? t('settings.general.keys.persisted')
|
||||
: t('settings.general.keys.sessionOnly')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Model
|
||||
{t('settings.general.openai.model')}
|
||||
</label>
|
||||
<select
|
||||
className="settings-select"
|
||||
@@ -601,18 +636,18 @@ const GeneralSettings: React.FC = () => {
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Flex Mode
|
||||
{t('settings.general.openai.flexMode')}
|
||||
</label>
|
||||
<select
|
||||
className="settings-select"
|
||||
value={openaiFlex ? 'yes' : 'no'}
|
||||
onChange={(e) => handleOpenaiFlexChange(e.target.value)}
|
||||
>
|
||||
<option value="no">No</option>
|
||||
<option value="yes">Yes</option>
|
||||
<option value="no">{t('settings.no')}</option>
|
||||
<option value="yes">{t('settings.yes')}</option>
|
||||
</select>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
Flex Mode uses OpenAI's flexible service tier. Pros: potential cost savings and higher throughput during busy periods. Cons: variable latency and possible queueing/deprioritization. Applies only to the OpenAI provider; no effect for OpenAI Compatible servers.
|
||||
{t('settings.general.openai.flexHelp')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -678,31 +713,31 @@ const GeneralSettings: React.FC = () => {
|
||||
</div> */}
|
||||
|
||||
<div className="settings-group">
|
||||
<h4>Anthropic Claude (via OpenRouter)</h4>
|
||||
<h4>{t('settings.general.claudeOpenRouter.section')}</h4>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Key
|
||||
{t('settings.general.openai.key')}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="settings-input"
|
||||
placeholder="Enter your Claude API key"
|
||||
placeholder={t('settings.general.claudeOpenRouter.keyPlaceholder')}
|
||||
value={claudeOpenRouterKey}
|
||||
onChange={(e) => handleClaudeOpenRouterKeyChange(e.target.value)}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
{isLocalEnvironment
|
||||
? 'Keys are persisted locally (the IndexedDB in your browser).'
|
||||
? t('settings.general.keys.persisted')
|
||||
: persistApiKeysNonLocalhost
|
||||
? 'Keys are persisted locally (the IndexedDB in your browser).'
|
||||
: 'For security, keys are not persisted on non-local hosts and are kept in-memory for this session.'}
|
||||
? t('settings.general.keys.persisted')
|
||||
: t('settings.general.keys.sessionOnly')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Base URL
|
||||
{t('settings.general.baseUrl')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -712,13 +747,13 @@ const GeneralSettings: React.FC = () => {
|
||||
onChange={(e) => handleClaudeOpenRouterBaseUrlChange(e.target.value)}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
This is the base URL for the OpenRouter API. Please do not change this unless you know what you are doing.
|
||||
{t('settings.general.claudeOpenRouter.baseUrlHelp')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Model
|
||||
{t('settings.general.openai.model')}
|
||||
</label>
|
||||
<select
|
||||
className="settings-select"
|
||||
@@ -736,41 +771,41 @@ const GeneralSettings: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="settings-group">
|
||||
<h4>OpenAI Compatible Server</h4>
|
||||
<h4>{t('settings.general.openaiCompatible.section')}</h4>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Key
|
||||
{t('settings.general.openai.key')}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="settings-input"
|
||||
placeholder="Enter your API key"
|
||||
placeholder={t('settings.general.openaiCompatible.keyPlaceholder')}
|
||||
value={compatibleKey}
|
||||
onChange={(e) => handleCompatibleKeyChange(e.target.value)}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
{isLocalEnvironment
|
||||
? 'Keys are persisted locally (the IndexedDB in your browser).'
|
||||
? t('settings.general.keys.persisted')
|
||||
: persistApiKeysNonLocalhost
|
||||
? 'Keys are persisted locally (the IndexedDB in your browser).'
|
||||
: 'For security, keys are not persisted on non-local hosts and are kept in-memory for this session.'}
|
||||
? t('settings.general.keys.persisted')
|
||||
: t('settings.general.keys.sessionOnly')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Base URL
|
||||
{t('settings.general.baseUrl')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="settings-input"
|
||||
placeholder="e.g. https://openrouter.ai/api/v1"
|
||||
placeholder={t('settings.general.openaiCompatible.baseUrlPlaceholder')}
|
||||
value={compatibleBaseUrl}
|
||||
onChange={(e) => handleCompatibleBaseUrlChange(e.target.value)}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
Quick presets:{' '}
|
||||
{t('settings.general.openaiCompatible.baseUrlHelp')}{' '}
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
@@ -819,12 +854,12 @@ const GeneralSettings: React.FC = () => {
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Model
|
||||
{t('settings.general.openai.model')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="settings-input"
|
||||
placeholder="e.g. qwen3:30b"
|
||||
placeholder={t('settings.general.openaiCompatible.modelPlaceholder')}
|
||||
value={compatibleModel}
|
||||
onChange={(e) => handleCompatibleModelChange(e.target.value)}
|
||||
/>
|
||||
@@ -832,17 +867,17 @@ const GeneralSettings: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="settings-group">
|
||||
<h4>Soundfont Settings</h4>
|
||||
<h4>{t('settings.general.soundfont.section')}</h4>
|
||||
|
||||
{soundfontServerManaged && (
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px', marginBottom: '8px' }}>
|
||||
Soundfont configuration is managed by the server (kgone-server.json). Settings are read-only.
|
||||
{t('settings.general.soundfont.managed')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Base URL
|
||||
{t('settings.general.soundfont.baseUrl')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -853,7 +888,7 @@ const GeneralSettings: React.FC = () => {
|
||||
disabled={soundfontServerManaged}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
Changing this URL to an incompatible soundfont source may cause some instruments to sound wrong or not play.{' '}
|
||||
{t('settings.general.soundfont.baseUrlHelp')}{' '}
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
@@ -862,24 +897,24 @@ const GeneralSettings: React.FC = () => {
|
||||
}}
|
||||
style={{ color: '#5a9fd4', textDecoration: 'underline', cursor: 'pointer' }}
|
||||
>
|
||||
Restore default
|
||||
{t('settings.restoreDefault')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-group">
|
||||
<h4>K.G.One Settings</h4>
|
||||
<h4>{t('settings.general.kgone.section')}</h4>
|
||||
|
||||
{kgoneServerManaged && (
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px', marginBottom: '8px' }}>
|
||||
K.G.One configuration is managed by the server (kgone-server.json). Settings are read-only.
|
||||
{t('settings.general.kgone.managed')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Enable K.G.One Integration
|
||||
{t('settings.general.kgone.enabled')}
|
||||
</label>
|
||||
<select
|
||||
className="settings-select"
|
||||
@@ -887,14 +922,14 @@ const GeneralSettings: React.FC = () => {
|
||||
onChange={(e) => handleKgoneEnabledChange(e.target.value === 'true')}
|
||||
disabled={kgoneServerManaged}
|
||||
>
|
||||
<option value="false">Disabled</option>
|
||||
<option value="true">Enabled</option>
|
||||
<option value="false">{t('settings.general.kgone.disabled')}</option>
|
||||
<option value="true">{t('settings.general.kgone.enabledOption')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<label className="settings-label">
|
||||
Server Base URL
|
||||
{t('settings.general.kgone.serverBaseUrl')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -905,7 +940,7 @@ const GeneralSettings: React.FC = () => {
|
||||
disabled={kgoneServerManaged}
|
||||
/>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
Base URL of a running K.G.One Music Studio server. Used for full-song generation, clip generation, and stem separation.
|
||||
{t('settings.general.kgone.serverBaseUrlHelp')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user