From b5546aa230d1c9f5ee48e26d8034f1789ea5b4e5 Mon Sep 17 00:00:00 2001 From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com> Date: Sat, 30 May 2026 20:29:10 -0700 Subject: [PATCH] feat: added translation for Music Generator panel; provided `K.G.Studio Musician Assistant` a Chinese name --- README-zh_cn.md | 18 +- public/chat/help-zh_cn.md | 4 +- public/chat/welcome_again-zh_cn.md | 2 +- public/chat/welcome_local_llm-zh_cn.md | 2 +- public/chat/welcome_new-zh_cn.md | 4 +- src/components/ChatBox.test.tsx | 147 ++++++ src/components/ChatBox.tsx | 4 +- src/components/KGOnePanel.tsx | 474 +++++++++--------- src/i18n/messages/en_us.ts | 152 ++++++ src/i18n/messages/zh_cn.ts | 152 ++++++ .../messageFilter/UserMessageFilter.test.ts | 13 + src/util/messageFilter/UserMessageFilter.ts | 6 +- 12 files changed, 711 insertions(+), 267 deletions(-) create mode 100644 src/components/ChatBox.test.tsx diff --git a/README-zh_cn.md b/README-zh_cn.md index 929c611..de4c4e1 100644 --- a/README-zh_cn.md +++ b/README-zh_cn.md @@ -17,9 +17,9 @@ ## 什么是 K.G.Studio? -K.G.Studio 是一款轻量、现代化的 DAW,完全运行于浏览器中,并以 **K.G.Studio Musician Assistant** 为核心。它提供基于 Tone.js sampler 的真实乐器回放、钢琴卷帘编辑器、支持完整撤销/重做的音轨与区域管理、基于 OPFS(Origin Private File System)的项目持久化、可配置的设置面板,以及可执行工具的内置 AI 助手。 +K.G.Studio 是一款轻量、现代化的 DAW,完全运行于浏览器中,并以 **K.G.Studio 音乐创作助手** 为核心。它提供基于 Tone.js sampler 的真实乐器回放、钢琴卷帘编辑器、支持完整撤销/重做的音轨与区域管理、基于 OPFS(Origin Private File System)的项目持久化、可配置的设置面板,以及可执行工具的内置 AI 助手。 -**K.G.Studio Musician Assistant** 是一个面向和声、编曲与音符编辑的 AI 助手,但并不负责整首作品的全自动作曲。 +**K.G.Studio 音乐创作助手** 是一个面向和声、编曲与音符编辑的 AI 助手,但并不负责整首作品的全自动作曲。
K.G.One Logo @@ -35,7 +35,7 @@ K.G.Studio 是一款轻量、现代化的 DAW,完全运行于浏览器中, - 新增 **带自动对齐拍点的速度检测** 功能。您可以为音频区域打开钢琴卷帘窗口,然后在工具栏点击“...” -> **Detect Tempo**,分析音频 BPM,并可选择将项目中的 Tempo Track 区段自动重新对齐。 - 新增 **Demucs 4S** 作为第二个本地浏览器内嵌分轨模型。现有的双 stem UVR-MDX-NET 模型之外,又加入了四 stem 的 `htdemucs_4s` 模型(约 172 MB,vocals / 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** 面板(✦ 按钮),下载模型一次后,即可完全在本机将人声与伴奏分离。以上两项功能都要求浏览器支持 WebGPU(Chrome 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** 面板(✦ 按钮),下载模型一次后,即可完全在本机将人声与伴奏分离。以上两项功能都要求浏览器支持 WebGPU(Chrome 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 提供方**,以获得更高质量的回复。 #### 方案 A:Local 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**:通过 WebGPU(LiteRT-LM)在浏览器中直接运行 **Gemma 4 E4B**。无 API 调用、无费用、数据不离开您的设备。模型只需下载一次,随后会缓存在本地。 - **浏览器内嵌分轨,无需服务器**:使用 **UVR-MDX-NET-Inst_HQ_3**(2-stem:Vocals / Instrumental)或 **Demucs htdemucs_4s**(4-stem:Vocals / 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 也可能出错,因此您应始终检查结果,并在必要时自行调整。您也可以通过撤销/重做回退修改。 diff --git a/public/chat/help-zh_cn.md b/public/chat/help-zh_cn.md index 993ebac..cebab8e 100644 --- a/public/chat/help-zh_cn.md +++ b/public/chat/help-zh_cn.md @@ -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 音乐创作助手创作愉快! diff --git a/public/chat/welcome_again-zh_cn.md b/public/chat/welcome_again-zh_cn.md index 2989906..eea57df 100644 --- a/public/chat/welcome_again-zh_cn.md +++ b/public/chat/welcome_again-zh_cn.md @@ -8,4 +8,4 @@ - 输入 `/help` 查看帮助说明。 - 输入 `/hotkeys` 查看快捷键指南。 -祝你在 K.G.Studio Musician Assistant 中创作顺利。 +祝你在 K.G.Studio 音乐创作助手中创作顺利。 diff --git a/public/chat/welcome_local_llm-zh_cn.md b/public/chat/welcome_local_llm-zh_cn.md index 4d14bd3..a9a5979 100644 --- a/public/chat/welcome_local_llm-zh_cn.md +++ b/public/chat/welcome_local_llm-zh_cn.md @@ -1,6 +1,6 @@ ## 本地 LLM 模式 -欢迎使用 **K.G.Studio Musician Assistant** 的本地 LLM 模式。 +欢迎使用 **K.G.Studio 音乐创作助手** 的本地 LLM 模式。 - 无需外部 API 调用。所有内容都直接在您的浏览器中运行,不会产生额外 API 成本。 - 此模式通过 **LiteRT-LM** 和 **WebGPU** 加速运行 **Gemma 4 E4B**。 diff --git a/public/chat/welcome_new-zh_cn.md b/public/chat/welcome_new-zh_cn.md index 0c12336..8343882 100644 --- a/public/chat/welcome_new-zh_cn.md +++ b/public/chat/welcome_new-zh_cn.md @@ -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 默认不会持久化,刷新后需要重新输入,请妥善保管。 diff --git a/src/components/ChatBox.test.tsx b/src/components/ChatBox.test.tsx new file mode 100644 index 0000000..2265cb6 --- /dev/null +++ b/src/components/ChatBox.test.tsx @@ -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 }) =>
{content}
, + AssistantMessage: ({ content }: { content: string }) =>
{content}
, +})); + +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( + undefined, + t: (key, params) => translate(key, params, resolvedLocale), + }} + > + + , + ); +} + +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(); + }); +}); diff --git a/src/components/ChatBox.tsx b/src/components/ChatBox.tsx index d5aab5c..0881490 100644 --- a/src/components/ChatBox.tsx +++ b/src/components/ChatBox.tsx @@ -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 = ({ isVisible }) => { + const { t } = useI18n(); const [inputValue, setInputValue] = useState(''); const textareaRef = useRef(null); @@ -345,7 +347,7 @@ const ChatBox: React.FC = ({ isVisible }) => { return (
-

K.G.Studio Musician Assistant

+

{t('assistant.displayName')}

{isProcessing && (