From a3ff89a85cf746a0561bb1d19b7cdd76837a8a05 Mon Sep 17 00:00:00 2001 From: tegnike Date: Mon, 9 Sep 2024 22:55:05 +0200 Subject: [PATCH 1/7] =?UTF-8?q?.env=E3=82=92gitignore=E3=81=8B=E3=82=89?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 10 ++++++++++ .gitignore | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 00000000..7bc01782 --- /dev/null +++ b/.env @@ -0,0 +1,10 @@ +GOOGLE_APPLICATION_CREDENTIALS="./credentials.json" +NEXT_PUBLIC_OPEN_AI_KEY="" +NEXT_PUBLIC_ANTHROPIC_KEY="" +NEXT_PUBLIC_GOOGLE_KEY="" +NEXT_PUBLIC_GROQ_KEY="" +NEXT_PUBLIC_LOCAL_LLM_URL="" +NEXT_PUBLIC_LOCAL_LLM_MODEL="" +NEXT_PUBLIC_DIFY_KEY="" +NEXT_PUBLIC_DIFY_URL="" +NEXT_PUBLIC_GOOGLE_TTS_TYPE="" diff --git a/.gitignore b/.gitignore index 20b435df..247cf22e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,6 @@ credentials.json .tool-versions certificates -.env /public/slides/* !/public/slides/demo/ From 11f788677e489ca47f1a224f91b2e7eb5fc450df Mon Sep 17 00:00:00 2001 From: tegnike Date: Mon, 16 Sep 2024 09:17:50 +0200 Subject: [PATCH 2/7] =?UTF-8?q?Dify=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92API?= =?UTF-8?q?=E3=81=AB=E5=88=86=E5=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/chat/difyChat.ts | 53 +++++++++++++--------------- src/pages/api/difyChat.ts | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 29 deletions(-) create mode 100644 src/pages/api/difyChat.ts diff --git a/src/features/chat/difyChat.ts b/src/features/chat/difyChat.ts index 0a136c8c..f6a71e59 100644 --- a/src/features/chat/difyChat.ts +++ b/src/features/chat/difyChat.ts @@ -6,43 +6,39 @@ export async function getDifyChatResponseStream( apiKey: string, url: string, conversationId: string -) { - if (!apiKey) { - throw new Error('Invalid API Key') - } - - const headers = { - Authorization: `Bearer ${apiKey}`, - 'Content-Type': 'application/json', - } - const body = JSON.stringify({ - inputs: {}, - query: messages[messages.length - 1].content, // messages[-1] は TypeScript では無効です - response_mode: 'streaming', - conversation_id: conversationId, - user: 'aituber-kit', - files: [], - }) - - const response = await fetch(url.replace(/\/$/, ''), { +): Promise> { + const response = await fetch('/api/difyChat', { method: 'POST', - headers: headers, - body: body, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: messages[messages.length - 1].content, + apiKey, + url, + conversationId, + stream: true, + }), }) - if (!response.body) { - throw new Error('Invalid response body') + if (!response.ok) { + throw new Error(`API request to Dify failed with status ${response.status}`) } - const reader = response.body.getReader() - - const res = new ReadableStream({ + return new ReadableStream({ async start(controller) { + if (!response.body) { + throw new Error('API response from Dify is empty') + } + + const reader = response.body.getReader() + const decoder = new TextDecoder('utf-8') + try { while (true) { const { done, value } = await reader.read() if (done) break - const textChunk = new TextDecoder('utf-8').decode(value) + const textChunk = decoder.decode(value, { stream: true }) const messages = textChunk .split('\n') .filter((line) => line.startsWith('data:')) @@ -60,9 +56,8 @@ export async function getDifyChatResponseStream( controller.error(error) } finally { controller.close() + reader.releaseLock() } }, }) - - return res } diff --git a/src/pages/api/difyChat.ts b/src/pages/api/difyChat.ts new file mode 100644 index 00000000..2290d518 --- /dev/null +++ b/src/pages/api/difyChat.ts @@ -0,0 +1,65 @@ +import { NextRequest } from 'next/server' + +export const config = { + runtime: 'edge', +} + +export default async function handler(req: NextRequest) { + if (req.method !== 'POST') { + return new Response(JSON.stringify({ error: 'Method Not Allowed' }), { + status: 405, + headers: { 'Content-Type': 'application/json' }, + }) + } + + const { query, apiKey, url, conversationId, stream } = await req.json() + + if (!apiKey) { + return new Response(JSON.stringify({ error: 'Invalid API Key' }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }) + } + + const headers = { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + } + const body = JSON.stringify({ + inputs: {}, + query: query, + response_mode: stream ? 'streaming' : 'blocking', + conversation_id: conversationId, + user: 'aituber-kit', + files: [], + }) + + try { + const response = await fetch(url.replace(/\/$/, ''), { + method: 'POST', + headers: headers, + body: body, + }) + + if (!response.ok) { + throw new Error(`Dify API request failed with status ${response.status}`) + } + + if (stream) { + return new Response(response.body, { + headers: { 'Content-Type': 'text/event-stream' }, + }) + } else { + const data = await response.json() + return new Response(JSON.stringify(data), { + headers: { 'Content-Type': 'application/json' }, + }) + } + } catch (error) { + console.error('Error in Dify API call:', error) + return new Response(JSON.stringify({ error: 'Internal Server Error' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }) + } +} From 04d2ac8cff988ba9bbe8f2786aa847bf2e8591f0 Mon Sep 17 00:00:00 2001 From: tegnike Date: Mon, 16 Sep 2024 10:12:52 +0200 Subject: [PATCH 3/7] =?UTF-8?q?ElevenLabs=E3=81=AE=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92API=E3=81=AB=E5=88=86=E9=9B=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../messages/synthesizeVoiceElevenlabs.ts | 48 +++++----- src/pages/api/elevenLabs.ts | 89 +++++++++++++++++++ 2 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 src/pages/api/elevenLabs.ts diff --git a/src/features/messages/synthesizeVoiceElevenlabs.ts b/src/features/messages/synthesizeVoiceElevenlabs.ts index c4160c33..fe65717a 100644 --- a/src/features/messages/synthesizeVoiceElevenlabs.ts +++ b/src/features/messages/synthesizeVoiceElevenlabs.ts @@ -38,39 +38,31 @@ export async function synthesizeVoiceElevenlabsApi( voiceId: string, language: Language ) { - const res = await fetch( - `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}?output_format=pcm_16000`, - { + const body = { + message, + voiceId, + apiKey, + language, + } + + try { + const res = await fetch('/api/elevenLabs', { method: 'POST', headers: { 'Content-Type': 'application/json', - 'xi-api-key': apiKey, - accept: 'audio/mpeg', }, - body: JSON.stringify({ - text: message, - model_id: 'eleven_turbo_v2_5', - language_code: language, - }), + body: JSON.stringify(body), + }) + + if (!res.ok) { + throw new Error( + `APIからの応答が異常です。ステータスコード: ${res.status}` + ) } - ) - if (!res.ok) { - throw new Error(`ElevenLabs API request failed with status: ${res.status}`) + const buffer = await res.arrayBuffer() + return { audio: new Uint8Array(buffer) } + } catch (error: any) { + throw new Error(`APIリクエスト中にエラーが発生しました: ${error.message}`) } - - const pcmData = await res.arrayBuffer() - const wavHeader = createWavHeader(pcmData.byteLength) - const wavData = new Uint8Array(wavHeader.byteLength + pcmData.byteLength) - wavData.set(new Uint8Array(wavHeader), 0) - wavData.set(new Uint8Array(pcmData), wavHeader.byteLength) - - // Convert the ArrayBuffer to a Blob - const blob = new Blob([wavData.buffer], { type: 'audio/wav' }) - - // Create a temporary URL for the Blob - const audioURL = URL.createObjectURL(blob) - - // return { audio: audioURL }; - return { audio: wavData } } diff --git a/src/pages/api/elevenLabs.ts b/src/pages/api/elevenLabs.ts new file mode 100644 index 00000000..c056c9f5 --- /dev/null +++ b/src/pages/api/elevenLabs.ts @@ -0,0 +1,89 @@ +import type { NextApiRequest, NextApiResponse } from 'next' + +type Data = { + audio?: Buffer + error?: string +} + +function createWavHeader(dataLength: number) { + const buffer = new ArrayBuffer(44) + const view = new DataView(buffer) + + // RIFF header + writeString(view, 0, 'RIFF') + view.setUint32(4, 36 + dataLength, true) + writeString(view, 8, 'WAVE') + + // fmt chunk + writeString(view, 12, 'fmt ') + view.setUint32(16, 16, true) // chunk size + view.setUint16(20, 1, true) // PCM format + view.setUint16(22, 1, true) // mono + view.setUint32(24, 16000, true) // sample rate + view.setUint32(28, 16000 * 2, true) // byte rate + view.setUint16(32, 2, true) // block align + view.setUint16(34, 16, true) // bits per sample + + // data chunk + writeString(view, 36, 'data') + view.setUint32(40, dataLength, true) + + return buffer +} + +function writeString(view: DataView, offset: number, str: string) { + for (let i = 0; i < str.length; i++) { + view.setUint8(offset + i, str.charCodeAt(i)) + } +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const body = req.body + const message = body.message + const voiceId = body.voiceId + const apiKey = body.apiKey + const language = body.language + + try { + const response = await fetch( + `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}?output_format=pcm_16000`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'xi-api-key': apiKey, + accept: 'audio/mpeg', + }, + body: JSON.stringify({ + text: message, + model_id: 'eleven_turbo_v2_5', + language_code: language, + }), + } + ) + + if (!response.ok) { + throw new Error( + `ElevenLabs APIからの応答が異常です。ステータスコード: ${response.status}` + ) + } + + const arrayBuffer = await response.arrayBuffer() + const wavHeader = createWavHeader(arrayBuffer.byteLength) + const fullBuffer = Buffer.concat([ + Buffer.from(wavHeader), + Buffer.from(arrayBuffer), + ]) + + res.writeHead(200, { + 'Content-Type': 'audio/wav', + 'Content-Length': fullBuffer.length, + }) + res.end(fullBuffer) + } catch (error: any) { + res.status(500).json({ error: error.message }) + } +} From 9a5766e9d791206140a3f1d620e0efc4898b5fdd Mon Sep 17 00:00:00 2001 From: tegnike Date: Mon, 16 Sep 2024 17:50:08 +0200 Subject: [PATCH 4/7] =?UTF-8?q?=E7=92=B0=E5=A2=83=E5=A4=89=E6=95=B0?= =?UTF-8?q?=E5=91=A8=E3=82=8A=E3=81=AE=E5=A4=A7=E5=B9=85=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 75 ++++++++++-- .env.example | 75 ++++++++++-- docs/README_ko.md | 1 - docs/README_zh.md | 1 - locales/en/translation.json | 10 +- locales/ja/translation.json | 10 +- locales/ko/translation.json | 10 +- locales/zh/translation.json | 10 +- src/components/introduction.tsx | 17 ++- src/components/menu.tsx | 2 +- src/components/settings/advancedSettings.tsx | 4 +- src/features/chat/aiChatFactory.ts | 12 +- src/features/chat/difyChat.ts | 97 ++++++++++----- src/features/chat/handlers.ts | 47 -------- src/features/chat/vercelAIChat.ts | 111 ++++++++++------- src/features/messages/speakCharacter.ts | 3 +- src/features/stores/home.ts | 13 +- src/features/stores/menu.ts | 2 - src/features/stores/settings.ts | 88 +++++++++----- src/features/stores/youtube.ts | 0 src/pages/api/aiChat.ts | 120 ++++++++++++++----- src/pages/api/difyChat.ts | 79 ++++++++---- src/pages/api/elevenLabs.ts | 23 +++- src/pages/api/stylebertvits2.ts | 3 +- src/utils/migrateStore.ts | 2 +- 25 files changed, 555 insertions(+), 260 deletions(-) delete mode 100644 src/features/stores/youtube.ts diff --git a/.env b/.env index 7bc01782..2a9c6c76 100644 --- a/.env +++ b/.env @@ -1,10 +1,71 @@ -GOOGLE_APPLICATION_CREDENTIALS="./credentials.json" -NEXT_PUBLIC_OPEN_AI_KEY="" -NEXT_PUBLIC_ANTHROPIC_KEY="" -NEXT_PUBLIC_GOOGLE_KEY="" -NEXT_PUBLIC_GROQ_KEY="" +# AI Service +NEXT_PUBLIC_SELECT_AI_SERVICE="" # openai, dify, groq, etc. +NEXT_PUBLIC_SELECT_AI_MODEL="" # gpt-4o, claude-3-5-sonnet-20240620, etc. + +# Local LLM NEXT_PUBLIC_LOCAL_LLM_URL="" NEXT_PUBLIC_LOCAL_LLM_MODEL="" -NEXT_PUBLIC_DIFY_KEY="" -NEXT_PUBLIC_DIFY_URL="" + +# Voice +NEXT_PUBLIC_SELECT_VOICE="" # voicevox, elevenlabs, etc. + +# VoiceVox +NEXT_PUBLIC_VOICEVOX_SPEAKER="" # 46, 47, etc. +NEXT_PUBLIC_VOICEVOX_SPEED="" # 1.0, 1.2, etc. +NEXT_PUBLIC_VOICEVOX_PITCH="" # 0.0, 0.2, etc. +NEXT_PUBLIC_VOICEVOX_INTONATION="" # 1.0, 1.2, etc. + +# KoeiroMap +NEXT_PUBLIC_KOEIROMAP_KEY="" + +# Google TTS NEXT_PUBLIC_GOOGLE_TTS_TYPE="" + +# StyleBertVits2 +NEXT_PUBLIC_STYLEBERTVITS2_MODEL_ID="" +NEXT_PUBLIC_STYLEBERTVITS2_STYLE="" + +# GSVI TTS +NEXT_PUBLIC_GSVI_TTS_URL="" +NEXT_PUBLIC_GSVI_TTS_MODEL_ID="" +NEXT_PUBLIC_GSVI_TTS_BATCH_SIZE="" +NEXT_PUBLIC_GSVI_TTS_SPEECH_RATE="" + +# Youtube +NEXT_PUBLIC_YOUTUBE_API_KEY="" +NEXT_PUBLIC_YOUTUBE_MODE="" +NEXT_PUBLIC_YOUTUBE_LIVE_ID="" + +# Language +NEXT_PUBLIC_SELECT_LANGUAGE="" # en, ja, etc. +NEXT_PUBLIC_SELECT_VOICE_LANGUAGE="" # en-US, ja-JP, etc. + +# Other +NEXT_PUBLIC_BACKGROUND_IMAGE_PATH="/bg-c.png" +NEXT_PUBLIC_SHOW_INTRODUCTION=false +NEXT_PUBLIC_CHARACTER_NAME="" +NEXT_PUBLIC_SHOW_ASSISTANT_TEXT="" +NEXT_PUBLIC_SHOW_CHARACTER_NAME="" +NEXT_PUBLIC_CHANGE_ENGLISH_TO_JAPANESE="" +NEXT_PUBLIC_SHOW_CONTROL_PANEL="" +NEXT_PUBLIC_WEBSOCKET_MODE="" +NEXT_PUBLIC_SLIDE_MODE="" + +# AI API Keys +OPENAI_KEY="" +ANTHROPIC_KEY="" +GOOGLE_KEY="" +AZURE_KEY="" +GROQ_KEY="" +COHERE_KEY="" +MISTRALAI_KEY="" +PERPLEXITY_KEY="" +FIREWORKS_KEY="" + +# Dify +DIFY_KEY="" +DIFY_URL="" + +# Voice Keys +ELEVENLABS_API_KEY="" +STYLEBERTVITS2_SERVER_URL="" diff --git a/.env.example b/.env.example index 7bc01782..2a9c6c76 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,71 @@ -GOOGLE_APPLICATION_CREDENTIALS="./credentials.json" -NEXT_PUBLIC_OPEN_AI_KEY="" -NEXT_PUBLIC_ANTHROPIC_KEY="" -NEXT_PUBLIC_GOOGLE_KEY="" -NEXT_PUBLIC_GROQ_KEY="" +# AI Service +NEXT_PUBLIC_SELECT_AI_SERVICE="" # openai, dify, groq, etc. +NEXT_PUBLIC_SELECT_AI_MODEL="" # gpt-4o, claude-3-5-sonnet-20240620, etc. + +# Local LLM NEXT_PUBLIC_LOCAL_LLM_URL="" NEXT_PUBLIC_LOCAL_LLM_MODEL="" -NEXT_PUBLIC_DIFY_KEY="" -NEXT_PUBLIC_DIFY_URL="" + +# Voice +NEXT_PUBLIC_SELECT_VOICE="" # voicevox, elevenlabs, etc. + +# VoiceVox +NEXT_PUBLIC_VOICEVOX_SPEAKER="" # 46, 47, etc. +NEXT_PUBLIC_VOICEVOX_SPEED="" # 1.0, 1.2, etc. +NEXT_PUBLIC_VOICEVOX_PITCH="" # 0.0, 0.2, etc. +NEXT_PUBLIC_VOICEVOX_INTONATION="" # 1.0, 1.2, etc. + +# KoeiroMap +NEXT_PUBLIC_KOEIROMAP_KEY="" + +# Google TTS NEXT_PUBLIC_GOOGLE_TTS_TYPE="" + +# StyleBertVits2 +NEXT_PUBLIC_STYLEBERTVITS2_MODEL_ID="" +NEXT_PUBLIC_STYLEBERTVITS2_STYLE="" + +# GSVI TTS +NEXT_PUBLIC_GSVI_TTS_URL="" +NEXT_PUBLIC_GSVI_TTS_MODEL_ID="" +NEXT_PUBLIC_GSVI_TTS_BATCH_SIZE="" +NEXT_PUBLIC_GSVI_TTS_SPEECH_RATE="" + +# Youtube +NEXT_PUBLIC_YOUTUBE_API_KEY="" +NEXT_PUBLIC_YOUTUBE_MODE="" +NEXT_PUBLIC_YOUTUBE_LIVE_ID="" + +# Language +NEXT_PUBLIC_SELECT_LANGUAGE="" # en, ja, etc. +NEXT_PUBLIC_SELECT_VOICE_LANGUAGE="" # en-US, ja-JP, etc. + +# Other +NEXT_PUBLIC_BACKGROUND_IMAGE_PATH="/bg-c.png" +NEXT_PUBLIC_SHOW_INTRODUCTION=false +NEXT_PUBLIC_CHARACTER_NAME="" +NEXT_PUBLIC_SHOW_ASSISTANT_TEXT="" +NEXT_PUBLIC_SHOW_CHARACTER_NAME="" +NEXT_PUBLIC_CHANGE_ENGLISH_TO_JAPANESE="" +NEXT_PUBLIC_SHOW_CONTROL_PANEL="" +NEXT_PUBLIC_WEBSOCKET_MODE="" +NEXT_PUBLIC_SLIDE_MODE="" + +# AI API Keys +OPENAI_KEY="" +ANTHROPIC_KEY="" +GOOGLE_KEY="" +AZURE_KEY="" +GROQ_KEY="" +COHERE_KEY="" +MISTRALAI_KEY="" +PERPLEXITY_KEY="" +FIREWORKS_KEY="" + +# Dify +DIFY_KEY="" +DIFY_URL="" + +# Voice Keys +ELEVENLABS_API_KEY="" +STYLEBERTVITS2_SERVER_URL="" diff --git a/docs/README_ko.md b/docs/README_ko.md index 794e9c57..5dc7f293 100644 --- a/docs/README_ko.md +++ b/docs/README_ko.md @@ -45,7 +45,6 @@ - Node.js: ^20.0.0 - npm: 10.8.1 - ## 공통 사전 준비 1. 리포지토리를 로컬에 클론합니다. diff --git a/docs/README_zh.md b/docs/README_zh.md index a2500795..33603454 100644 --- a/docs/README_zh.md +++ b/docs/README_zh.md @@ -45,7 +45,6 @@ - Node.js: ^20.0.0 - npm: 10.8.1 - ## 共同準備 1. 將存儲庫克隆到本地。 diff --git a/locales/en/translation.json b/locales/en/translation.json index cc101320..8d222c3c 100644 --- a/locales/en/translation.json +++ b/locales/en/translation.json @@ -122,5 +122,13 @@ "PdfConvertLoading": "Converting...", "PdfConvertSuccess": "Conversion completed", "PdfConvertError": "Conversion failed", - "PdfConvertSubmitError": "Please make sure the PDF file, folder name, and API key are set." + "PdfConvertSubmitError": "Please make sure the PDF file, folder name, and API key are set.", + "Errors": { + "EmptyAPIKey": "API key is not set", + "AIInvalidProperty": "AI service settings are incorrect", + "AIAPIError": "An error occurred while executing the AI API", + "InvalidAIService": "The selected AI service is not valid", + "MethodNotAllowed": "The request is not appropriate", + "UnexpectedError": "An unexpected error occurred" + } } diff --git a/locales/ja/translation.json b/locales/ja/translation.json index d3cc7267..b02492cc 100644 --- a/locales/ja/translation.json +++ b/locales/ja/translation.json @@ -123,5 +123,13 @@ "PdfConvertLoading": "変換中...", "PdfConvertSuccess": "変換が完了しました", "PdfConvertError": "変換に失敗しました", - "PdfConvertSubmitError": "PDFファイル、フォルダ名、APIキーが設定されているか確認をしてください" + "PdfConvertSubmitError": "PDFファイル、フォルダ名、APIキーが設定されているか確認をしてください", + "Errors": { + "EmptyAPIKey": "APIキーが設定されていません", + "AIInvalidProperty": "AIサービスの設定値が正しくありません", + "AIAPIError": "AI API実行時にエラーが発生しました", + "InvalidAIService": "選択しているAIサービスが正しくありません", + "MethodNotAllowed": "リクエストが適切でありません", + "UnexpectedError": "予期せぬエラーが発生しました" + } } diff --git a/locales/ko/translation.json b/locales/ko/translation.json index 9a9c26da..1bf1d48b 100644 --- a/locales/ko/translation.json +++ b/locales/ko/translation.json @@ -122,5 +122,13 @@ "PdfConvertLoading": "변환 중...", "PdfConvertSuccess": "변환 완료", "PdfConvertError": "변환 실패", - "PdfConvertSubmitError": "PDF 파일, 폴더 이름, API 키가 설정되어 있는지 확인하세요." + "PdfConvertSubmitError": "PDF 파일, 폴더 이름, API 키가 설정되어 있는지 확인하세요.", + "Errors": { + "EmptyAPIKey": "API 키가 설정되지 않았습니다", + "AIInvalidProperty": "AI 서비스 설정값이 올바르지 않습니다", + "AIAPIError": "AI API 실행 중 오류가 발생했습니다", + "InvalidAIService": "선택한 AI 서비스가 올바르지 않습니다", + "MethodNotAllowed": "요청이 적절하지 않습니다", + "UnexpectedError": "예기치 않은 오류가 발생했습니다" + } } diff --git a/locales/zh/translation.json b/locales/zh/translation.json index f9b6643c..5b3c25ec 100644 --- a/locales/zh/translation.json +++ b/locales/zh/translation.json @@ -122,5 +122,13 @@ "PdfConvertLoading": "转换中...", "PdfConvertSuccess": "转换完成", "PdfConvertError": "转换失败", - "PdfConvertSubmitError": "请确保已设置PDF文件、文件夹名称和API密钥。" + "PdfConvertSubmitError": "请确保已设置PDF文件、文件夹名称和API密钥。", + "Errors": { + "EmptyAPIKey": "API 密钥未设置", + "AIInvalidProperty": "AI 服务设置值不正确", + "AIAPIError": "AI API 执行时发生错误", + "InvalidAIService": "选择的 AI 服务不正确", + "MethodNotAllowed": "请求不适当", + "UnexpectedError": "意外的错误发生" + } } diff --git a/src/components/introduction.tsx b/src/components/introduction.tsx index 526096bd..1fde4be4 100644 --- a/src/components/introduction.tsx +++ b/src/components/introduction.tsx @@ -12,19 +12,17 @@ import { } from '@/features/constants/settings' export const Introduction = () => { - const dontShowIntroduction = homeStore((s) => s.dontShowIntroduction) + const showIntroduction = homeStore((s) => s.showIntroduction) const selectLanguage = settingsStore((s) => s.selectLanguage) - const [showIntroduction, setShowIntroduction] = useState(false) + const [displayIntroduction, setDisplayIntroduction] = useState(false) const [opened, setOpened] = useState(true) const { t } = useTranslation() useEffect(() => { - // wait for local storage to be fully initialized - // to prevent a flash of - setShowIntroduction(!homeStore.getState().dontShowIntroduction) - }, [dontShowIntroduction]) + setDisplayIntroduction(homeStore.getState().showIntroduction) + }, [showIntroduction]) const updateLanguage = () => { console.log('i18n.language', i18n.language) @@ -54,7 +52,7 @@ export const Introduction = () => { }) } - return showIntroduction && opened ? ( + return displayIntroduction && opened ? (
{
- {/* dontShowIntroductionのチェックボックスを表示 */}