feat: added translation for Music Generator panel; provided K.G.Studio Musician Assistant a Chinese name

This commit is contained in:
Xiaohan-Tian
2026-05-30 20:29:10 -07:00
parent 957f6db950
commit b5546aa230
12 changed files with 711 additions and 267 deletions
+9 -9
View File
@@ -17,9 +17,9 @@
## 什么是 K.G.Studio
K.G.Studio 是一款轻量、现代化的 DAW,完全运行于浏览器中,并以 **K.G.Studio Musician Assistant** 为核心。它提供基于 Tone.js sampler 的真实乐器回放、钢琴卷帘编辑器、支持完整撤销/重做的音轨与区域管理、基于 OPFSOrigin Private File System)的项目持久化、可配置的设置面板,以及可执行工具的内置 AI 助手。
K.G.Studio 是一款轻量、现代化的 DAW,完全运行于浏览器中,并以 **K.G.Studio 音乐创作助手** 为核心。它提供基于 Tone.js sampler 的真实乐器回放、钢琴卷帘编辑器、支持完整撤销/重做的音轨与区域管理、基于 OPFSOrigin Private File System)的项目持久化、可配置的设置面板,以及可执行工具的内置 AI 助手。
**K.G.Studio Musician Assistant** 是一个面向和声、编曲与音符编辑的 AI 助手,但并不负责整首作品的全自动作曲。
**K.G.Studio 音乐创作助手** 是一个面向和声、编曲与音符编辑的 AI 助手,但并不负责整首作品的全自动作曲。
<div align="center">
<img src="./docs/KGOne-Demo-GIF.gif" alt="K.G.One Logo" width="640" />
@@ -35,7 +35,7 @@ K.G.Studio 是一款轻量、现代化的 DAW,完全运行于浏览器中,
- 新增 **带自动对齐拍点的速度检测** 功能。您可以为音频区域打开钢琴卷帘窗口,然后在工具栏点击“...” -> **Detect Tempo**,分析音频 BPM,并可选择将项目中的 Tempo Track 区段自动重新对齐。
- 新增 **Demucs 4S** 作为第二个本地浏览器内嵌分轨模型。现有的双 stem UVR-MDX-NET 模型之外,又加入了四 stem 的 `htdemucs_4s` 模型(约 172 MBvocals / drums / bass / others),两者都可通过 ONNX Runtime WebGPU 完全在浏览器中运行。
- **2026.05.15**: 新增 **浏览器内嵌 AI 模型**。现在有两个 AI 模型可完全在浏览器中运行,无需外部服务、无需 API Key,也无需 K.G.One 服务器。**K.G.Studio Musician Assistant** 新增 **Local LLM (Browser)** 提供方,由 **Gemma 4 E4B** 驱动,并通过 LiteRT-LM 与 WebGPU 加速运行;模型只需下载一次,随后会缓存在 OPFS 中,后续启动几乎可即时使用,同时支持可配置的上下文长度(32 k / 64 k / 128 k tokens)与实时推理性能统计。**Stem separation** 也支持本地运行,基于浏览器内嵌的 **UVR-MDX-NET-Inst_HQ_3** ONNX 模型并使用 WebGPU 加速。您可以打开 **Music Generator** 面板(✦ 按钮),下载模型一次后,即可完全在本机将人声与伴奏分离。以上两项功能都要求浏览器支持 WebGPUChrome 113+ 或 Edge 113+)并运行在安全上下文中(HTTPS 或 localhost)。推荐硬件:至少 8 GB 显存的 GPU,或至少 16 GB 统一内存的系统。
- **2026.05.15**: 新增 **浏览器内嵌 AI 模型**。现在有两个 AI 模型可完全在浏览器中运行,无需外部服务、无需 API Key,也无需 K.G.One 服务器。**K.G.Studio 音乐创作助手** 新增 **Local LLM (Browser)** 提供方,由 **Gemma 4 E4B** 驱动,并通过 LiteRT-LM 与 WebGPU 加速运行;模型只需下载一次,随后会缓存在 OPFS 中,后续启动几乎可即时使用,同时支持可配置的上下文长度(32 k / 64 k / 128 k tokens)与实时推理性能统计。**Stem separation** 也支持本地运行,基于浏览器内嵌的 **UVR-MDX-NET-Inst_HQ_3** ONNX 模型并使用 WebGPU 加速。您可以打开 **Music Generator** 面板(✦ 按钮),下载模型一次后,即可完全在本机将人声与伴奏分离。以上两项功能都要求浏览器支持 WebGPUChrome 113+ 或 Edge 113+)并运行在安全上下文中(HTTPS 或 localhost)。推荐硬件:至少 8 GB 显存的 GPU,或至少 16 GB 统一内存的系统。
- **2026.05.10**: 新增 **五线谱视图**。钢琴卷帘现已支持完整的标准乐谱显示模式。您可以通过钢琴卷帘工具栏中的切换按钮在 Piano Roll 和 Sheet Music 视图之间切换。在五线谱模式下,音符会通过 VexFlow 排版,并支持基于当前乐器自动选择谱号(高音或低音)、显示调号、自动符杠分组、跨小节连音线,以及可配置的音值量化。启用 **Track Scope** 后,整条音轨上的所有 MIDI 区域会以连续谱面的形式显示,而不再局限于单个区域。
@@ -90,9 +90,9 @@ K.G.Studio 是一款轻量、现代化的 DAW,完全运行于浏览器中,
## 快速开始
### 配置 K.G.Studio Musician Assistant
### 配置 K.G.Studio 音乐创作助手
使用 K.G.Studio Musician Assistant 有两种方式:一种是 **Local LLM (Browser)**,完全在浏览器中运行,无需 API Key、无需费用,且数据不会离开您的设备;另一种是使用 **外部 LLM 提供方**,以获得更高质量的回复。
使用 K.G.Studio 音乐创作助手有两种方式:一种是 **Local LLM (Browser)**,完全在浏览器中运行,无需 API Key、无需费用,且数据不会离开您的设备;另一种是使用 **外部 LLM 提供方**,以获得更高质量的回复。
#### 方案 ALocal LLM (Browser) — 无需 API Key ✦
@@ -131,7 +131,7 @@ K.G.Studio 可以借助 WebGPU 加速,在浏览器中直接运行 **Gemma 4 E4
- 拖动音符边缘可调整长度;拖动音符主体可移动已选音符。
- 使用钢琴卷帘工具栏右上角的 Snapping 功能可将编辑量化到网格。
### 使用 K.G.Studio Musician Assistant
### 使用 K.G.Studio 音乐创作助手
- 选中您希望助手处理的音乐区域,在聊天框中输入提示词;按 Enter 发送,按 Shift+Enter 插入换行。
- agent 会自动处理您的请求,并调用工具,在所选区域范围内执行修改。某些任务可能需要一轮或多轮对话才能完成。
@@ -143,7 +143,7 @@ K.G.Studio 可以借助 WebGPU 加速,在浏览器中直接运行 **Gemma 4 E4
您可以在[这里](./docs/USER_GUIDE.md)查看详细用户指南。
### 亮点功能
- **K.G.Studio Musician Assistant**:与由 LLM 驱动的 AI agent 进行对话;它会自动执行工具,对您所选区域内的音乐内容进行编辑。
- **K.G.Studio 音乐创作助手**:与由 LLM 驱动的 AI agent 进行对话;它会自动执行工具,对您所选区域内的音乐内容进行编辑。
- **浏览器内嵌 LLM,无需 API Key**:通过 WebGPULiteRT-LM)在浏览器中直接运行 **Gemma 4 E4B**。无 API 调用、无费用、数据不离开您的设备。模型只需下载一次,随后会缓存在本地。
- **浏览器内嵌分轨,无需服务器**:使用 **UVR-MDX-NET-Inst_HQ_3**2-stemVocals / Instrumental)或 **Demucs htdemucs_4s**4-stemVocals / Drums / Bass / Others)将任意音频区域拆分为 stems。两者都完全在浏览器中通过 ONNX Runtime WebGPU 运行,无需 K.G.One 服务器。
- **音频和弦检测**:为音频区域打开钢琴卷帘后运行 **Detect Chords**,即可通过零依赖 FFT 流水线自动分析录音并填充全局 Chord Track,同时支持灵敏度、稳定性和七和弦检测等配置。
@@ -257,10 +257,10 @@ K.G.Studio 会从 `./public/config.json` 加载默认配置(内部也提供回
## AI 助手
### 使用 K.G.Studio Musician Assistant
### 使用 K.G.Studio 音乐创作助手
- 请先按照前文说明完成 LLM 提供方设置。
- 您可以在右侧找到 K.G.Studio Musician Assistant 聊天框。如果当前未显示,可点击工具栏中的 Chat 🗨️ 按钮打开。
- 您可以在右侧找到 K.G.Studio 音乐创作助手聊天框。如果当前未显示,可点击工具栏中的 Chat 🗨️ 按钮打开。
- 选中您希望助手处理的区域,在聊天框中输入提示词;按 Enter 发送,按 Shift+Enter 插入换行。
- agent 会自动处理您的请求,并调用工具,在所选区域范围内执行修改。某些任务可能需要一轮或多轮对话才能完成。
- 请注意,AI 也可能出错,因此您应始终检查结果,并在必要时自行调整。您也可以通过撤销/重做回退修改。
+2 -2
View File
@@ -1,6 +1,6 @@
## 帮助
欢迎使用 **K.G.Studio Musician Assistant**。这是您的 AI 音乐创作与编曲助手,可以帮助您构思、编排和修改音乐内容。
欢迎使用 **K.G.Studio 音乐创作助手**。这是您的 AI 音乐创作与编曲助手,可以帮助您构思、编排和修改音乐内容。
您可以选择使用 **内置本地浏览器 LLM**(无需 API Key),也可以配置外部 LLM 提供方。下面是常用的入门方式。
@@ -123,4 +123,4 @@ OpenRouter 提供统一接口,可访问多个语言模型提供方的模型,
K.G.Studio 不提供也不托管上述任何模型,也不隶属于任何模型提供方。所有数据默认都保存在您的本地设备中;您向第三方模型提供方发送的任何数据,都由您自行负责。
祝您使用 K.G.Studio Musician Assistant 创作愉快!
祝您使用 K.G.Studio 音乐创作助手创作愉快!
+1 -1
View File
@@ -8,4 +8,4 @@
- 输入 `/help` 查看帮助说明。
- 输入 `/hotkeys` 查看快捷键指南。
祝你在 K.G.Studio Musician Assistant 中创作顺利。
祝你在 K.G.Studio 音乐创作助手中创作顺利。
+1 -1
View File
@@ -1,6 +1,6 @@
## 本地 LLM 模式
欢迎使用 **K.G.Studio Musician Assistant** 的本地 LLM 模式。
欢迎使用 **K.G.Studio 音乐创作助手** 的本地 LLM 模式。
- 无需外部 API 调用。所有内容都直接在您的浏览器中运行,不会产生额外 API 成本。
- 此模式通过 **LiteRT-LM****WebGPU** 加速运行 **Gemma 4 E4B**
+2 -2
View File
@@ -1,9 +1,9 @@
## 欢迎
欢迎使用 **K.G.Studio Musician Assistant**。这是您在浏览器中的 AI 音乐创作与编曲助手。
欢迎使用 **K.G.Studio 音乐创作助手**。这是您在浏览器中的 AI 音乐创作与编曲助手。
### 快速开始
**配置 K.G.Studio Musician Assistant**
**配置 K.G.Studio 音乐创作助手**
- [点击这里获取免费的 OpenRouter API Key](https://openrouter.ai/keys)(您可能需要一个 OpenRouter 账号)。
- 进入 **设置 ⚙️ → 通用 → LLM 提供方**,选择 **OpenAI 兼容服务**
-**OpenAI 兼容服务 → 密钥** 中粘贴您的 Key。注意:在非 localhost 环境下,Key 默认不会持久化,刷新后需要重新输入,请妥善保管。
+147
View File
@@ -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();
});
});
+3 -1
View File
@@ -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
File diff suppressed because it is too large Load Diff
+152
View File
@@ -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.10.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.',
};
+152
View File
@@ -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.10.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', '');
+4 -2
View File
@@ -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': {
try {
const configManager = ConfigManager.instance();
try {
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,