diff --git a/App.tsx b/App.tsx index da8e26d..53c56fa 100644 --- a/App.tsx +++ b/App.tsx @@ -13,7 +13,7 @@ import ToastContainer, { ToastMessage } from './components/Toast'; import ConfirmModal from './components/ConfirmModal'; import Onboarding from './components/Onboarding'; import { MessageCircle, Palette, Mic2, Settings, Globe, Sparkles, BookOpen, Languages, Download, Upload, FileText, X, ScanText, Key, Save, Trash2, Menu, BrainCircuit, Link, Headphones, AlertTriangle, Home } from 'lucide-react'; -import { AppMode, Language, ChatMessage, TranslationRecord, AppDataBackup, Role, MessageType, ReadingLessonRecord, AVAILABLE_CHAT_MODELS, ChatSession, OCRRecord, ListeningLessonRecord } from './types'; +import { AppMode, Language, ChatMessage, TranslationRecord, AppDataBackup, Role, MessageType, ReadingLessonRecord, ChatSession, OCRRecord, ListeningLessonRecord } from './types'; import { translations } from './utils/localization'; import { USER_API_KEY_STORAGE, USER_BASE_URL_STORAGE } from './services/geminiService'; @@ -25,7 +25,6 @@ const STORAGE_KEYS = { LISTENING_HISTORY: 'sakura_listening_history', OCR_HISTORY: 'sakura_ocr_history', LANGUAGE: 'sakura_language', - SELECTED_MODEL: 'sakura_selected_model', HAS_SEEN_ONBOARDING: 'sakura_has_seen_onboarding' }; @@ -66,7 +65,6 @@ const App: React.FC = () => { const [listeningHistory, setListeningHistory] = useState(() => safeJSONParse(STORAGE_KEYS.LISTENING_HISTORY, [])); const [ocrHistory, setOcrHistory] = useState(() => safeJSONParse(STORAGE_KEYS.OCR_HISTORY, [])); - const [selectedModel, setSelectedModel] = useState(() => safeJSONParse(STORAGE_KEYS.SELECTED_MODEL, AVAILABLE_CHAT_MODELS[0].id)); const [hasSeenOnboarding, setHasSeenOnboarding] = useState(() => !!localStorage.getItem(STORAGE_KEYS.HAS_SEEN_ONBOARDING)); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false); @@ -124,7 +122,6 @@ const App: React.FC = () => { useEffect(() => { saveToStorage(STORAGE_KEYS.LISTENING_HISTORY, listeningHistory); }, [listeningHistory]); useEffect(() => { saveToStorage(STORAGE_KEYS.OCR_HISTORY, ocrHistory); }, [ocrHistory]); useEffect(() => { saveToStorage(STORAGE_KEYS.LANGUAGE, language); }, [language]); - useEffect(() => { saveToStorage(STORAGE_KEYS.SELECTED_MODEL, selectedModel); }, [selectedModel]); useEffect(() => { // Only update welcome message if session is empty/default @@ -509,7 +506,7 @@ const App: React.FC = () => {
{currentView === AppMode.HOME && } - {currentView === AppMode.CHAT && } + {currentView === AppMode.CHAT && } {currentView === AppMode.TRANSLATION && setTranslationHistory(prev => [...prev, rec])} clearHistory={clearTranslationHistory} onDeleteHistoryItem={deleteTranslationRecord} />} {currentView === AppMode.SPEAKING && } {currentView === AppMode.CREATIVE && } @@ -542,12 +539,7 @@ const App: React.FC = () => {
-
-

{t.settings.modelTitle}

- -
+

{t.settings.backupTitle}

diff --git a/README.md b/README.md index 50b0df3..eaf7824 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ - # Sakura Sensei 🌸 - AI Japanese Tutor ![React](https://img.shields.io/badge/React-19.0-blue?logo=react) @@ -8,7 +7,7 @@ ![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) -**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. +**Sakura Sensei** is a next-generation language learning platform powered by Google's state-of-the-art **Gemini 3** and **Veo** models. It transcends traditional learning apps by offering a context-aware AI tutor, real-time pronunciation scoring, and generative media tools to create a truly immersive Japanese learning environment. [English](#english) | [日本語](#japanese) | [中文](#chinese) @@ -17,32 +16,33 @@ ## 🇬🇧 English -### ✨ Features +### ✨ Key Features -* **Tutor Dojo (Chat):** - * Free chat with Sakura (AI Tutor) using `gemini-3-pro-preview` (Reasoning) or `gemini-3-flash-preview`. - * **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). +#### 🗣️ Tutor Dojo (Context-Aware Chat) +* **Dual-Model Intelligence:** Switch instantly between **Gemini 3 Flash** (Fast & Responsive) and **Gemini 3 Pro** (Deep Reasoning) directly within the chat interface. +* **Deep Context:** The AI remembers your conversation history, allowing for natural, flowing dialogue and follow-up corrections. +* **Thinking Mode:** Visualize the AI's "thought process" as it breaks down complex grammar rules or cultural nuances before answering. +* **Multimodal Input:** Chat via text, voice (speech-to-text), or by uploading images for analysis. + +#### 🎭 Roleplay (Speaking Practice) +* **Real-world Scenarios:** Practice checking into a hotel, ordering at a konbini, or passing immigration. +* **AI Audio Feedback:** Receive instant, actionable feedback on your pronunciation, intonation, and grammar. +* **Native TTS:** High-fidelity Japanese Text-to-Speech powered by Gemini. + +#### 📜 Reading Hall & 🎧 Listening Lab +* **Custom Lesson Generation:** Generate unique reading materials and listening scripts tailored exactly to your JLPT level (N5–N1) and interests. +* **Interactive Study:** Click to translate, hear pronunciations, or ask the tutor specific questions about the generated content. +* **Comprehension Quizzes:** Test your understanding with AI-generated quizzes. + +#### 🎨 Creative Atelier +* **Visual Learning:** Generate images using **Imagen 3** to visualize vocabulary. +* **Video Immersion:** Create short, AI-generated videos using **Veo** to see cultural concepts in motion. + +#### 🧰 Toolbox +* **OCR Scanner:** Snap a photo of a textbook or menu; the AI extracts the text, translates it, and explains the grammar. +* **Smart Translator:** Context-aware translation for text and images. + +--- ### 🚀 Getting Started @@ -59,11 +59,11 @@ 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: + * **Option A (Recommended):** 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.* + * **Option B:** Enter the key directly in the app's **Settings** menu. 4. **Run the app:** ```bash @@ -72,118 +72,62 @@ ### 🛠 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-3-flash-preview` - * Audio: `gemini-2.5-flash-preview-tts` - * Vision/OCR: `gemini-3-flash-preview` - * Image Gen: `imagen-4.0-generate-001`, `gemini-2.5-flash-image` - * Video: `veo-3.1-fast-generate-preview` +* **Core:** React 19, TypeScript, Vite +* **Styling:** Tailwind CSS, Lucide React, Framer Motion-style CSS animations +* **AI SDK:** `@google/genai` +* **Models:** + * **Reasoning:** `gemini-3-pro-preview` / `gemini-3-flash-preview` + * **Speech:** `gemini-2.5-flash-preview-tts` + * **Vision:** `gemini-3-flash-preview` (OCR/Multimodal) + * **Image Generation:** `imagen-4.0-generate-001` + * **Image Editing:** `gemini-2.5-flash-image` + * **Video Generation:** `veo-3.1-fast-generate-preview` --- ## 🇯🇵 日本語 -**さくら先生**は、Googleの最新Geminiモデルを搭載した没入型の日本語学習プラットフォームです。文法解説、ロールプレイ、読解・聴解練習など、あらゆる学習ニーズに対応します。 +**さくら先生**は、Googleの最新Geminiモデル(Gemini 3 / Veo)を搭載した、没入型の日本語学習プラットフォームです。文脈を理解するAIとの会話、発音のリアルタイム分析、そして生成AIによる教材作成機能で、あなたの日本語学習を加速させます。 ### ✨ 主な機能 * **学習道場 (チャット):** - * `gemini-3-pro-preview` を活用した高度な推論能力を持つAIチューターとの会話。 - * 音声入力・音声再生対応。思考モード(Thinking Mode)でAIの考え方を可視化。 - * チャット履歴の画像共有機能。 -* **読書の間:** - * トピックと難易度(N5〜N1)を指定して、オリジナルの読み物を生成。 - * 単語リスト、文法解説、翻訳付き。テキストについてAIに質問可能。 -* **聴解ラボ:** - * リスニング練習用の会話スクリプトとクイズを自動生成。 - * 音声再生、スクリプトの表示/非表示切り替え。 + * 文脈を記憶するAIチューターと自然な会話が可能。 + * **モデル切り替え機能:** 高速な `Gemini 3 Flash` と、深い推論を行う `Gemini 3 Pro` を用途に合わせて選択可能。 + * **Thinking Mode:** 複雑な文法解説を行う際、AIの思考プロセスを可視化。 * **会話道場 (ロールプレイ):** - * カフェ、駅、入国審査などのリアルなシナリオで会話練習。 - * 発音、流暢さ、文法ミスに対する即時フィードバック機能。 + * カフェ、駅、ホテルなど、リアルな場面での会話練習。 + * 発音や文法ミスに対する即時フィードバック。 +* **読書の間 & 聴解ラボ:** + * トピックと難易度(N5〜N1)を指定して、あなただけの教材を自動生成。 + * 理解度クイズや、テキストに関する質疑応答機能付き。 * **アトリエ:** - * 画像生成 (`imagen-4.0`) や動画生成 (`veo-3.1`) で学習を視覚的にサポート。 + * `Imagen 3` による画像生成や `Veo` による動画生成で、視覚的に単語を記憶。 * **ツールボックス:** - * **スキャナー (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 - ``` + * **OCRスキャナー:** カメラで撮影した日本語テキストを瞬時に分析・解説。 --- ## 🇨🇳 中文 -**樱花老师 (Sakura Sensei)** 是一个基于 Google Gemini 模型的全能型 AI 日语学习助手。它集成了对话练习、阅读生成、听力训练和实时纠错功能,为您提供沉浸式的日语学习体验。 +**樱花老师 (Sakura Sensei)** 是一款基于 Google 最新 Gemini 3 和 Veo 模型的下一代 AI 日语学习助手。它具备上下文记忆功能,提供深度推理、发音纠正以及多模态生成工具,为您打造身临其境的日语学习体验。 ### ✨ 主要功能 -* **学习道场 (Tutor Chat):** - * 与 AI 导师自由对话,支持 `gemini-3-pro` 深度推理模式。 - * 支持语音输入 (STT) 和高质量语音朗读 (TTS)。 - * 支持将对话记录导出为图片、文本或文件。 -* **阅读室:** - * 根据您感兴趣的主题和 JLPT 等级 (N5-N1) 生成阅读文章。 - * 自动提取词汇表、语法点和翻译。支持针对文章内容的提问。 -* **听力实验室:** - * 生成包含理解测验的听力对话脚本。 - * 支持音频播放控制和脚本隐藏/显示。 -* **对话道场 (Roleplay):** - * 在真实场景(如便利店、机场、酒店)中进行角色扮演。 - * AI 会对您的发音、流利度和语法进行打分并提供建议。 +* **学习道场 (智能对话):** + * **双模型支持:** 在聊天界面直接切换 `Gemini 3 Flash` (极速) 和 `Gemini 3 Pro` (深度推理)。 + * **上下文记忆:** AI 能够记住之前的对话内容,提供连贯的辅导。 + * **思维链模式:** 可视化 AI 的思考过程,深度解析复杂语法。 +* **对话道场 (角色扮演):** + * 模拟真实场景(如便利店、机场入境),提供实时语音评分与建议。 +* **阅读室 & 听力实验室:** + * 根据您的 JLPT 等级 (N5-N1) 和兴趣,一键生成专属的阅读文章和听力测试。 * **创意工作室:** - * 使用 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 - ``` + * 利用 `Imagen` 生成助记图片,或使用 `Veo` 生成短视频辅助学习。 +* **实用工具箱:** + * **OCR 扫描:** 拍照识别日语文本,自动生成生词本和语法解析。 --- @@ -191,4 +135,4 @@ MIT License. -Powered by [Google Gemini API](https://ai.google.dev/). \ No newline at end of file +Powered by [Google Gemini API](https://ai.google.dev/). diff --git a/components/ChatBubble.tsx b/components/ChatBubble.tsx index 4f7ae98..69d9361 100644 --- a/components/ChatBubble.tsx +++ b/components/ChatBubble.tsx @@ -28,6 +28,13 @@ const ChatBubble: React.FC = ({ message, language, onUpdateMess const t = translations[language].chat; const tCommon = translations[language].common; + // Prevent rendering empty chat bubbles (e.g. initial placeholder before stream starts) + // This ensures the "thinking" loader in ChatView is the only thing shown initially + const isEmpty = !message.content?.trim() && !message.metadata?.imageUrl && !message.metadata?.audioUrl; + if (isEmpty) { + return null; + } + const stopAudio = () => { if (audioSourceRef.current) { audioSourceRef.current.stop(); diff --git a/releases/HTY1024-APP-SKR-0.11.0_20251229.zip b/releases/HTY1024-APP-SKR-0.11.0_20251229.zip new file mode 100644 index 0000000..c011e7e Binary files /dev/null and b/releases/HTY1024-APP-SKR-0.11.0_20251229.zip differ diff --git a/services/geminiService.ts b/services/geminiService.ts index ed7167a..90de1c9 100644 --- a/services/geminiService.ts +++ b/services/geminiService.ts @@ -1,7 +1,7 @@ import { GoogleGenAI, Modality, Type } from "@google/genai"; -import { PronunciationFeedback, Language, ReadingLesson, ReadingDifficulty, OCRAnalysis, ListeningLesson } from "../types"; +import { PronunciationFeedback, Language, ReadingLesson, ReadingDifficulty, OCRAnalysis, ListeningLesson, ChatMessage, Role, MessageType } from "../types"; import { base64ToUint8Array, uint8ArrayToBase64 } from "../utils/audioUtils"; export const USER_API_KEY_STORAGE = 'sakura_user_api_key'; @@ -118,13 +118,24 @@ class GeminiService { } } + // Helper to format history + private _formatHistory(history: ChatMessage[]): any[] { + return history + .filter(msg => (msg.type === MessageType.TEXT || msg.type === MessageType.AUDIO) && msg.content) + .map(msg => ({ + role: msg.role, + parts: [{ text: msg.content }] + })); + } + private _getChatConfig( prompt: string, imageBase64?: string, useThinking: boolean = false, language: Language = 'en', modelOverride?: string, - aiSpeakingLanguage: 'ja' | 'native' = 'native' + aiSpeakingLanguage: 'ja' | 'native' = 'native', + history: ChatMessage[] = [] ) { // Ensure model name is clean let modelName = useThinking @@ -135,20 +146,6 @@ class GeminiService { modelName = modelName.replace(/['"]/g, ''); const targetLangName = LANGUAGE_MAP[language]; - const parts: any[] = []; - - if (imageBase64) { - parts.push({ - inlineData: { - mimeType: 'image/jpeg', - data: imageBase64 - } - }); - parts.push({ text: `Analyze this image in the context of learning Japanese. Explain in ${targetLangName}: ` + prompt }); - } else { - parts.push({ text: prompt }); - } - let instruction = ""; if (aiSpeakingLanguage === 'ja') { instruction = `You are Sakura, a Japanese language tutor. @@ -164,6 +161,32 @@ class GeminiService { - Provide your explanations, translations, and feedback in ${targetLangName}.`; } + // Build contents + // 1. History + const contents = this._formatHistory(history); + + // 2. Current Turn + const currentParts: any[] = []; + + if (imageBase64) { + const cleanBase64 = imageBase64.replace(/^data:image\/\w+;base64,/, ""); + currentParts.push({ + inlineData: { + mimeType: 'image/jpeg', + data: cleanBase64 + } + }); + currentParts.push({ text: `Analyze this image in the context of learning Japanese. Explain in ${targetLangName}: ` + prompt }); + } else { + currentParts.push({ text: prompt }); + } + + // Append current turn + contents.push({ + role: 'user', + parts: currentParts + }); + const config: any = { systemInstruction: instruction, }; @@ -172,7 +195,7 @@ class GeminiService { config.thinkingConfig = { thinkingBudget: 32768 }; } - return { modelName, parts, config }; + return { modelName, contents, config }; } // 1. Text Chat Response - Returns { text, model } @@ -182,15 +205,16 @@ class GeminiService { useThinking: boolean = false, language: Language = 'en', modelOverride?: string, - aiSpeakingLanguage: 'ja' | 'native' = 'native' + aiSpeakingLanguage: 'ja' | 'native' = 'native', + history: ChatMessage[] = [] ): Promise<{ text: string, model: string }> { const ai = this.getAi(); - const { modelName, parts, config } = this._getChatConfig(prompt, imageBase64, useThinking, language, modelOverride, aiSpeakingLanguage); + const { modelName, contents, config } = this._getChatConfig(prompt, imageBase64, useThinking, language, modelOverride, aiSpeakingLanguage, history); return this.retryOperation(async () => { const response = await ai.models.generateContent({ model: modelName, - contents: { parts }, + contents: contents, config: config }); return { @@ -207,16 +231,17 @@ class GeminiService { useThinking: boolean = false, language: Language = 'en', modelOverride?: string, - aiSpeakingLanguage: 'ja' | 'native' = 'native' + aiSpeakingLanguage: 'ja' | 'native' = 'native', + history: ChatMessage[] = [] ): AsyncGenerator<{ text: string, model: string }> { const ai = this.getAi(); - const { modelName, parts, config } = this._getChatConfig(prompt, imageBase64, useThinking, language, modelOverride, aiSpeakingLanguage); + const { modelName, contents, config } = this._getChatConfig(prompt, imageBase64, useThinking, language, modelOverride, aiSpeakingLanguage, history); // Initial stream connection with retry logic const stream = await this.retryOperation(async () => { return await ai.models.generateContentStream({ model: modelName, - contents: { parts }, + contents: contents, config: config }); }); diff --git a/views/ChatView.tsx b/views/ChatView.tsx index a2d13f6..94b8a61 100644 --- a/views/ChatView.tsx +++ b/views/ChatView.tsx @@ -1,11 +1,11 @@ import React, { useState, useRef, useEffect } from 'react'; -import { ChatMessage, Role, MessageType, Language, ChatSession } from '../types'; +import { ChatMessage, Role, MessageType, Language, ChatSession, AVAILABLE_CHAT_MODELS } from '../types'; import { geminiService } from '../services/geminiService'; import ChatBubble from '../components/ChatBubble'; import AudioRecorder from '../components/AudioRecorder'; -import { Send, Image as ImageIcon, BrainCircuit, Loader2, Plus, History, MessageSquare, Trash2, X, Sparkles, PanelRightClose, PanelRightOpen, Share2, Download, FileText, Image as ImageIconLucide, Languages } from 'lucide-react'; +import { Send, Image as ImageIcon, BrainCircuit, Loader2, Plus, History, MessageSquare, Trash2, X, Sparkles, PanelRightClose, PanelRightOpen, Share2, Download, FileText, Image as ImageIconLucide, Languages, ChevronDown } from 'lucide-react'; import { translations } from '../utils/localization'; import html2canvas from 'html2canvas'; @@ -18,7 +18,6 @@ interface ChatViewProps { onDeleteSession: (id: string) => void; onClearAllSessions: () => void; onUpdateSession: (id: string, messages: ChatMessage[]) => void; - selectedModel?: string; addToast: (type: 'success' | 'error' | 'info', msg: string) => void; } @@ -31,7 +30,6 @@ const ChatView: React.FC = ({ onDeleteSession, onClearAllSessions, onUpdateSession, - selectedModel, addToast }) => { const t = translations[language].chat; @@ -45,6 +43,11 @@ const ChatView: React.FC = ({ const [useThinking, setUseThinking] = useState(false); const [attachedImage, setAttachedImage] = useState(null); + // Local Model State + const [selectedModel, setSelectedModel] = useState(() => { + return localStorage.getItem('sakura_chat_selected_model') || AVAILABLE_CHAT_MODELS[0].id; + }); + // Settings State const [aiSpeakingLanguage, setAiSpeakingLanguage] = useState<'ja' | 'native'>('ja'); const [isShareMenuOpen, setIsShareMenuOpen] = useState(false); @@ -56,6 +59,10 @@ const ChatView: React.FC = ({ const messagesContainerRef = useRef(null); const fileInputRef = useRef(null); + useEffect(() => { + localStorage.setItem('sakura_chat_selected_model', selectedModel); + }, [selectedModel]); + const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; @@ -124,7 +131,8 @@ const ChatView: React.FC = ({ useThinking, language, selectedModel, - aiSpeakingLanguage + aiSpeakingLanguage, + messages // Pass history (prior to current message) ); for await (const chunk of stream) { @@ -199,7 +207,15 @@ const ChatView: React.FC = ({ // 4. Stream Response let fullText = ""; let modelUsed = ""; - const stream = geminiService.generateTextStream(transcription, undefined, false, language, selectedModel, aiSpeakingLanguage); + const stream = geminiService.generateTextStream( + transcription, + undefined, + false, + language, + selectedModel, + aiSpeakingLanguage, + messages // Pass history + ); for await (const chunk of stream) { fullText += chunk.text; @@ -393,10 +409,24 @@ const ChatView: React.FC = ({ {/* Header / Toolbar */}
-
- - {selectedModel ? selectedModel.replace('gemini-', '').replace('-preview', '') : 'AI'} -
+
+
+ + + {AVAILABLE_CHAT_MODELS.find(m => m.id === selectedModel)?.name.split('(')[0].trim() || 'Gemini'} + + + +
+
{/* AI Language Toggle */}