diff --git a/README.md b/README.md index 9487d72..098f1f6 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,192 @@ -
-GHBanner -
+# Sakura Sensei 🌸 - AI Japanese Tutor -# Run and deploy your AI Studio app +![React](https://img.shields.io/badge/React-19.0-blue?logo=react) +![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue?logo=typescript) +![Gemini API](https://img.shields.io/badge/Google%20Gemini-API-orange?logo=google) +![Vite](https://img.shields.io/badge/Vite-5.0-purple?logo=vite) +![Tailwind CSS](https://img.shields.io/badge/Tailwind-3.4-cyan?logo=tailwindcss) -This contains everything you need to run your app locally. +**Sakura Sensei** is an immersive, all-in-one Japanese learning platform powered by Google's latest Gemini models. It provides a personalized tutor, realistic roleplay scenarios, custom reading/listening materials, and creative tools to make learning Japanese engaging and effective. -View your app in AI Studio: https://ai.studio/apps/drive/1MdpOjnvh39r0kvYmztzlvr-cTY1iF2tW +[English](#english) | [日本語](#japanese) | [中文](#chinese) -## Run Locally +--- -**Prerequisites:** Node.js + +## 🇬🇧 English +### ✨ Features -1. Install dependencies: - `npm install` -2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key -3. Run the app: - `npm run dev` +* **Tutor Dojo (Chat):** + * Free chat with Sakura (AI Tutor) using `gemini-3-pro-preview` (Reasoning) or `gemini-2.5-flash`. + * **Voice Interaction:** Real-time Speech-to-Text (STT) and high-quality Text-to-Speech (TTS). + * **Thinking Mode:** Visualize the AI's reasoning process for complex grammar explanations. + * **Share:** Export chat history as Text, File, or Image (screenshot). +* **Reading Hall:** + * Generates custom reading lessons based on your topic and JLPT level (Beginner N5 - Advanced N1). + * Includes vocabulary lists, grammar analysis, and translations. + * **Contextual Tutor:** Ask questions specifically about the generated text. +* **Listening Lab:** + * AI-generated conversations and monologues with comprehension quizzes. + * Audio playback with speed controls and transcript toggles. +* **Roleplay (Speaking):** + * Practice realistic scenarios (Cafe, Hotel, Immigration, etc.). + * **AI Feedback:** Receive instant scoring on pronunciation, fluency, and grammar corrections. +* **Creative Atelier:** + * **Paint:** Generate images using `imagen-4.0` to visualize vocabulary. + * **Dream Video:** Generate short videos using `veo-3.1` (requires specific API access). +* **Toolbox:** + * **Scanner (OCR):** Upload or snap photos of Japanese text for instant analysis and study notes. + * **Translator:** Text and Image translation with audio support. +* **Data Management:** + * Local storage for history (Chat, Reading, Listening, OCR). + * Backup and Restore functionality (JSON). + +### 🚀 Getting Started + +1. **Clone the repository:** + ```bash + git clone https://github.com/yourusername/sakura-sensei.git + cd sakura-sensei + ``` + +2. **Install dependencies:** + ```bash + npm install + ``` + +3. **Set up API Key:** + * Get your API key from [Google AI Studio](https://aistudio.google.com/). + * Create a `.env` file in the root directory: + ```env + VITE_API_KEY=your_gemini_api_key_here + ``` + * *Alternatively, you can enter the API Key directly in the app's Settings menu.* + +4. **Run the app:** + ```bash + npm run dev + ``` + +### 🛠 Tech Stack + +* **Frontend:** React 19, TypeScript, Vite +* **Styling:** Tailwind CSS, Lucide React (Icons) +* **AI Integration:** `@google/genai` SDK +* **Models Used:** + * Text/Reasoning: `gemini-3-pro-preview`, `gemini-2.5-flash` + * Audio: `gemini-2.5-flash-preview-tts` + * Vision/OCR: `gemini-2.5-flash` + * Image Gen: `imagen-4.0-generate-001`, `gemini-2.5-flash-image` + * Video: `veo-3.1-fast-generate-preview` + +--- + + +## 🇯🇵 日本語 + +**さくら先生**は、Googleの最新Geminiモデルを搭載した没入型の日本語学習プラットフォームです。文法解説、ロールプレイ、読解・聴解練習など、あらゆる学習ニーズに対応します。 + +### ✨ 主な機能 + +* **学習道場 (チャット):** + * `gemini-3-pro-preview` を活用した高度な推論能力を持つAIチューターとの会話。 + * 音声入力・音声再生対応。思考モード(Thinking Mode)でAIの考え方を可視化。 + * チャット履歴の画像共有機能。 +* **読書の間:** + * トピックと難易度(N5〜N1)を指定して、オリジナルの読み物を生成。 + * 単語リスト、文法解説、翻訳付き。テキストについてAIに質問可能。 +* **聴解ラボ:** + * リスニング練習用の会話スクリプトとクイズを自動生成。 + * 音声再生、スクリプトの表示/非表示切り替え。 +* **会話道場 (ロールプレイ):** + * カフェ、駅、入国審査などのリアルなシナリオで会話練習。 + * 発音、流暢さ、文法ミスに対する即時フィードバック機能。 +* **アトリエ:** + * 画像生成 (`imagen-4.0`) や動画生成 (`veo-3.1`) で学習を視覚的にサポート。 +* **ツールボックス:** + * **スキャナー (OCR):** カメラや画像から日本語テキストを抽出し、解説を生成。 + * **翻訳機:** テキスト・画像の翻訳と音声再生。 +* **設定・データ:** + * 学習履歴のローカル保存とバックアップ/復元機能。 + +### 🚀 始め方 + +1. **リポジトリのクローン:** + ```bash + git clone https://github.com/yourusername/sakura-sensei.git + ``` + +2. **依存関係のインストール:** + ```bash + npm install + ``` + +3. **APIキーの設定:** + * [Google AI Studio](https://aistudio.google.com/) でAPIキーを取得してください。 + * `.env` ファイルを作成するか、アプリ内の「設定」メニューからキーを入力します。 + +4. **起動:** + ```bash + npm run dev + ``` + +--- + + +## 🇨🇳 中文 + +**樱花老师 (Sakura Sensei)** 是一个基于 Google Gemini 模型的全能型 AI 日语学习助手。它集成了对话练习、阅读生成、听力训练和实时纠错功能,为您提供沉浸式的日语学习体验。 + +### ✨ 主要功能 + +* **学习道场 (Tutor Chat):** + * 与 AI 导师自由对话,支持 `gemini-3-pro` 深度推理模式。 + * 支持语音输入 (STT) 和高质量语音朗读 (TTS)。 + * 支持将对话记录导出为图片、文本或文件。 +* **阅读室:** + * 根据您感兴趣的主题和 JLPT 等级 (N5-N1) 生成阅读文章。 + * 自动提取词汇表、语法点和翻译。支持针对文章内容的提问。 +* **听力实验室:** + * 生成包含理解测验的听力对话脚本。 + * 支持音频播放控制和脚本隐藏/显示。 +* **对话道场 (Roleplay):** + * 在真实场景(如便利店、机场、酒店)中进行角色扮演。 + * AI 会对您的发音、流利度和语法进行打分并提供建议。 +* **创意工作室:** + * 使用 AI 生成图片或视频来辅助记忆单词和场景。 +* **实用工具:** + * **扫描仪 (OCR):** 拍照识别日语文本,生成学习笔记。 + * **翻译机:** 支持文本和图片翻译,带发音功能。 +* **数据隐私:** + * 所有聊天和学习记录均存储在本地浏览器中 (LocalStorage)。 + * 支持数据的备份与恢复 (JSON 格式)。 + +### 🚀 快速开始 + +1. **克隆项目:** + ```bash + git clone https://github.com/yourusername/sakura-sensei.git + ``` + +2. **安装依赖:** + ```bash + npm install + ``` + +3. **配置 API Key:** + * 前往 [Google AI Studio](https://aistudio.google.com/) 获取 Gemini API Key。 + * 在项目根目录创建 `.env` 文件,或直接在应用“设置”中输入 Key。 + +4. **运行应用:** + ```bash + npm run dev + ``` + +--- + +## License + +MIT License. + +Powered by [Google Gemini API](https://ai.google.dev/). diff --git a/components/ErrorBoundary.tsx b/components/ErrorBoundary.tsx index 1830c7b..0e8c77a 100644 --- a/components/ErrorBoundary.tsx +++ b/components/ErrorBoundary.tsx @@ -1,9 +1,8 @@ - import React, { Component, ErrorInfo, ReactNode } from "react"; import { AlertCircle, RefreshCw, Trash2 } from 'lucide-react'; interface Props { - children: ReactNode; + children?: ReactNode; } interface State { @@ -12,10 +11,13 @@ interface State { } export class ErrorBoundary extends Component { - public state: State = { - hasError: false, - error: null - }; + constructor(props: Props) { + super(props); + this.state = { + hasError: false, + error: null + }; + } public static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; @@ -67,4 +69,4 @@ export class ErrorBoundary extends Component { return this.props.children; } -} +} \ No newline at end of file diff --git a/releases/HTY1024-APP-SKR-0.6.0_20251125.zip b/releases/HTY1024-APP-SKR-0.6.0_20251125.zip new file mode 100644 index 0000000..e5e6d7b Binary files /dev/null and b/releases/HTY1024-APP-SKR-0.6.0_20251125.zip differ diff --git a/services/geminiService.ts b/services/geminiService.ts index f196ec6..6028d3a 100644 --- a/services/geminiService.ts +++ b/services/geminiService.ts @@ -396,7 +396,7 @@ class GeminiService { const targetLangName = LANGUAGE_MAP[language]; const prompt = `Create a complete Japanese reading lesson on "${topic}", level ${difficulty}. The 'japaneseContent' MUST be a complete article or story (at least 300 characters). - Output JSON with title, japaneseContent, translation (${targetLangName}), vocabulary, and grammarPoints (list of key grammar used in the text with explanations).`; + Output JSON with title, japaneseContent, translation (in ${targetLangName}), vocabulary (meanings in ${targetLangName}), and grammarPoints (explanations in ${targetLangName}).`; return this.retryOperation(async () => { const response = await ai.models.generateContent({ @@ -430,9 +430,9 @@ class GeminiService { - title - script (The full Japanese text of the conversation/monologue) - translation (The full text in ${targetLangName}) - - vocabulary (Key words) + - vocabulary (Key words with meanings in ${targetLangName}) - questions (3 multiple choice comprehension questions in ${targetLangName}) - - Each question needs: question, options (array of 3 strings), correctIndex (0-2), explanation. + - Each question needs: question, options (array of 3 strings), correctIndex (0-2), explanation (in ${targetLangName}). `; return this.retryOperation(async () => { @@ -530,7 +530,7 @@ class GeminiService { const ai = this.getAi(); const cleanBase64 = base64.replace(/^data:image\/(png|jpeg|jpg|webp|heic|heif);base64,/i, ""); const targetLang = LANGUAGE_MAP[language]; - const prompt = `OCR and analyze text. Explain in ${targetLang}. JSON: extractedText, detectedLanguage, summary, vocabulary, grammarPoints.`; + const prompt = `OCR and analyze text. Explain in ${targetLang}. JSON: extractedText, detectedLanguage, summary (in ${targetLang}), vocabulary (meanings in ${targetLang}), grammarPoints (explanations in ${targetLang}).`; return this.retryOperation(async () => { const res = await ai.models.generateContent({ diff --git a/views/ListeningView.tsx b/views/ListeningView.tsx index 041f5d6..9ceab7d 100644 --- a/views/ListeningView.tsx +++ b/views/ListeningView.tsx @@ -1,10 +1,8 @@ - - import React, { useState, useRef, useEffect } from 'react'; import { Language, ListeningLesson, ListeningLessonRecord, ReadingDifficulty, ChatMessage, Role, MessageType } from '../types'; import { geminiService, decodeAudioData } from '../services/geminiService'; import { processAndDownloadAudio } from '../utils/audioUtils'; -import { Headphones, Loader2, Send, Eye, EyeOff, List, HelpCircle, ChevronLeft, History, Trash2, X, PanelRightClose, PanelRightOpen, Volume2, Square, Play, Pause, CheckCircle, AlertCircle, FileText, MessageCircle, Download, RotateCcw } from 'lucide-react'; +import { Headphones, Loader2, Send, Eye, EyeOff, List, HelpCircle, ChevronLeft, History, Trash2, X, PanelRightClose, PanelRightOpen, Volume2, Square, Play, Pause, CheckCircle, AlertCircle, FileText, MessageCircle, Download, RotateCcw, Copy, Check } from 'lucide-react'; import { translations } from '../utils/localization'; import ChatBubble from '../components/ChatBubble'; @@ -17,6 +15,28 @@ interface ListeningViewProps { onDeleteHistoryItem: (id: string) => void; } +// Internal Copy Button Component +const CopyButton: React.FC<{ text: string; label?: string }> = ({ text, label }) => { + const [copied, setCopied] = useState(false); + + const handleCopy = (e: React.MouseEvent) => { + e.stopPropagation(); + navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + ); +}; + const ListeningView: React.FC = ({ language, history, onSaveToHistory, onUpdateHistory, onClearHistory, onDeleteHistoryItem }) => { const t = translations[language].listening; const tCommon = translations[language].common; @@ -603,15 +623,21 @@ const ListeningView: React.FC = ({ language, history, onSave {/* Script Reveal */} {showScript && (
-
-

{t.scriptTitle}

+
+
+

{t.scriptTitle}

+ +

{lesson.script || {t.scriptMissing}}

-
-

{translations[language].reading.translationLabel}

+
+
+

{translations[language].reading.translationLabel}

+ +

{lesson.translation}

diff --git a/views/ReadingView.tsx b/views/ReadingView.tsx index 63cdfa5..f1ce009 100644 --- a/views/ReadingView.tsx +++ b/views/ReadingView.tsx @@ -1,8 +1,9 @@ + import React, { useState, useRef, useEffect } from 'react'; import { Language, ReadingLesson, ReadingDifficulty, ChatMessage, Role, MessageType, ReadingLessonRecord } from '../types'; import { geminiService, decodeAudioData } from '../services/geminiService'; import { processAndDownloadAudio } from '../utils/audioUtils'; -import { BookOpen, Loader2, Send, ToggleLeft, ToggleRight, List, HelpCircle, ChevronLeft, RotateCcw, History, Trash2, X, PanelRightClose, PanelRightOpen, Volume2, Square, MessageCircle, FileText, PenTool, Download } from 'lucide-react'; +import { BookOpen, Loader2, Send, ToggleLeft, ToggleRight, List, HelpCircle, ChevronLeft, RotateCcw, History, Trash2, X, PanelRightClose, PanelRightOpen, Volume2, Square, MessageCircle, FileText, PenTool, Download, Copy, Check } from 'lucide-react'; import { translations } from '../utils/localization'; import ChatBubble from '../components/ChatBubble'; @@ -15,6 +16,28 @@ interface ReadingViewProps { onDeleteHistoryItem: (id: string) => void; } +// Internal Copy Button Component +const CopyButton: React.FC<{ text: string; label?: string }> = ({ text, label }) => { + const [copied, setCopied] = useState(false); + + const handleCopy = (e: React.MouseEvent) => { + e.stopPropagation(); + navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + ); +}; + const ReadingView: React.FC = ({ language, history, onSaveToHistory, onUpdateHistory, onClearHistory, onDeleteHistoryItem }) => { const t = translations[language].reading; const tCommon = translations[language].common; @@ -502,6 +525,9 @@ const ReadingView: React.FC = ({ language, history, onSaveToHi {t.translationLabel} + {/* Copy Content Button - New */} + + {/* Sidebar Toggle In Lesson View */}
{showTranslation && ( -
-

{t.translationLabel}

+
+
+

{t.translationLabel}

+ +

{lesson.translation || {t.translationMissing}}

)} -
+ + {/* Vocabulary Section */} +

{t.vocabTitle}

{lesson.vocabulary?.map((v, i) => ( -
-
-
+
+
+
{v.word} - ({v.reading}) + {v.reading && ({v.reading})}
@@ -556,13 +587,13 @@ const ReadingView: React.FC = ({ language, history, onSaveToHi {/* Grammar Section */} {lesson.grammarPoints && lesson.grammarPoints.length > 0 && ( -
+

{t.grammarHeader}

{lesson.grammarPoints.map((g, i) => ( -
+
{g.point}

{g.explanation}

@@ -642,4 +673,4 @@ const ReadingView: React.FC = ({ language, history, onSaveToHi ); }; -export default ReadingView; \ No newline at end of file +export default ReadingView;