From a9969ad361083743130cd6b45e0fcc104b970ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E7=8C=AB?= Date: Sun, 15 Mar 2026 11:14:56 +0900 Subject: [PATCH] refactor: enhance setup page with translation support and default configurations - Added translation support for various text elements using `useTranslation`. - Created default personality and emoji configurations to streamline setup. - Updated step titles and descriptions to use translated strings. - Improved validation messages to be translatable. - Refactored loading and success/error messages for better user feedback. - Enhanced UI structure for better readability and maintainability. --- dashboard/src/i18n/locales/en.json | 214 +++++++++ dashboard/src/i18n/locales/ja.json | 214 +++++++++ dashboard/src/i18n/locales/ko.json | 214 +++++++++ dashboard/src/i18n/locales/zh.json | 214 +++++++++ dashboard/src/routes/setup/StepForms.tsx | 416 ++++++++--------- dashboard/src/routes/setup/index.tsx | 540 +++++++++++------------ 6 files changed, 1335 insertions(+), 477 deletions(-) diff --git a/dashboard/src/i18n/locales/en.json b/dashboard/src/i18n/locales/en.json index 9ed66798..9be16280 100644 --- a/dashboard/src/i18n/locales/en.json +++ b/dashboard/src/i18n/locales/en.json @@ -379,6 +379,220 @@ "switchToLight": "Switch to light mode", "switchToDark": "Switch to dark mode" }, + "setupPage": { + "steps": { + "botBasic": { + "title": "Bot Basics", + "description": "Configure the bot's basic information" + }, + "personality": { + "title": "Personality", + "description": "Define the bot's personality and speaking style" + }, + "emoji": { + "title": "Emoji", + "description": "Configure emoji-related settings" + }, + "other": { + "title": "Other Settings", + "description": "Configure tools, emotion system, and more" + }, + "siliconFlow": { + "title": "API Setup", + "description": "Configure the SiliconFlow API key" + } + }, + "loading": { + "title": "Loading configuration...", + "description": "Reading existing configuration" + }, + "header": { + "title": "Initial Setup Wizard", + "description": "Let's complete the initial setup for {{appName}}" + }, + "progress": { + "stepCounter": "Step {{current}} / {{total}}" + }, + "validation": { + "selectPlatform": "Please select a platform", + "enterNickname": "Please enter a nickname", + "enterQqAccount": "Please enter a QQ account", + "enterAccountId": "Please enter an account ID" + }, + "toast": { + "loadFailedTitle": "Failed to load configuration", + "loadFailedDescription": "Unable to load the existing configuration. Default values will be used.", + "saveSuccessTitle": "Saved successfully", + "saveSuccessDescription": "{{step}} configuration has been saved", + "saveFailedTitle": "Save failed", + "validationFailedTitle": "Validation failed", + "completeSuccessTitle": "Setup complete", + "completeSuccessDescription": "{{appName}} is restarting to apply the new configuration...", + "completeFailedTitle": "Setup failed", + "skipFailedTitle": "Failed to skip the setup wizard", + "unknownError": "Unknown error" + }, + "actions": { + "previous": "Previous", + "next": "Next", + "skip": "Skip Wizard", + "complete": "Complete Setup", + "saving": "Saving...", + "completing": "Completing..." + }, + "skipDialog": { + "title": "Skip the setup wizard?", + "description": "You can reopen the setup wizard anytime from System Settings. Are you sure you want to skip it?", + "confirm": "Skip Anyway" + }, + "footer": "You can change these settings anytime in Settings", + "defaults": { + "personality": { + "personality": "She is a sophomore college student who spends time on Tieba.", + "replyStyle": "Keep replies plain and concise, speak Chinese, and avoid deliberately emphasizing any academic background. You can refer to the reply styles commonly seen on Tieba, Zhihu, and Weibo.", + "interest": "Interested in technology, games, anime, and everyday topics, and dislikes topics that are overly heavy or serious.", + "planStyle": "1. Consider **every action** in **all** available actions and use it if its conditions match the current conversation.\n2. Do not repeat content that has already been executed.\n3. Control how often you speak and avoid replying too frequently.\n4. If someone seems annoyed with you, reduce your replies.\n5. If someone attacks you or becomes emotional, respond appropriately.", + "privatePlanStyle": "1. Consider **every action** in **all** available actions and use it if its conditions match the current conversation.\n2. Do not repeat content that has already been executed.\n3. If a sentence has already been replied to, do not reply to it again." + }, + "emoji": { + "filtrationPrompt": "Appropriate and safe for general audiences" + } + }, + "forms": { + "botBasic": { + "platform": { + "label": "Platform *", + "placeholder": "Select a platform", + "description": "Choose the platform where the bot runs", + "options": { + "custom": "Other Platform" + } + }, + "customPlatform": { + "label": "Platform Name *", + "placeholder": "Enter a platform name, such as matrix" + }, + "qqAccount": { + "label": "QQ Account *", + "placeholder": "Enter the bot's QQ account", + "description": "The QQ account used to log in the bot" + }, + "primaryAccount": { + "label": "Account ID *", + "placeholder": "Enter the bot's account ID", + "description": "The bot's account identifier on this platform" + }, + "nickname": { + "label": "Nickname *", + "placeholder": "Enter the bot's nickname", + "description": "The bot's primary display name" + }, + "alias": { + "label": "Aliases", + "placeholder": "Type an alias and press Enter to add it", + "add": "Add", + "description": "Other names for the bot. You can add multiple aliases.", + "remove": "Remove alias {{alias}}" + } + }, + "personality": { + "personality": { + "label": "Personality Traits *", + "placeholder": "Describe the bot's personality and identity traits (recommended within 120 characters)", + "description": "Example: a sophomore college girl who browses Tieba" + }, + "replyStyle": { + "label": "Reply Style *", + "placeholder": "Describe how the bot speaks and expresses itself", + "description": "Example: keep replies plain and concise, speak Chinese, and refer to styles seen on Tieba, Zhihu, and Weibo" + }, + "interest": { + "label": "Interests *", + "placeholder": "Describe the topics the bot is interested in", + "description": "This affects which topics the bot is more likely to respond to" + }, + "planStyle": { + "label": "Group Chat Rules *", + "placeholder": "The bot's behavior style and rules in group chats", + "description": "Defines how the bot acts in group chats, such as reply frequency and conditions" + }, + "privatePlanStyle": { + "label": "Private Chat Rules *", + "placeholder": "The bot's behavior style and rules in private chats", + "description": "Defines how the bot behaves in private chats" + } + }, + "emoji": { + "emojiChance": { + "label": "Emoji Activation Chance", + "description": "How likely the bot is to send an emoji" + }, + "maxRegNum": { + "label": "Maximum Emoji Count", + "description": "The maximum number of emojis the bot can store" + }, + "doReplace": { + "label": "Replace When Full", + "description": "When enabled, old emojis will be removed; when disabled, new emojis will no longer be collected" + }, + "checkInterval": { + "label": "Check Interval (Minutes)", + "description": "How often to check emoji registration, damage, and deletion" + }, + "stealEmoji": { + "label": "Collect Emojis", + "description": "Allow the bot to keep certain emojis for itself" + }, + "contentFiltration": { + "label": "Enable Emoji Filtering", + "description": "Only save emojis that meet the requirements" + }, + "filtrationPrompt": { + "label": "Filter Requirements", + "placeholder": "For example: appropriate and safe for general audiences", + "description": "Describe what kind of emojis should be allowed" + } + }, + "other": { + "enableTool": { + "label": "Enable Tool System", + "description": "Allow the bot to use tools to enhance its capabilities" + }, + "allGlobal": { + "label": "Enable Global Slang Mode", + "description": "Allow the bot to learn and use group-specific slang" + } + }, + "siliconFlow": { + "about": { + "title": "About SiliconFlow", + "description": "SiliconFlow provides broad model coverage, including DeepSeek V3, Qwen, vision models, speech recognition, and embedding models. A single API key unlocks all MaiBot features.", + "link": "Get an API key from SiliconFlow" + }, + "apiKey": { + "label": "SiliconFlow API Key *", + "description": "Enter your SiliconFlow API key. Once provided, MaiBot will automatically configure all required models.", + "show": "Show API key", + "hide": "Hide API key" + }, + "autoConfig": { + "title": "The following models will be configured automatically:", + "items": { + "deepseek": "DeepSeek V3 - primary chat and tool model", + "qwen3": "Qwen3 30B - frequent small tasks and tool calls", + "qwen3Vl": "Qwen3 VL 30B - image recognition", + "senseVoice": "SenseVoice - speech recognition", + "bgeM3": "BGE-M3 - text embeddings", + "lpmm": "Knowledge-base-related models (LPMM)" + } + }, + "hint": { + "title": "Tip: ", + "description": "After finishing the wizard, you can add more API providers and models in \"System Settings -> Model Config\"." + } + } + } + }, "common": { "loading": "Loading...", "error": "Error", diff --git a/dashboard/src/i18n/locales/ja.json b/dashboard/src/i18n/locales/ja.json index 6da99281..e33e0205 100644 --- a/dashboard/src/i18n/locales/ja.json +++ b/dashboard/src/i18n/locales/ja.json @@ -379,6 +379,220 @@ "switchToLight": "ライトモードに切り替える", "switchToDark": "ダークモードに切り替える" }, + "setupPage": { + "steps": { + "botBasic": { + "title": "Bot基本設定", + "description": "ボットの基本情報を設定します" + }, + "personality": { + "title": "人格設定", + "description": "ボットの性格や話し方を定義します" + }, + "emoji": { + "title": "絵文字パック", + "description": "絵文字パック関連の設定を行います" + }, + "other": { + "title": "その他の設定", + "description": "ツールや感情システムなどを設定します" + }, + "siliconFlow": { + "title": "API設定", + "description": "SiliconFlow API キーを設定します" + } + }, + "loading": { + "title": "設定を読み込み中...", + "description": "既存の設定を読み取っています" + }, + "header": { + "title": "初回セットアップウィザード", + "description": "{{appName}} の初期設定を一緒に完了しましょう" + }, + "progress": { + "stepCounter": "ステップ {{current}} / {{total}}" + }, + "validation": { + "selectPlatform": "プラットフォームを選択してください", + "enterNickname": "ニックネームを入力してください", + "enterQqAccount": "QQ アカウントを入力してください", + "enterAccountId": "アカウント ID を入力してください" + }, + "toast": { + "loadFailedTitle": "設定の読み込みに失敗しました", + "loadFailedDescription": "既存の設定を読み込めなかったため、デフォルト値を使用します", + "saveSuccessTitle": "保存しました", + "saveSuccessDescription": "{{step}} を保存しました", + "saveFailedTitle": "保存に失敗しました", + "validationFailedTitle": "入力内容を確認してください", + "completeSuccessTitle": "設定が完了しました", + "completeSuccessDescription": "新しい設定を反映するために {{appName}} を再起動しています...", + "completeFailedTitle": "設定の完了に失敗しました", + "skipFailedTitle": "セットアップのスキップに失敗しました", + "unknownError": "不明なエラー" + }, + "actions": { + "previous": "前へ", + "next": "次へ", + "skip": "ウィザードをスキップ", + "complete": "設定を完了", + "saving": "保存中...", + "completing": "完了処理中..." + }, + "skipDialog": { + "title": "セットアップウィザードをスキップしますか?", + "description": "システム設定からいつでも再度セットアップウィザードを開けます。スキップしてもよろしいですか?", + "confirm": "スキップする" + }, + "footer": "これらの設定はいつでも設定画面から変更できます", + "defaults": { + "personality": { + "personality": "女子大生で、現在大学2年生。掲示板を見るのが好き。", + "replyStyle": "返信は淡々と、短めにし、中国語で話してください。自分の学科背景をわざと強調しないでください。Tieba、Zhihu、Weibo の返信スタイルを参考にできます。", + "interest": "技術、ゲーム、アニメ、日常の話題に興味があり、重すぎたり厳粛すぎたりする話題は好みません。", + "planStyle": "1. 利用可能な **すべて** の action の **各アクション** が現在の条件に合うか考え、会話内容に合えば使用してください\n2. 同じ内容がすでに実行されている場合は繰り返さないでください\n3. 発言頻度を調整し、発言しすぎないでください\n4. 誰かがあなたにうんざりしている場合は、返信を減らしてください\n5. 誰かがあなたを攻撃したり感情的になったりした場合は、適切に対応してください", + "privatePlanStyle": "1. 利用可能な **すべて** の action の **各アクション** が現在の条件に合うか考え、会話内容に合えば使用してください\n2. 同じ内容がすでに実行されている場合は繰り返さないでください\n3. すでに返信した文には再度返信しないでください" + }, + "emoji": { + "filtrationPrompt": "公序良俗に反しないこと" + } + }, + "forms": { + "botBasic": { + "platform": { + "label": "プラットフォーム *", + "placeholder": "プラットフォームを選択", + "description": "ボットが動作するプラットフォームを選択します", + "options": { + "custom": "その他のプラットフォーム" + } + }, + "customPlatform": { + "label": "プラットフォーム名 *", + "placeholder": "matrix などのプラットフォーム名を入力" + }, + "qqAccount": { + "label": "QQ アカウント *", + "placeholder": "ボットの QQ アカウントを入力", + "description": "ボットのログインに使用する QQ アカウントです" + }, + "primaryAccount": { + "label": "アカウント ID *", + "placeholder": "ボットのアカウント ID を入力", + "description": "このプラットフォーム上でのボットのアカウント識別子です" + }, + "nickname": { + "label": "ニックネーム *", + "placeholder": "ボットのニックネームを入力", + "description": "ボットの主な呼び名です" + }, + "alias": { + "label": "別名", + "placeholder": "別名を入力して Enter で追加", + "add": "追加", + "description": "ボットの他の呼び名を複数追加できます", + "remove": "別名 {{alias}} を削除" + } + }, + "personality": { + "personality": { + "label": "人格特性 *", + "placeholder": "ボットの人格や設定を説明してください(120文字以内推奨)", + "description": "例:大学2年生の女子大生で、Tieba をよく見ている" + }, + "replyStyle": { + "label": "話し方 *", + "placeholder": "ボットの話し方や表現の癖を説明してください", + "description": "例:返信は淡々と短めにし、中国語で話し、Tieba・Zhihu・Weibo の雰囲気を参考にする" + }, + "interest": { + "label": "興味 *", + "placeholder": "ボットが興味を持つ話題を説明してください", + "description": "どの話題に返信しやすくなるかに影響します" + }, + "planStyle": { + "label": "グループチャットのルール *", + "placeholder": "グループチャットでの行動方針やルール", + "description": "返信頻度や条件など、グループチャットでの振る舞いを定義します" + }, + "privatePlanStyle": { + "label": "個別チャットのルール *", + "placeholder": "個別チャットでの行動方針やルール", + "description": "個別チャットでの振る舞いを定義します" + } + }, + "emoji": { + "emojiChance": { + "label": "絵文字パック発動確率", + "description": "ボットが絵文字を送る確率です" + }, + "maxRegNum": { + "label": "最大絵文字数", + "description": "ボットが保存できる絵文字の最大数です" + }, + "doReplace": { + "label": "上限到達時に置き換える", + "description": "有効にすると古い絵文字を削除し、無効にすると新しい絵文字を収集しません" + }, + "checkInterval": { + "label": "確認間隔(分)", + "description": "絵文字の登録、破損、削除を確認する間隔です" + }, + "stealEmoji": { + "label": "絵文字を収集する", + "description": "一部の絵文字をボットが自分用に保存できるようにします" + }, + "contentFiltration": { + "label": "絵文字フィルタリングを有効にする", + "description": "条件に合う絵文字だけを保存します" + }, + "filtrationPrompt": { + "label": "フィルタ条件", + "placeholder": "例:公序良俗に反しないこと", + "description": "保存する絵文字の条件を説明してください" + } + }, + "other": { + "enableTool": { + "label": "ツールシステムを有効にする", + "description": "ボットが各種ツールを使って機能を拡張できるようにします" + }, + "allGlobal": { + "label": "グローバルスラングモードを有効にする", + "description": "グループ内のスラングを学習して使えるようにします" + } + }, + "siliconFlow": { + "about": { + "title": "SiliconFlow について", + "description": "SiliconFlow は DeepSeek V3、Qwen、ビジョンモデル、音声認識、埋め込みモデルなど幅広いモデルを提供します。API Key が1つあれば MaiBot の全機能を利用できます。", + "link": "SiliconFlow で API Key を取得する" + }, + "apiKey": { + "label": "SiliconFlow API Key *", + "description": "SiliconFlow の API Key を入力してください。入力後、MaiBot が必要なモデルを自動設定します。", + "show": "API Key を表示", + "hide": "API Key を隠す" + }, + "autoConfig": { + "title": "以下のモデルが自動設定されます:", + "items": { + "deepseek": "DeepSeek V3 - メインの会話・ツールモデル", + "qwen3": "Qwen3 30B - 頻繁な小タスクとツール呼び出し", + "qwen3Vl": "Qwen3 VL 30B - 画像認識", + "senseVoice": "SenseVoice - 音声認識", + "bgeM3": "BGE-M3 - テキスト埋め込み", + "lpmm": "知識ベース関連モデル (LPMM)" + } + }, + "hint": { + "title": "ヒント:", + "description": "ウィザード完了後は、「システム設定 -> モデル設定」でさらに API プロバイダーやモデルを追加できます。" + } + } + } + }, "common": { "loading": "読み込み中...", "error": "エラー", diff --git a/dashboard/src/i18n/locales/ko.json b/dashboard/src/i18n/locales/ko.json index bacaab72..1b539271 100644 --- a/dashboard/src/i18n/locales/ko.json +++ b/dashboard/src/i18n/locales/ko.json @@ -379,6 +379,220 @@ "switchToLight": "라이트 모드로 전환", "switchToDark": "다크 모드로 전환" }, + "setupPage": { + "steps": { + "botBasic": { + "title": "Bot 기본 설정", + "description": "봇의 기본 정보를 설정합니다" + }, + "personality": { + "title": "성격 설정", + "description": "봇의 성격과 말투를 정의합니다" + }, + "emoji": { + "title": "이모지 팩", + "description": "이모지 관련 설정을 구성합니다" + }, + "other": { + "title": "기타 설정", + "description": "도구, 감정 시스템 등의 설정을 구성합니다" + }, + "siliconFlow": { + "title": "API 설정", + "description": "SiliconFlow API 키를 설정합니다" + } + }, + "loading": { + "title": "설정을 불러오는 중...", + "description": "기존 설정을 읽고 있습니다" + }, + "header": { + "title": "초기 설정 마법사", + "description": "{{appName}}의 초기 설정을 함께 완료해 봅시다" + }, + "progress": { + "stepCounter": "단계 {{current}} / {{total}}" + }, + "validation": { + "selectPlatform": "플랫폼을 선택해 주세요", + "enterNickname": "닉네임을 입력해 주세요", + "enterQqAccount": "QQ 계정을 입력해 주세요", + "enterAccountId": "계정 ID를 입력해 주세요" + }, + "toast": { + "loadFailedTitle": "설정 불러오기에 실패했습니다", + "loadFailedDescription": "기존 설정을 불러올 수 없어 기본값을 사용합니다", + "saveSuccessTitle": "저장되었습니다", + "saveSuccessDescription": "{{step}} 설정이 저장되었습니다", + "saveFailedTitle": "저장에 실패했습니다", + "validationFailedTitle": "입력값을 확인해 주세요", + "completeSuccessTitle": "설정이 완료되었습니다", + "completeSuccessDescription": "새 설정을 적용하기 위해 {{appName}}을(를) 재시작하는 중입니다...", + "completeFailedTitle": "설정 완료에 실패했습니다", + "skipFailedTitle": "설정 마법사 건너뛰기에 실패했습니다", + "unknownError": "알 수 없는 오류" + }, + "actions": { + "previous": "이전", + "next": "다음", + "skip": "마법사 건너뛰기", + "complete": "설정 완료", + "saving": "저장 중...", + "completing": "완료 중..." + }, + "skipDialog": { + "title": "설정 마법사를 건너뛸까요?", + "description": "시스템 설정에서 언제든지 다시 설정 마법사를 열 수 있습니다. 정말 건너뛰시겠습니까?", + "confirm": "건너뛰기" + }, + "footer": "이 설정들은 언제든지 설정 화면에서 변경할 수 있습니다", + "defaults": { + "personality": { + "personality": "여자 대학생이며 현재 2학년이고, Tieba 같은 커뮤니티를 자주 봅니다.", + "replyStyle": "답변은 담백하고 짧게 하며 중국어로 말하세요. 자신의 학과 배경을 일부러 강조하지 마세요. Tieba, Zhihu, Weibo의 답변 스타일을 참고할 수 있습니다.", + "interest": "기술, 게임, 애니메이션, 일상적인 주제에 관심이 있고, 너무 무겁거나 엄숙한 주제는 좋아하지 않습니다.", + "planStyle": "1. 사용 가능한 **모든** action 의 **각 동작** 이 현재 조건에 맞는지 검토하고, 대화 내용에 맞으면 사용하세요\n2. 같은 내용이 이미 실행되었다면 반복하지 마세요\n3. 발화 빈도를 조절하고 너무 자주 말하지 마세요\n4. 누군가 당신을 귀찮아하는 것 같다면 답장을 줄이세요\n5. 누군가 당신을 공격하거나 감정적으로 반응하면 적절하게 대응하세요", + "privatePlanStyle": "1. 사용 가능한 **모든** action 의 **각 동작** 이 현재 조건에 맞는지 검토하고, 대화 내용에 맞으면 사용하세요\n2. 같은 내용이 이미 실행되었다면 반복하지 마세요\n3. 이미 답한 문장에는 다시 답하지 마세요" + }, + "emoji": { + "filtrationPrompt": "공공질서와 미풍양속에 어긋나지 않음" + } + }, + "forms": { + "botBasic": { + "platform": { + "label": "플랫폼 *", + "placeholder": "플랫폼 선택", + "description": "봇이 실행될 플랫폼을 선택합니다", + "options": { + "custom": "기타 플랫폼" + } + }, + "customPlatform": { + "label": "플랫폼 이름 *", + "placeholder": "예: matrix 와 같은 플랫폼 이름 입력" + }, + "qqAccount": { + "label": "QQ 계정 *", + "placeholder": "봇의 QQ 계정을 입력하세요", + "description": "봇 로그인에 사용하는 QQ 계정입니다" + }, + "primaryAccount": { + "label": "계정 ID *", + "placeholder": "봇의 계정 ID를 입력하세요", + "description": "이 플랫폼에서 봇을 식별하는 계정 ID입니다" + }, + "nickname": { + "label": "닉네임 *", + "placeholder": "봇의 닉네임을 입력하세요", + "description": "봇의 대표 호칭입니다" + }, + "alias": { + "label": "별칭", + "placeholder": "별칭을 입력하고 Enter 로 추가", + "add": "추가", + "description": "봇의 다른 호칭을 여러 개 추가할 수 있습니다", + "remove": "별칭 {{alias}} 제거" + } + }, + "personality": { + "personality": { + "label": "성격 특성 *", + "placeholder": "봇의 성격과 설정을 설명해 주세요 (권장 120자 이내)", + "description": "예: 대학 2학년 여자 대학생으로 Tieba 를 자주 본다" + }, + "replyStyle": { + "label": "말투 *", + "placeholder": "봇이 말하는 방식과 표현 습관을 설명해 주세요", + "description": "예: 답변은 담백하고 짧게, 중국어로 말하며 Tieba, Zhihu, Weibo 스타일을 참고한다" + }, + "interest": { + "label": "관심사 *", + "placeholder": "봇이 관심을 가지는 주제를 설명해 주세요", + "description": "어떤 주제에 더 잘 반응할지에 영향을 줍니다" + }, + "planStyle": { + "label": "그룹 채팅 규칙 *", + "placeholder": "그룹 채팅에서의 행동 스타일과 규칙", + "description": "답장 빈도와 조건 등 그룹 채팅에서의 행동 방식을 정의합니다" + }, + "privatePlanStyle": { + "label": "개인 채팅 규칙 *", + "placeholder": "개인 채팅에서의 행동 스타일과 규칙", + "description": "개인 채팅에서의 행동 방식을 정의합니다" + } + }, + "emoji": { + "emojiChance": { + "label": "이모지 활성화 확률", + "description": "봇이 이모지를 보낼 확률입니다" + }, + "maxRegNum": { + "label": "최대 이모지 수", + "description": "봇이 저장할 수 있는 이모지의 최대 개수입니다" + }, + "doReplace": { + "label": "최대 수 도달 시 교체", + "description": "켜면 오래된 이모지를 삭제하고, 끄면 새 이모지를 더 이상 수집하지 않습니다" + }, + "checkInterval": { + "label": "확인 간격 (분)", + "description": "이모지 등록, 손상, 삭제를 확인하는 간격입니다" + }, + "stealEmoji": { + "label": "이모지 수집", + "description": "일부 이모지를 봇이 자신의 것으로 저장할 수 있게 합니다" + }, + "contentFiltration": { + "label": "이모지 필터링 사용", + "description": "조건에 맞는 이모지만 저장합니다" + }, + "filtrationPrompt": { + "label": "필터 조건", + "placeholder": "예: 공공질서와 미풍양속에 어긋나지 않음", + "description": "저장할 이모지가 충족해야 하는 조건을 설명해 주세요" + } + }, + "other": { + "enableTool": { + "label": "도구 시스템 사용", + "description": "봇이 다양한 도구를 사용해 기능을 확장할 수 있게 합니다" + }, + "allGlobal": { + "label": "전역 슬랭 모드 사용", + "description": "봇이 그룹 슬랭을 학습하고 사용할 수 있게 합니다" + } + }, + "siliconFlow": { + "about": { + "title": "SiliconFlow 소개", + "description": "SiliconFlow 는 DeepSeek V3, Qwen, 비전 모델, 음성 인식, 임베딩 모델 등 폭넓은 모델을 제공합니다. API Key 하나로 MaiBot 의 모든 기능을 사용할 수 있습니다.", + "link": "SiliconFlow 에서 API Key 받기" + }, + "apiKey": { + "label": "SiliconFlow API Key *", + "description": "SiliconFlow API Key를 입력해 주세요. 입력하면 MaiBot 이 필요한 모델을 자동으로 구성합니다.", + "show": "API Key 표시", + "hide": "API Key 숨기기" + }, + "autoConfig": { + "title": "다음 모델이 자동으로 구성됩니다:", + "items": { + "deepseek": "DeepSeek V3 - 주요 대화 및 도구 모델", + "qwen3": "Qwen3 30B - 잦은 소규모 작업과 도구 호출", + "qwen3Vl": "Qwen3 VL 30B - 이미지 인식", + "senseVoice": "SenseVoice - 음성 인식", + "bgeM3": "BGE-M3 - 텍스트 임베딩", + "lpmm": "지식 베이스 관련 모델 (LPMM)" + } + }, + "hint": { + "title": "팁: ", + "description": "마법사를 마친 뒤에는 \"시스템 설정 -> 모델 설정\"에서 더 많은 API 제공자와 모델을 추가할 수 있습니다." + } + } + } + }, "common": { "loading": "로딩 중...", "error": "오류", diff --git a/dashboard/src/i18n/locales/zh.json b/dashboard/src/i18n/locales/zh.json index 9feb7bfe..dcb69539 100644 --- a/dashboard/src/i18n/locales/zh.json +++ b/dashboard/src/i18n/locales/zh.json @@ -379,6 +379,220 @@ "switchToLight": "切换到浅色模式", "switchToDark": "切换到深色模式" }, + "setupPage": { + "steps": { + "botBasic": { + "title": "Bot基础", + "description": "配置机器人的基本信息" + }, + "personality": { + "title": "人格配置", + "description": "定义机器人的性格和说话风格" + }, + "emoji": { + "title": "表情包", + "description": "配置表情包相关设置" + }, + "other": { + "title": "其他设置", + "description": "工具、情绪系统等配置" + }, + "siliconFlow": { + "title": "API配置", + "description": "配置硅基流动 API 密钥" + } + }, + "loading": { + "title": "加载配置中...", + "description": "正在读取现有配置" + }, + "header": { + "title": "首次配置向导", + "description": "让我们一起完成 {{appName}} 的初始配置" + }, + "progress": { + "stepCounter": "步骤 {{current}} / {{total}}" + }, + "validation": { + "selectPlatform": "请选择平台", + "enterNickname": "请输入昵称", + "enterQqAccount": "请输入 QQ 账号", + "enterAccountId": "请输入账号 ID" + }, + "toast": { + "loadFailedTitle": "加载配置失败", + "loadFailedDescription": "无法加载现有配置,将使用默认值", + "saveSuccessTitle": "保存成功", + "saveSuccessDescription": "{{step}} 配置已保存", + "saveFailedTitle": "保存失败", + "validationFailedTitle": "验证失败", + "completeSuccessTitle": "配置完成", + "completeSuccessDescription": "{{appName}} 正在重启以应用新配置...", + "completeFailedTitle": "配置失败", + "skipFailedTitle": "跳过失败", + "unknownError": "未知错误" + }, + "actions": { + "previous": "上一步", + "next": "下一步", + "skip": "跳过向导", + "complete": "完成配置", + "saving": "保存中...", + "completing": "完成中..." + }, + "skipDialog": { + "title": "确认跳过配置向导", + "description": "您可以随时在系统设置中重新进入配置向导。确定要跳过吗?", + "confirm": "确认跳过" + }, + "footer": "您可以随时在设置中修改这些配置", + "defaults": { + "personality": { + "personality": "是一个女大学生,现在在读大二,会刷贴吧。", + "replyStyle": "请回复得平淡一些,简短一些,说中文,不要刻意突出自身学科背景。可以参考贴吧、知乎和微博的回复风格。", + "interest": "对技术相关话题、游戏和动漫相关话题感兴趣,也对日常话题感兴趣,不喜欢太过沉重严肃的话题。", + "planStyle": "1.思考**所有**的可用的 action 中的**每个动作**是否符合当下条件,如果动作使用条件符合聊天内容就使用\n2.如果相同的内容已经被执行,请不要重复执行\n3.请控制你的发言频率,不要太过频繁地发言\n4.如果有人对你感到厌烦,请减少回复\n5.如果有人对你进行攻击,或者情绪激动,请你以合适的方法应对", + "privatePlanStyle": "1.思考**所有**的可用的 action 中的**每个动作**是否符合当下条件,如果动作使用条件符合聊天内容就使用\n2.如果相同的内容已经被执行,请不要重复执行\n3.某句话如果已经被回复过,不要重复回复" + }, + "emoji": { + "filtrationPrompt": "符合公序良俗" + } + }, + "forms": { + "botBasic": { + "platform": { + "label": "平台 *", + "placeholder": "请选择平台", + "description": "选择机器人运行的平台", + "options": { + "custom": "其他平台" + } + }, + "customPlatform": { + "label": "平台名称 *", + "placeholder": "请输入平台名称,如 matrix" + }, + "qqAccount": { + "label": "QQ账号 *", + "placeholder": "请输入机器人的 QQ 账号", + "description": "机器人登录使用的 QQ 账号" + }, + "primaryAccount": { + "label": "账号 ID *", + "placeholder": "请输入机器人的账号 ID", + "description": "机器人在该平台上的账号标识" + }, + "nickname": { + "label": "昵称 *", + "placeholder": "请输入机器人的昵称", + "description": "机器人的主要称呼名称" + }, + "alias": { + "label": "别名", + "placeholder": "输入别名后按回车添加", + "add": "添加", + "description": "机器人的其他称呼,可以添加多个", + "remove": "移除别名 {{alias}}" + } + }, + "personality": { + "personality": { + "label": "人格特征 *", + "placeholder": "描述机器人的人格特质和身份特征(建议 120 字以内)", + "description": "例如:是一个女大学生,现在在读大二,会刷贴吧" + }, + "replyStyle": { + "label": "表达风格 *", + "placeholder": "描述机器人说话的表达风格、表达习惯", + "description": "例如:回复平淡一些,简短一些,说中文,参考贴吧、知乎和微博的回复风格" + }, + "interest": { + "label": "兴趣 *", + "placeholder": "描述机器人感兴趣的话题", + "description": "会影响机器人对什么话题进行回复" + }, + "planStyle": { + "label": "群聊说话规则 *", + "placeholder": "机器人在群聊中的行为风格和规则", + "description": "定义机器人在群聊中如何行动,例如回复频率、条件等" + }, + "privatePlanStyle": { + "label": "私聊说话规则 *", + "placeholder": "机器人在私聊中的行为风格和规则", + "description": "定义机器人在私聊中的行为方式" + } + }, + "emoji": { + "emojiChance": { + "label": "表情包激活概率", + "description": "机器人发送表情包的概率" + }, + "maxRegNum": { + "label": "最大表情包数量", + "description": "机器人最多保存的表情包数量" + }, + "doReplace": { + "label": "达到最大数量时替换", + "description": "开启后会删除旧表情包,关闭则不再收集新表情包" + }, + "checkInterval": { + "label": "检查间隔(分钟)", + "description": "检查表情包注册、破损、删除的时间间隔" + }, + "stealEmoji": { + "label": "偷取表情包", + "description": "允许机器人将一些表情包据为己有" + }, + "contentFiltration": { + "label": "启用表情包过滤", + "description": "只保存符合要求的表情包" + }, + "filtrationPrompt": { + "label": "过滤要求", + "placeholder": "例如:符合公序良俗", + "description": "描述表情包应该符合的要求" + } + }, + "other": { + "enableTool": { + "label": "启用工具系统", + "description": "允许机器人使用各种工具增强功能" + }, + "allGlobal": { + "label": "启用全局黑话模式", + "description": "允许机器人学习和使用群组黑话" + } + }, + "siliconFlow": { + "about": { + "title": "关于硅基流动 (SiliconFlow)", + "description": "硅基流动提供了完整的模型覆盖,包括 DeepSeek V3、Qwen、视觉模型、语音识别和嵌入模型。只需一个 API Key 即可使用麦麦的所有功能!", + "link": "前往硅基流动获取 API Key" + }, + "apiKey": { + "label": "SiliconFlow API Key *", + "description": "请输入您的硅基流动 API 密钥。获取后,麦麦将自动配置所有必需的模型。", + "show": "显示 API Key", + "hide": "隐藏 API Key" + }, + "autoConfig": { + "title": "将自动配置以下模型:", + "items": { + "deepseek": "DeepSeek V3 - 主要对话和工具模型", + "qwen3": "Qwen3 30B - 高频小任务和工具调用", + "qwen3Vl": "Qwen3 VL 30B - 图像识别", + "senseVoice": "SenseVoice - 语音识别", + "bgeM3": "BGE-M3 - 文本嵌入", + "lpmm": "知识库相关模型 (LPMM)" + } + }, + "hint": { + "title": "💡 提示:", + "description": "完成向导后,您可以在“系统设置 -> 模型配置”中添加更多 API 提供商和模型。" + } + } + } + }, "common": { "loading": "加载中...", "error": "错误", diff --git a/dashboard/src/routes/setup/StepForms.tsx b/dashboard/src/routes/setup/StepForms.tsx index 9c935bf7..cd19a085 100644 --- a/dashboard/src/routes/setup/StepForms.tsx +++ b/dashboard/src/routes/setup/StepForms.tsx @@ -1,13 +1,13 @@ // 设置向导各步骤表单组件 -import { useState, useEffect } from 'react' +import { ExternalLink, Eye, EyeOff, X } from 'lucide-react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' -import { Textarea } from '@/components/ui/textarea' -import { Switch } from '@/components/ui/switch' -import { Separator } from '@/components/ui/separator' -import { Button } from '@/components/ui/button' -import { Badge } from '@/components/ui/badge' import { Select, SelectContent, @@ -15,12 +15,15 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select' -import { X, ExternalLink, Eye, EyeOff } from 'lucide-react' +import { Separator } from '@/components/ui/separator' +import { Switch } from '@/components/ui/switch' +import { Textarea } from '@/components/ui/textarea' + import type { BotBasicConfig, - PersonalityConfig, EmojiConfig, OtherBasicConfig, + PersonalityConfig, SiliconFlowConfig, } from './types' @@ -34,13 +37,7 @@ const KNOWN_PLATFORMS: Record = { kook: 'kook', } -const PLATFORM_OPTIONS = [ - { value: 'qq', label: 'QQ' }, - { value: 'telegram', label: 'Telegram' }, - { value: 'discord', label: 'Discord' }, - { value: 'kook', label: 'Kook' }, - { value: 'custom', label: '其他平台' }, -] +const PLATFORM_OPTIONS = ['qq', 'telegram', 'discord', 'kook', 'custom'] as const function normalizePlatform(raw: string): string { const key = raw.trim().toLowerCase() @@ -56,17 +53,21 @@ function deriveSelectedPlatform(config: BotBasicConfig): { selected: string; cus if (!platform) { return { selected: '', customName: '' } } - const known = PLATFORM_OPTIONS.find((opt) => opt.value === platform && opt.value !== 'custom') + const known = PLATFORM_OPTIONS.find((value) => value === platform && value !== 'custom') if (known) { return { selected: platform, customName: '' } } return { selected: 'custom', customName: platform } } -function upsertPlatformAccount(platforms: string[], platformName: string, accountId: string): string[] { +function upsertPlatformAccount( + platforms: string[], + platformName: string, + accountId: string +): string[] { const normalized = normalizePlatform(platformName) - const filtered = platforms.filter((p) => { - const prefix = p.split(':')[0] + const filtered = platforms.filter((platform) => { + const prefix = platform.split(':')[0] return normalizePlatform(prefix) !== normalized }) if (accountId.trim()) { @@ -77,8 +78,8 @@ function upsertPlatformAccount(platforms: string[], platformName: string, accoun function getPrimaryAccount(platforms: string[], platformName: string): string { const normalized = normalizePlatform(platformName) - const entry = platforms.find((p) => { - const prefix = p.split(':')[0] + const entry = platforms.find((platform) => { + const prefix = platform.split(':')[0] return normalizePlatform(prefix) === normalized }) return entry ? entry.split(':').slice(1).join(':') : '' @@ -90,58 +91,53 @@ interface BotBasicFormProps { } export function BotBasicForm({ config, onChange }: BotBasicFormProps) { + const { t } = useTranslation() const derived = deriveSelectedPlatform(config) - const [selectedPlatform, setSelectedPlatform] = useState(derived.selected) - const [customPlatformName, setCustomPlatformName] = useState(derived.customName) - const [primaryAccount, setPrimaryAccount] = useState(() => { - if (derived.selected === 'qq') { - return config.qq_account > 0 ? String(config.qq_account) : '' - } - if (config.platform) { - return getPrimaryAccount(config.platforms, config.platform) - } - return '' - }) + const [selectedPlatformOverride, setSelectedPlatformOverride] = useState(null) + const [customPlatformNameOverride, setCustomPlatformNameOverride] = useState(null) + const selectedPlatform = selectedPlatformOverride ?? derived.selected + const customPlatformName = customPlatformNameOverride ?? derived.customName + const primaryAccount = + selectedPlatform === 'qq' + ? config.qq_account > 0 + ? String(config.qq_account) + : '' + : config.platform + ? getPrimaryAccount(config.platforms, config.platform) + : '' - // Re-derive when config loads from API (e.g. after initial fetch) - useEffect(() => { - const d = deriveSelectedPlatform(config) - setSelectedPlatform(d.selected) - setCustomPlatformName(d.customName) - if (d.selected === 'qq') { - setPrimaryAccount(config.qq_account > 0 ? String(config.qq_account) : '') - } else if (config.platform) { - setPrimaryAccount(getPrimaryAccount(config.platforms, config.platform)) - } - }, [config.platform, config.qq_account, config.platforms]) + const platformOptions = [ + { value: 'qq', label: 'QQ' }, + { value: 'telegram', label: 'Telegram' }, + { value: 'discord', label: 'Discord' }, + { value: 'kook', label: 'Kook' }, + { value: 'custom', label: t('setupPage.forms.botBasic.platform.options.custom') }, + ] const handlePlatformChange = (value: string) => { - setSelectedPlatform(value) + setSelectedPlatformOverride(value) const realPlatform = value === 'custom' ? customPlatformName : value - setPrimaryAccount('') onChange({ ...config, platform: normalizePlatform(realPlatform), - qq_account: value === 'qq' ? config.qq_account : config.qq_account, // preserve + qq_account: value === 'qq' ? config.qq_account : config.qq_account, }) } const handleCustomNameChange = (name: string) => { - setCustomPlatformName(name) + setCustomPlatformNameOverride(name) const normalized = normalizePlatform(name) - // Move account to new platform name if we had one - const newPlatforms = primaryAccount + const nextPlatforms = primaryAccount ? upsertPlatformAccount(config.platforms, normalized, primaryAccount) : config.platforms onChange({ ...config, platform: normalized, - platforms: newPlatforms, + platforms: nextPlatforms, }) } const handleAccountChange = (accountId: string) => { - setPrimaryAccount(accountId) const realPlatform = selectedPlatform === 'custom' ? customPlatformName : selectedPlatform const normalized = normalizePlatform(realPlatform) @@ -172,37 +168,39 @@ export function BotBasicForm({ config, onChange }: BotBasicFormProps) { const handleRemoveAlias = (index: number) => { onChange({ ...config, - alias_names: config.alias_names.filter((_, i) => i !== index), + alias_names: config.alias_names.filter((_, aliasIndex) => aliasIndex !== index), }) } return (
- + -

- 选择机器人运行的平台 +

+ {t('setupPage.forms.botBasic.platform.description')}

{selectedPlatform === 'custom' && (
- + handleCustomNameChange(e.target.value)} /> @@ -211,58 +209,63 @@ export function BotBasicForm({ config, onChange }: BotBasicFormProps) { {selectedPlatform === 'qq' && (
- + handleAccountChange(e.target.value)} /> -

- 机器人登录使用的QQ账号 +

+ {t('setupPage.forms.botBasic.qqAccount.description')}

)} - {selectedPlatform && selectedPlatform !== 'qq' && (selectedPlatform !== 'custom' || customPlatformName) && ( -
- - handleAccountChange(e.target.value)} - /> -

- 机器人在该平台上的账号标识 -

-
- )} + {selectedPlatform && + selectedPlatform !== 'qq' && + (selectedPlatform !== 'custom' || customPlatformName) && ( +
+ + handleAccountChange(e.target.value)} + /> +

+ {t('setupPage.forms.botBasic.primaryAccount.description')} +

+
+ )}
- + onChange({ ...config, nickname: e.target.value })} /> -

- 机器人的主要称呼名称 +

+ {t('setupPage.forms.botBasic.nickname.description')}

- -
+ +
{config.alias_names.map((alias, index) => ( {alias} @@ -272,7 +275,7 @@ export function BotBasicForm({ config, onChange }: BotBasicFormProps) {
{ if (e.key === 'Enter') { handleAddAlias((e.target as HTMLInputElement).value) @@ -284,20 +287,18 @@ export function BotBasicForm({ config, onChange }: BotBasicFormProps) { type="button" variant="outline" onClick={() => { - const input = document.getElementById( - 'alias_input' - ) as HTMLInputElement + const input = document.getElementById('alias_input') as HTMLInputElement if (input) { handleAddAlias(input.value) input.value = '' } }} > - 添加 + {t('setupPage.forms.botBasic.alias.add')}
-

- 机器人的其他称呼,可以添加多个 +

+ {t('setupPage.forms.botBasic.alias.description')}

@@ -311,79 +312,81 @@ interface PersonalityFormProps { } export function PersonalityForm({ config, onChange }: PersonalityFormProps) { + const { t } = useTranslation() + return (
- +