feat: added translation for Music Generator panel; provided K.G.Studio Musician Assistant a Chinese name
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import React from 'react';
|
||||
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import ChatBox from './ChatBox';
|
||||
import { I18nContext } from '../i18n/I18nProvider';
|
||||
import { translate } from '../i18n/translate';
|
||||
|
||||
vi.mock('./chat', () => ({
|
||||
UserMessage: ({ content }: { content: string }) => <div>{content}</div>,
|
||||
AssistantMessage: ({ content }: { content: string }) => <div>{content}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../agent/core/AgentCore', () => ({
|
||||
AgentCore: {
|
||||
instance: () => ({
|
||||
setLLMProvider: vi.fn(),
|
||||
getLLMProvider: vi.fn(),
|
||||
abortCurrentRequest: vi.fn(),
|
||||
getAgentState: vi.fn(() => ({ getMessages: vi.fn(() => []) })),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../agent/llm/LLMProvider', () => ({
|
||||
OpenAICompatibleLLMProvider: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../agent/llm/LocalBrowserLLMProvider', () => ({
|
||||
LocalBrowserLLMProvider: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../core/config/ConfigManager', () => ({
|
||||
ConfigManager: {
|
||||
instance: () => ({
|
||||
getIsInitialized: () => true,
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
get: (key: string) => {
|
||||
if (key === 'general.llm_provider') {
|
||||
return 'openai';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
addChangeListener: () => () => undefined,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../stores/projectStore', () => ({
|
||||
useProjectStore: {
|
||||
getState: () => ({
|
||||
setStatus: vi.fn(),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../agent/core/SystemPrompts', () => ({
|
||||
SystemPrompts: {
|
||||
getSystemPromptWithContext: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../util/chatUtil', () => ({
|
||||
clearChatHistoryAndUI: vi.fn(),
|
||||
registerClearChatUICallback: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../util/messageFilter/UserMessageFilter', () => ({
|
||||
processUserMessage: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/useStreamProcessor', () => ({
|
||||
useStreamProcessor: () => ({
|
||||
abortController: null,
|
||||
processStream: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../utils/chatMessageUtils', () => ({
|
||||
createMessage: vi.fn(),
|
||||
addWelcomeMessage: vi.fn().mockResolvedValue(null),
|
||||
}));
|
||||
|
||||
vi.mock('../util/timeUtil', () => ({
|
||||
formatLocalDateTime: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../util/miscUtil', () => ({
|
||||
downloadBlob: vi.fn(),
|
||||
buildTimestampSuffix: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../util/localLLMModelManager', () => ({
|
||||
LocalLLMModelManager: {
|
||||
getState: () => ({
|
||||
runtimeSupport: { supported: true, reason: null },
|
||||
isCached: false,
|
||||
isDownloading: false,
|
||||
isChecking: false,
|
||||
progressText: '',
|
||||
progressPercent: 0,
|
||||
error: null,
|
||||
}),
|
||||
subscribe: () => () => undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../util/localLLMConfig', () => ({
|
||||
LOCAL_LLM_DISPLAY_NAME: 'Gemma 4 E4B',
|
||||
LOCAL_LLM_PROVIDER_KEY: 'local_browser',
|
||||
}));
|
||||
|
||||
vi.mock('./common/KGDropdown', () => ({
|
||||
default: () => null,
|
||||
}));
|
||||
|
||||
function renderWithLocale(resolvedLocale: 'en_us' | 'zh_cn') {
|
||||
return render(
|
||||
<I18nContext.Provider
|
||||
value={{
|
||||
languageSetting: resolvedLocale,
|
||||
resolvedLocale,
|
||||
setLanguageSetting: async () => undefined,
|
||||
t: (key, params) => translate(key, params, resolvedLocale),
|
||||
}}
|
||||
>
|
||||
<ChatBox isVisible={true} />
|
||||
</I18nContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('ChatBox', () => {
|
||||
beforeAll(() => {
|
||||
Element.prototype.scrollIntoView = vi.fn();
|
||||
});
|
||||
|
||||
it('renders the English assistant title under en_us', () => {
|
||||
renderWithLocale('en_us');
|
||||
|
||||
expect(screen.getByRole('heading', { level: 3, name: 'K.G.Studio Musician Assistant' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders the Chinese assistant title under zh_cn', () => {
|
||||
renderWithLocale('zh_cn');
|
||||
|
||||
expect(screen.getByRole('heading', { level: 3, name: 'K.G.Studio 音乐创作助手' })).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,7 @@ import { downloadBlob, buildTimestampSuffix } from '../util/miscUtil';
|
||||
import { LocalLLMModelManager, type LocalLLMModelState } from '../util/localLLMModelManager';
|
||||
import { LOCAL_LLM_DISPLAY_NAME, LOCAL_LLM_PROVIDER_KEY } from '../util/localLLMConfig';
|
||||
import KGDropdown from './common/KGDropdown';
|
||||
import { useI18n } from '../i18n/useI18n';
|
||||
|
||||
import type { ChatMessage } from '../types/projectTypes';
|
||||
|
||||
@@ -63,6 +64,7 @@ interface ChatBoxProps {
|
||||
}
|
||||
|
||||
const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
|
||||
const { t } = useI18n();
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@@ -345,7 +347,7 @@ const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
|
||||
return (
|
||||
<div className={`chatbox ${isVisible ? '' : 'is-hidden'}`}>
|
||||
<div className="chatbox-header">
|
||||
<h3>K.G.Studio Musician Assistant</h3>
|
||||
<h3>{t('assistant.displayName')}</h3>
|
||||
<div className="chatbox-actions">
|
||||
{isProcessing && (
|
||||
<button
|
||||
|
||||
+225
-249
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@ import type { TranslationMessages } from '../types';
|
||||
|
||||
export const enUsMessages: TranslationMessages = {
|
||||
'app.loading': 'Loading ...',
|
||||
'assistant.displayName': 'K.G.Studio Musician Assistant',
|
||||
'assistant.welcomeFallback': 'Welcome to K.G.Studio Musician Assistant.',
|
||||
'status.chordGuideCandidate': 'Chord Guide Candidate: {name} - {notes} - {note}',
|
||||
'settings.sidebar.title': 'Settings',
|
||||
'settings.sidebar.close': 'Close Settings',
|
||||
@@ -507,4 +509,154 @@ export const enUsMessages: TranslationMessages = {
|
||||
'instrument.name.fx_6_goblins': 'FX 6 (goblins)',
|
||||
'instrument.name.fx_7_echoes': 'FX 7 (echoes)',
|
||||
'instrument.name.fx_8_scifi': 'FX 8 (sci-fi)',
|
||||
// ─── K.G.One / Music Generator Panel ─────────────────────────────────────────
|
||||
'kgone.panel.title.server': 'K.G.One Music Generator',
|
||||
'kgone.panel.title.local': 'Music Generator',
|
||||
'kgone.tab.fullSong': 'Full Song',
|
||||
'kgone.tab.remix': 'Remix',
|
||||
'kgone.tab.repaint': 'Repaint',
|
||||
'kgone.tab.separator': 'Separator',
|
||||
'kgone.tab.requiresServer': 'Requires K.G.One Music Studio server integration',
|
||||
// Shared across tabs
|
||||
'kgone.shared.advancedSettings': 'Advanced Settings',
|
||||
'kgone.shared.caption': 'Caption',
|
||||
'kgone.shared.lyrics': 'Lyrics',
|
||||
'kgone.shared.lyricsPlaceholder': '[Verse 1]\nYour lyrics here...\n\n[Chorus]\n...',
|
||||
'kgone.shared.instrumental': 'Instrumental (no vocals)',
|
||||
'kgone.shared.inferenceSteps': 'Inference Steps',
|
||||
'kgone.shared.guidanceScale': 'Guidance Scale',
|
||||
'kgone.shared.useRandomSeed': 'Use Random Seed',
|
||||
'kgone.shared.seed': 'Seed',
|
||||
'kgone.shared.thinking': 'Thinking (CoT metadata generation)',
|
||||
'kgone.shared.selectedRegion': 'Selected Region',
|
||||
'kgone.shared.track': 'Track',
|
||||
'kgone.shared.poweredBy': 'Powered by ',
|
||||
'kgone.shared.btn.loadingModel': 'Loading model...',
|
||||
'kgone.shared.btn.generating': 'Generating...',
|
||||
'kgone.shared.btn.processing': 'Processing...',
|
||||
'kgone.shared.btn.downloading': 'Downloading...',
|
||||
'kgone.shared.btn.preparingUpload': 'Preparing upload...',
|
||||
'kgone.shared.btn.importing': 'Importing...',
|
||||
'kgone.shared.hint.loadingModel': 'Loading model — this can take 60+ seconds, please wait...',
|
||||
'kgone.shared.hint.submitting': 'Submitting generation request...',
|
||||
'kgone.shared.hint.downloadingAudio': 'Downloading audio...',
|
||||
// Clip tab
|
||||
'kgone.clip.field.prompt': 'Prompt',
|
||||
'kgone.clip.field.promptPlaceholder': 'e.g. Gritty, Acid, Bassline, 303, Synth Lead, FM, Sub, Upper Mids, High Phaser, High Reverb, Pitch Bend, 8 Bars, 140 BPM, E minor',
|
||||
'kgone.clip.field.promptHint': 'Describe the clip with comma-separated tags: instrument family, sub-type, timbre, FX, bars, BPM, key.',
|
||||
'kgone.clip.field.negativePrompt': 'Negative Prompt',
|
||||
'kgone.clip.field.negativePromptPlaceholder': 'e.g. distortion, noise',
|
||||
'kgone.clip.field.bars': 'Bars',
|
||||
'kgone.clip.field.bars4': '4 Bars',
|
||||
'kgone.clip.field.bars8': '8 Bars',
|
||||
'kgone.clip.field.note': 'Note',
|
||||
'kgone.clip.field.scale': 'Scale',
|
||||
'kgone.clip.field.scaleMajor': 'Major',
|
||||
'kgone.clip.field.scaleMinor': 'Minor',
|
||||
'kgone.clip.field.bpm': 'BPM',
|
||||
'kgone.clip.field.steps': 'Steps',
|
||||
'kgone.clip.field.cfgScale': 'CFG Scale',
|
||||
'kgone.clip.field.seedLabel': 'Seed (-1 = random)',
|
||||
'kgone.clip.field.samplerType': 'Sampler Type',
|
||||
'kgone.clip.field.sigmaMin': 'Sigma Min',
|
||||
'kgone.clip.field.sigmaMax': 'Sigma Max',
|
||||
'kgone.clip.field.cfgRescale': 'CFG Rescale',
|
||||
'kgone.clip.hint.generating': 'Generating clip...',
|
||||
'kgone.clip.hint.drag': 'Drag the player above to a track to import the clip. Drop onto an <strong>audio track</strong> to import as a WAV region (recommended), or onto a <strong>MIDI track</strong> to import as a MIDI region. Note: MIDI is transcribed from the audio and may not be perfectly accurate.',
|
||||
'kgone.clip.btn.generate': 'Generate Clip',
|
||||
'kgone.clip.poweredBy': 'Foundation-1',
|
||||
// Full Song tab
|
||||
'kgone.fullSong.field.captionPlaceholder': 'e.g. Genre: Eurodance, 90s dance-pop, upbeat electronic. Style: Catchy, energetic... Tempo: ~130 BPM. Instrumentation: driving kick drum, eurodance bassline...',
|
||||
'kgone.fullSong.field.captionHint': 'Describe the song style, mood, tempo, instrumentation, and structure in natural language.',
|
||||
'kgone.fullSong.field.lyricsHint': 'Use [Intro], [Verse], [Chorus], [Bridge] tags to mark sections.',
|
||||
'kgone.fullSong.hint.generating': 'Generating...',
|
||||
'kgone.fullSong.hint.generatingProgress': 'Generating... {pct}% — {stage}',
|
||||
'kgone.fullSong.hint.generatingProgressNoStage': 'Generating... {pct}%',
|
||||
'kgone.fullSong.hint.drag': 'Drag the player above to an <strong>audio track</strong> to import the song. Dropping onto a MIDI track is not supported for full song generation.',
|
||||
'kgone.fullSong.btn.generate': 'Generate Song',
|
||||
'kgone.fullSong.poweredBy': 'ACE-Step 1.5',
|
||||
// Remix tab
|
||||
'kgone.remix.field.captionPlaceholder': 'e.g. Genre: Jazz, Style: Smooth and mellow, Instrumentation: piano, upright bass, brushed drums...',
|
||||
'kgone.remix.field.captionHint': 'Describe the target style, mood, and instrumentation for the remix.',
|
||||
'kgone.remix.field.lyricsHint': 'Leave empty to keep the original lyrics, or provide new ones. Use [Verse], [Chorus], [Bridge] tags.',
|
||||
'kgone.remix.field.coverStrength': 'Cover Strength',
|
||||
'kgone.remix.field.coverStrengthHint': '0 = creative, 1 = faithful to source structure',
|
||||
'kgone.remix.field.noiseStrength': 'Noise Strength',
|
||||
'kgone.remix.field.noiseStrengthHint': '0 = pure style transfer, 0.1–0.25 recommended',
|
||||
'kgone.remix.hint.submitting': 'Submitting remix request...',
|
||||
'kgone.remix.hint.generating': 'Generating remix...',
|
||||
'kgone.remix.hint.generatingProgress': 'Generating remix... {pct}% — {stage}',
|
||||
'kgone.remix.hint.generatingProgressNoStage': 'Generating remix... {pct}%',
|
||||
'kgone.remix.hint.drag': 'Drag the player above to an <strong>audio track</strong> to import the remix.',
|
||||
'kgone.remix.hint.noRegion': 'Select an audio region on the timeline to remix it. Only audio regions are supported — MIDI regions cannot be remixed.',
|
||||
'kgone.remix.btn.generate': 'Generate Remix',
|
||||
'kgone.remix.btn.processingRemix': 'Processing remix...',
|
||||
'kgone.remix.btn.importAligned': 'Import Aligned to Source',
|
||||
'kgone.remix.poweredBy': 'ACE-Step 1.5',
|
||||
// Repaint tab
|
||||
'kgone.repaint.field.repaintRange': 'Repaint Range',
|
||||
'kgone.repaint.field.loopModeOff': 'Loop mode is off. Enable the loop button on the toolbar and set a loop range to define the repaint window.',
|
||||
'kgone.repaint.field.start': 'Start',
|
||||
'kgone.repaint.field.end': 'End',
|
||||
'kgone.repaint.field.untilEnd': 'until end of audio',
|
||||
'kgone.repaint.field.captionPlaceholder': 'e.g. Genre: Jazz, Style: Smooth and mellow, Instrumentation: piano, upright bass, brushed drums...',
|
||||
'kgone.repaint.field.captionHint': 'Describe the target style and instrumentation for the repainted section.',
|
||||
'kgone.repaint.field.lyricsHint': 'Leave empty to keep the original lyrics, or provide new ones for the repainted section.',
|
||||
'kgone.repaint.field.repaintStrength': 'Repaint Strength',
|
||||
'kgone.repaint.field.repaintStrengthHint': '0 = preserve original, 1 = full regeneration',
|
||||
'kgone.repaint.hint.submitting': 'Submitting repaint request...',
|
||||
'kgone.repaint.hint.generating': 'Generating repaint...',
|
||||
'kgone.repaint.hint.generatingProgress': 'Generating repaint... {pct}% — {stage}',
|
||||
'kgone.repaint.hint.generatingProgressNoStage': 'Generating repaint... {pct}%',
|
||||
'kgone.repaint.hint.drag': 'Drag the player above to an <strong>audio track</strong> to import the repaint.',
|
||||
'kgone.repaint.hint.noRegion': 'Select an audio region on the timeline to repaint it. Only audio regions are supported — MIDI regions cannot be repainted.',
|
||||
'kgone.repaint.btn.generate': 'Generate Repaint',
|
||||
'kgone.repaint.btn.processingRepaint': 'Processing repaint...',
|
||||
'kgone.repaint.btn.importAligned': 'Import Aligned to Source',
|
||||
'kgone.repaint.poweredBy': 'ACE-Step 1.5',
|
||||
// Separator tab
|
||||
'kgone.separator.field.separationModel': 'Separation Model',
|
||||
'kgone.separator.field.chunkDuration': 'Optional audio chunk duration (seconds)',
|
||||
'kgone.separator.field.chunkDurationPlaceholder': 'Leave blank to process the full region',
|
||||
'kgone.separator.field.modelOverlap': 'Model overlap',
|
||||
'kgone.separator.hint.drag': 'Drag each stem player above to an <strong>audio track</strong> to import it. Dropping onto a MIDI track is not supported for stem separation.',
|
||||
'kgone.separator.hint.noRegion.download': 'Download {model}, then select an audio region on the timeline to extract stems from it.',
|
||||
'kgone.separator.hint.noRegion.select': 'Select an audio region on the timeline to extract stems from it. Only audio regions are supported — MIDI regions cannot be separated.',
|
||||
'kgone.separator.btn.separateStems': 'Separate Stems',
|
||||
'kgone.separator.btn.importAllStems': 'Import All Stems to Timeline',
|
||||
'kgone.separator.poweredBy': 'UVR5 CLI',
|
||||
'kgone.separator.model.vocalInstMedium': 'Vocal and Instrument (Medium Accuracy)',
|
||||
'kgone.separator.model.vocalInstHigh': 'Vocal and Instrument (High Accuracy)',
|
||||
'kgone.separator.model.6stem': 'Vocal, Drums, Bass, Guitar, Piano, and Others',
|
||||
'kgone.separator.model.htdemucs4s': 'Vocal, Drums, Bass, and Others',
|
||||
'kgone.separator.btn.server.loadingModel': 'Loading model...',
|
||||
'kgone.separator.btn.server.generating': 'Preparing upload...',
|
||||
'kgone.separator.btn.server.polling': 'Separating stems...',
|
||||
'kgone.separator.btn.server.downloading': 'Downloading...',
|
||||
'kgone.separator.btn.local.loadingModel': 'Preparing local model...',
|
||||
'kgone.separator.btn.local.generating': 'Preparing audio...',
|
||||
'kgone.separator.btn.local.polling': 'Separating locally...',
|
||||
'kgone.separator.btn.local.downloading': 'Finalizing...',
|
||||
'kgone.separator.local.title': 'Local Separator Mode',
|
||||
'kgone.separator.local.description': 'If K.G.One Music Studio is unavailable, Local Separator Mode provides a built-in alternative for extracting stems directly in your browser. Two local models are available: Vocal and Instrument (Medium Accuracy), and Vocal, Drums, Bass, and Others. Download status below reflects the currently selected model. Vocal and Instrument (Medium Accuracy) usually takes longer to process than Vocal, Drums, Bass, and Others, and total processing time will still depend on your hardware. If processing falls back to CPU, the page may become temporarily less responsive while separation is running.',
|
||||
'kgone.separator.local.learnMore': 'Learn more about K.G.One Music Studio server integration.',
|
||||
'kgone.separator.local.provider': 'Provider: ',
|
||||
'kgone.separator.local.model': 'Model: ',
|
||||
'kgone.separator.local.downloaded': 'downloaded',
|
||||
'kgone.separator.local.notDownloaded': 'not downloaded',
|
||||
'kgone.separator.local.checkingCache': 'Checking local model cache...',
|
||||
'kgone.separator.local.btn.download': 'Download Selected Model',
|
||||
'kgone.separator.local.btn.downloading': 'Downloading Model...',
|
||||
'kgone.separator.local.btn.redownload': 'Redownload Model',
|
||||
'kgone.separator.local.btn.redownloading': 'Redownloading...',
|
||||
'kgone.separator.local.btn.deleteCache': 'Delete Cached Model',
|
||||
'kgone.separator.local.btn.deleting': 'Deleting...',
|
||||
'kgone.separator.local.progress.downloading': 'Downloading {name}...',
|
||||
'kgone.separator.local.progress.downloadingWithSize': 'Downloading {name}... {received} / {total} MB',
|
||||
'kgone.separator.local.progress.downloadingMbOnly': 'Downloading {name}... {received} MB',
|
||||
'kgone.separator.local.progress.ready': '{name} is ready.',
|
||||
'kgone.separator.local.progress.preparingRuntime': 'Preparing ONNX Runtime session...',
|
||||
'kgone.separator.local.progress.readingAudio': 'Reading audio file...',
|
||||
'kgone.separator.local.progress.running': 'Running browser separation...',
|
||||
'kgone.separator.local.progress.complete': 'Separation complete.',
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { TranslationMessages } from '../types';
|
||||
|
||||
export const zhCnMessages: TranslationMessages = {
|
||||
'app.loading': '加载中...',
|
||||
'assistant.displayName': 'K.G.Studio 音乐创作助手',
|
||||
'assistant.welcomeFallback': '欢迎使用 K.G.Studio 音乐创作助手。',
|
||||
'status.chordGuideCandidate': '和弦指导候选: {name} - {notes} - {note}',
|
||||
'settings.sidebar.title': '设置',
|
||||
'settings.sidebar.close': '关闭设置',
|
||||
@@ -507,4 +509,154 @@ export const zhCnMessages: TranslationMessages = {
|
||||
'instrument.name.fx_6_goblins': '特效 6(妖精)',
|
||||
'instrument.name.fx_7_echoes': '特效 7(回声)',
|
||||
'instrument.name.fx_8_scifi': '特效 8(科幻)',
|
||||
// ─── K.G.One / Music Generator Panel ─────────────────────────────────────────
|
||||
'kgone.panel.title.server': 'K.G.One 音乐生成器',
|
||||
'kgone.panel.title.local': '音乐生成器',
|
||||
'kgone.tab.fullSong': '完整歌曲',
|
||||
'kgone.tab.remix': '混音',
|
||||
'kgone.tab.repaint': '重绘',
|
||||
'kgone.tab.separator': '分离',
|
||||
'kgone.tab.requiresServer': '需要 K.G.One Music Studio 服务器集成',
|
||||
// Shared across tabs
|
||||
'kgone.shared.advancedSettings': '高级设置',
|
||||
'kgone.shared.caption': '描述',
|
||||
'kgone.shared.lyrics': '歌词',
|
||||
'kgone.shared.lyricsPlaceholder': '[Verse 1]\n在这里输入歌词...\n\n[Chorus]\n...',
|
||||
'kgone.shared.instrumental': '纯音乐(无人声)',
|
||||
'kgone.shared.inferenceSteps': '推理步数',
|
||||
'kgone.shared.guidanceScale': '引导强度',
|
||||
'kgone.shared.useRandomSeed': '使用随机种子',
|
||||
'kgone.shared.seed': '种子',
|
||||
'kgone.shared.thinking': '思考模式(CoT 元数据生成)',
|
||||
'kgone.shared.selectedRegion': '已选区域',
|
||||
'kgone.shared.track': '音轨',
|
||||
'kgone.shared.poweredBy': '技术支持:',
|
||||
'kgone.shared.btn.loadingModel': '加载模型中...',
|
||||
'kgone.shared.btn.generating': '生成中...',
|
||||
'kgone.shared.btn.processing': '处理中...',
|
||||
'kgone.shared.btn.downloading': '下载中...',
|
||||
'kgone.shared.btn.preparingUpload': '准备上传...',
|
||||
'kgone.shared.btn.importing': '导入中...',
|
||||
'kgone.shared.hint.loadingModel': '正在加载模型——此过程可能需要 60 秒以上,请耐心等待...',
|
||||
'kgone.shared.hint.submitting': '正在提交生成请求...',
|
||||
'kgone.shared.hint.downloadingAudio': '正在下载音频...',
|
||||
// Clip tab
|
||||
'kgone.clip.field.prompt': '提示词',
|
||||
'kgone.clip.field.promptPlaceholder': '例:粗糙, 酸性, 贝斯线, 303, 合成主音, FM, 次低音, 中高频, 高频相位, 高频混响, 弯音, 8小节, 140 BPM, E 小调',
|
||||
'kgone.clip.field.promptHint': '用逗号分隔的标签描述片段:乐器类别、子类型、音色、效果、小节数、BPM、调性。',
|
||||
'kgone.clip.field.negativePrompt': '负向提示词',
|
||||
'kgone.clip.field.negativePromptPlaceholder': '例:失真, 噪音',
|
||||
'kgone.clip.field.bars': '小节数',
|
||||
'kgone.clip.field.bars4': '4 小节',
|
||||
'kgone.clip.field.bars8': '8 小节',
|
||||
'kgone.clip.field.note': '音符',
|
||||
'kgone.clip.field.scale': '音阶',
|
||||
'kgone.clip.field.scaleMajor': '大调',
|
||||
'kgone.clip.field.scaleMinor': '小调',
|
||||
'kgone.clip.field.bpm': 'BPM',
|
||||
'kgone.clip.field.steps': '步数',
|
||||
'kgone.clip.field.cfgScale': 'CFG 强度',
|
||||
'kgone.clip.field.seedLabel': '种子(-1 = 随机)',
|
||||
'kgone.clip.field.samplerType': '采样器类型',
|
||||
'kgone.clip.field.sigmaMin': 'Sigma 最小值',
|
||||
'kgone.clip.field.sigmaMax': 'Sigma 最大值',
|
||||
'kgone.clip.field.cfgRescale': 'CFG 重缩放',
|
||||
'kgone.clip.hint.generating': '正在生成片段...',
|
||||
'kgone.clip.hint.drag': '将上方播放器拖至音轨以导入片段。拖到<strong>音频音轨</strong>可作为 WAV 区域导入(推荐),拖到<strong>MIDI 音轨</strong>可作为 MIDI 区域导入。注意:MIDI 是从音频转录的,可能不完全准确。',
|
||||
'kgone.clip.btn.generate': '生成片段',
|
||||
'kgone.clip.poweredBy': 'Foundation-1',
|
||||
// Full Song tab
|
||||
'kgone.fullSong.field.captionPlaceholder': '例:风格:欧洲舞曲,90年代流行电子。感觉:朗朗上口、充满活力... 速度:约 130 BPM。编排:强劲底鼓、欧洲舞曲贝斯线...',
|
||||
'kgone.fullSong.field.captionHint': '用自然语言描述歌曲风格、情绪、速度、编排和结构。',
|
||||
'kgone.fullSong.field.lyricsHint': '使用 [Intro]、[Verse]、[Chorus]、[Bridge] 标签标注段落。',
|
||||
'kgone.fullSong.hint.generating': '生成中...',
|
||||
'kgone.fullSong.hint.generatingProgress': '生成中... {pct}% — {stage}',
|
||||
'kgone.fullSong.hint.generatingProgressNoStage': '生成中... {pct}%',
|
||||
'kgone.fullSong.hint.drag': '将上方播放器拖至<strong>音频音轨</strong>以导入歌曲。完整歌曲生成不支持拖至 MIDI 音轨。',
|
||||
'kgone.fullSong.btn.generate': '生成歌曲',
|
||||
'kgone.fullSong.poweredBy': 'ACE-Step 1.5',
|
||||
// Remix tab
|
||||
'kgone.remix.field.captionPlaceholder': '例:风格:爵士,感觉:流畅悠扬,编排:钢琴、立式贝斯、刷式鼓...',
|
||||
'kgone.remix.field.captionHint': '描述混音的目标风格、情绪和编排。',
|
||||
'kgone.remix.field.lyricsHint': '留空则保留原歌词,或提供新歌词。使用 [Verse]、[Chorus]、[Bridge] 标签。',
|
||||
'kgone.remix.field.coverStrength': '翻唱强度',
|
||||
'kgone.remix.field.coverStrengthHint': '0 = 创意改编,1 = 忠实于原曲结构',
|
||||
'kgone.remix.field.noiseStrength': '噪声强度',
|
||||
'kgone.remix.field.noiseStrengthHint': '0 = 纯风格迁移,推荐 0.1–0.25',
|
||||
'kgone.remix.hint.submitting': '正在提交混音请求...',
|
||||
'kgone.remix.hint.generating': '正在生成混音...',
|
||||
'kgone.remix.hint.generatingProgress': '正在生成混音... {pct}% — {stage}',
|
||||
'kgone.remix.hint.generatingProgressNoStage': '正在生成混音... {pct}%',
|
||||
'kgone.remix.hint.drag': '将上方播放器拖至<strong>音频音轨</strong>以导入混音。',
|
||||
'kgone.remix.hint.noRegion': '请在时间轴上选择一个音频区域以进行混音。仅支持音频区域——MIDI 区域不可混音。',
|
||||
'kgone.remix.btn.generate': '生成混音',
|
||||
'kgone.remix.btn.processingRemix': '处理混音中...',
|
||||
'kgone.remix.btn.importAligned': '对齐源区域并导入',
|
||||
'kgone.remix.poweredBy': 'ACE-Step 1.5',
|
||||
// Repaint tab
|
||||
'kgone.repaint.field.repaintRange': '重绘范围',
|
||||
'kgone.repaint.field.loopModeOff': '循环模式已关闭。请启用工具栏上的循环按钮并设置循环范围以定义重绘窗口。',
|
||||
'kgone.repaint.field.start': '开始',
|
||||
'kgone.repaint.field.end': '结束',
|
||||
'kgone.repaint.field.untilEnd': '直至音频末尾',
|
||||
'kgone.repaint.field.captionPlaceholder': '例:风格:爵士,感觉:流畅悠扬,编排:钢琴、立式贝斯、刷式鼓...',
|
||||
'kgone.repaint.field.captionHint': '描述重绘段落的目标风格和编排。',
|
||||
'kgone.repaint.field.lyricsHint': '留空则保留原歌词,或为重绘段落提供新歌词。',
|
||||
'kgone.repaint.field.repaintStrength': '重绘强度',
|
||||
'kgone.repaint.field.repaintStrengthHint': '0 = 保留原音,1 = 完全重新生成',
|
||||
'kgone.repaint.hint.submitting': '正在提交重绘请求...',
|
||||
'kgone.repaint.hint.generating': '正在生成重绘...',
|
||||
'kgone.repaint.hint.generatingProgress': '正在生成重绘... {pct}% — {stage}',
|
||||
'kgone.repaint.hint.generatingProgressNoStage': '正在生成重绘... {pct}%',
|
||||
'kgone.repaint.hint.drag': '将上方播放器拖至<strong>音频音轨</strong>以导入重绘结果。',
|
||||
'kgone.repaint.hint.noRegion': '请在时间轴上选择一个音频区域以进行重绘。仅支持音频区域——MIDI 区域不可重绘。',
|
||||
'kgone.repaint.btn.generate': '生成重绘',
|
||||
'kgone.repaint.btn.processingRepaint': '处理重绘中...',
|
||||
'kgone.repaint.btn.importAligned': '对齐源区域并导入',
|
||||
'kgone.repaint.poweredBy': 'ACE-Step 1.5',
|
||||
// Separator tab
|
||||
'kgone.separator.field.separationModel': '分离模型',
|
||||
'kgone.separator.field.chunkDuration': '可选音频分块时长(秒)',
|
||||
'kgone.separator.field.chunkDurationPlaceholder': '留空则处理整个区域',
|
||||
'kgone.separator.field.modelOverlap': '模型重叠',
|
||||
'kgone.separator.hint.drag': '将上方各音轨播放器拖至<strong>音频音轨</strong>以导入。不支持拖至 MIDI 音轨进行音轨分离。',
|
||||
'kgone.separator.hint.noRegion.download': '请下载 {model},然后在时间轴上选择一个音频区域以提取音轨。',
|
||||
'kgone.separator.hint.noRegion.select': '请在时间轴上选择一个音频区域以提取音轨。仅支持音频区域——MIDI 区域不可分离。',
|
||||
'kgone.separator.btn.separateStems': '分离音轨',
|
||||
'kgone.separator.btn.importAllStems': '将所有音轨导入时间轴',
|
||||
'kgone.separator.poweredBy': 'UVR5 CLI',
|
||||
'kgone.separator.model.vocalInstMedium': '人声与乐器(中等精度)',
|
||||
'kgone.separator.model.vocalInstHigh': '人声与乐器(高精度)',
|
||||
'kgone.separator.model.6stem': '人声、鼓、贝斯、吉他、钢琴及其他',
|
||||
'kgone.separator.model.htdemucs4s': '人声、鼓、贝斯及其他',
|
||||
'kgone.separator.btn.server.loadingModel': '加载模型中...',
|
||||
'kgone.separator.btn.server.generating': '准备上传...',
|
||||
'kgone.separator.btn.server.polling': '正在分离音轨...',
|
||||
'kgone.separator.btn.server.downloading': '下载中...',
|
||||
'kgone.separator.btn.local.loadingModel': '准备本地模型...',
|
||||
'kgone.separator.btn.local.generating': '准备音频...',
|
||||
'kgone.separator.btn.local.polling': '本地分离中...',
|
||||
'kgone.separator.btn.local.downloading': '收尾处理...',
|
||||
'kgone.separator.local.title': '本地分离模式',
|
||||
'kgone.separator.local.description': '如果 K.G.One Music Studio 不可用,本地分离模式提供了一种直接在浏览器中提取音轨的内置替代方案。提供两种本地模型:人声与乐器(中等精度),以及人声、鼓、贝斯及其他。下方的下载状态反映当前选定的模型。人声与乐器(中等精度)的处理时间通常比人声、鼓、贝斯及其他更长,总处理时间仍取决于您的硬件配置。如果处理回退至 CPU,分离运行期间页面响应可能暂时变慢。',
|
||||
'kgone.separator.local.learnMore': '了解更多关于 K.G.One Music Studio 服务器集成的信息。',
|
||||
'kgone.separator.local.provider': '当前设备:',
|
||||
'kgone.separator.local.model': '模型:',
|
||||
'kgone.separator.local.downloaded': '已下载',
|
||||
'kgone.separator.local.notDownloaded': '未下载',
|
||||
'kgone.separator.local.checkingCache': '正在检查本地模型缓存...',
|
||||
'kgone.separator.local.btn.download': '下载所选模型',
|
||||
'kgone.separator.local.btn.downloading': '模型下载中...',
|
||||
'kgone.separator.local.btn.redownload': '重新下载模型',
|
||||
'kgone.separator.local.btn.redownloading': '重新下载中...',
|
||||
'kgone.separator.local.btn.deleteCache': '删除已缓存模型',
|
||||
'kgone.separator.local.btn.deleting': '删除中...',
|
||||
'kgone.separator.local.progress.downloading': '正在下载 {name}...',
|
||||
'kgone.separator.local.progress.downloadingWithSize': '正在下载 {name}... {received} / {total} MB',
|
||||
'kgone.separator.local.progress.downloadingMbOnly': '正在下载 {name}... {received} MB',
|
||||
'kgone.separator.local.progress.ready': '{name} 已就绪。',
|
||||
'kgone.separator.local.progress.preparingRuntime': '正在准备 ONNX 运行时环境...',
|
||||
'kgone.separator.local.progress.readingAudio': '正在读取音频文件...',
|
||||
'kgone.separator.local.progress.running': '正在浏览器中执行分离...',
|
||||
'kgone.separator.local.progress.complete': '分离完成。',
|
||||
};
|
||||
|
||||
@@ -111,6 +111,19 @@ describe('processUserMessage slash commands', () => {
|
||||
expect(result.pseudoAssistantResponse).toContain('welcome_local_llm-zh_cn.md');
|
||||
});
|
||||
|
||||
it('falls back to a localized welcome string when welcome markdown fetch fails', async () => {
|
||||
configState.set('general.language', 'zh_cn');
|
||||
configState.set('general.llm_provider', 'local_browser');
|
||||
vi.stubGlobal('fetch', vi.fn(async () => {
|
||||
throw new Error('network down');
|
||||
}));
|
||||
|
||||
const result = await processUserMessage('/welcome');
|
||||
|
||||
expect(result.metadata).toMatchObject({ command: 'welcome' });
|
||||
expect(result.pseudoAssistantResponse).toBe('欢迎使用 K.G.Studio 音乐创作助手。');
|
||||
});
|
||||
|
||||
it('uses the new-user welcome for non-local providers without required config', async () => {
|
||||
configState.set('general.llm_provider', 'openai');
|
||||
configState.set('general.openai.api_key', '');
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SystemPrompts } from '../../agent/core/SystemPrompts';
|
||||
import { detectLocalLLMRuntimeSupport, LOCAL_LLM_PROVIDER_KEY } from '../localLLMConfig';
|
||||
import { normalizeLanguageSetting, resolveLanguageSetting } from '../../i18n/locale';
|
||||
import type { ResolvedLocaleCode } from '../../i18n/types';
|
||||
import { translate } from '../../i18n/translate';
|
||||
|
||||
export interface UserMessageFilterResult {
|
||||
// Whether to render the user message bubble (div.message-user)
|
||||
@@ -133,8 +134,8 @@ export async function processUserMessage(originalMessage: string): Promise<UserM
|
||||
}
|
||||
|
||||
case '/welcome': {
|
||||
const configManager = ConfigManager.instance();
|
||||
try {
|
||||
const configManager = ConfigManager.instance();
|
||||
if (!configManager.getIsInitialized()) {
|
||||
await configManager.initialize();
|
||||
}
|
||||
@@ -150,7 +151,8 @@ export async function processUserMessage(originalMessage: string): Promise<UserM
|
||||
metadata: { command: 'welcome', variant }
|
||||
};
|
||||
} catch (err) {
|
||||
const fallback = 'Welcome to K.G.Studio Musician Assistant.';
|
||||
const locale = resolveCurrentCommandLocale(configManager);
|
||||
const fallback = translate('assistant.welcomeFallback', undefined, locale);
|
||||
return {
|
||||
displayUserMessage: false,
|
||||
sendToLLM: false,
|
||||
|
||||
Reference in New Issue
Block a user