diff --git a/App.tsx b/App.tsx
index 20e0cbd..eb04be1 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,4 +1,5 @@
+
import React, { useState, useRef, useEffect } from 'react';
import ChatView from './views/ChatView';
import CreativeStudio from './views/CreativeStudio';
@@ -10,7 +11,7 @@ import ListeningView from './views/ListeningView';
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 } from 'lucide-react';
+import { MessageCircle, Palette, Mic2, Settings, Globe, Sparkles, BookOpen, Languages, Download, Upload, FileText, X, ScanText, Key, Save, Trash2, Menu, BrainCircuit, Link, Headphones, AlertTriangle } from 'lucide-react';
import { AppMode, Language, ChatMessage, TranslationRecord, AppDataBackup, Role, MessageType, ReadingLessonRecord, AVAILABLE_CHAT_MODELS, ChatSession, OCRRecord, ListeningLessonRecord } from './types';
import { translations } from './utils/localization';
import { USER_API_KEY_STORAGE, USER_BASE_URL_STORAGE } from './services/geminiService';
@@ -312,6 +313,35 @@ const App: React.FC = () => {
});
};
+ const handleClearData = () => {
+ setConfirmState({
+ isOpen: true,
+ title: t.settings.clearDataTitle,
+ message: t.settings.clearDataConfirm,
+ onConfirm: () => {
+ // 1. Reset State
+ setChatSessions([]);
+ setTranslationHistory([]);
+ setReadingHistory([]);
+ setListeningHistory([]);
+ setOcrHistory([]);
+
+ // 2. Clear Storage Explicitly
+ localStorage.removeItem(STORAGE_KEYS.CHAT_SESSIONS);
+ localStorage.removeItem(STORAGE_KEYS.TRANSLATION_HISTORY);
+ localStorage.removeItem(STORAGE_KEYS.READING_HISTORY);
+ localStorage.removeItem(STORAGE_KEYS.LISTENING_HISTORY);
+ localStorage.removeItem(STORAGE_KEYS.OCR_HISTORY);
+
+ // 3. Re-init session
+ createNewSession();
+
+ addToast('success', t.common.dataCleared);
+ setConfirmState(prev => ({ ...prev, isOpen: false }));
+ }
+ });
+ };
+
const addToast = (type: 'success' | 'error' | 'info', message: string) => {
const id = Date.now().toString();
setToasts(prev => [...prev, { id, type, message }]);
@@ -536,6 +566,18 @@ const App: React.FC = () => {
+
+ {/* Danger Zone */}
+
+
{t.settings.clearDataTitle}
+
+
+
{t.settings.clearDataDesc}
+
+
+
+
+
@@ -544,4 +586,4 @@ const App: React.FC = () => {
);
};
-export default App;
+export default App;
\ No newline at end of file
diff --git a/README.md b/README.md
index 098f1f6..50b0df3 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+
+
# Sakura Sensei 🌸 - AI Japanese Tutor

@@ -18,7 +20,7 @@
### ✨ Features
* **Tutor Dojo (Chat):**
- * Free chat with Sakura (AI Tutor) using `gemini-3-pro-preview` (Reasoning) or `gemini-2.5-flash`.
+ * 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).
@@ -74,9 +76,9 @@
* **Styling:** Tailwind CSS, Lucide React (Icons)
* **AI Integration:** `@google/genai` SDK
* **Models Used:**
- * Text/Reasoning: `gemini-3-pro-preview`, `gemini-2.5-flash`
+ * Text/Reasoning: `gemini-3-pro-preview`, `gemini-3-flash-preview`
* Audio: `gemini-2.5-flash-preview-tts`
- * Vision/OCR: `gemini-2.5-flash`
+ * 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`
@@ -189,4 +191,4 @@
MIT License.
-Powered by [Google Gemini API](https://ai.google.dev/).
+Powered by [Google Gemini API](https://ai.google.dev/).
\ No newline at end of file
diff --git a/components/ErrorBoundary.tsx b/components/ErrorBoundary.tsx
index 98590f8..fc2eb8c 100644
--- a/components/ErrorBoundary.tsx
+++ b/components/ErrorBoundary.tsx
@@ -1,4 +1,4 @@
-import React, { Component, ErrorInfo, ReactNode } from "react";
+import React, { ErrorInfo, ReactNode } from "react";
import { AlertCircle, RefreshCw, Trash2 } from 'lucide-react';
interface Props {
@@ -10,7 +10,7 @@ interface State {
error: Error | null;
}
-export class ErrorBoundary extends Component {
+export class ErrorBoundary extends React.Component {
public state: State = {
hasError: false,
error: null
diff --git a/releases/HTY1024-APP-SKR-0.8.0_20251223.zip b/releases/HTY1024-APP-SKR-0.8.0_20251223.zip
new file mode 100644
index 0000000..0a75c72
Binary files /dev/null and b/releases/HTY1024-APP-SKR-0.8.0_20251223.zip differ
diff --git a/services/geminiService.ts b/services/geminiService.ts
index 18bec9d..2498fdd 100644
--- a/services/geminiService.ts
+++ b/services/geminiService.ts
@@ -132,7 +132,7 @@ class GeminiService {
// Ensure model name is clean
let modelName = useThinking
? 'gemini-3-pro-preview'
- : (imageBase64 ? 'gemini-3-pro-preview' : (modelOverride || 'gemini-2.5-flash'));
+ : (imageBase64 ? 'gemini-3-pro-preview' : (modelOverride || 'gemini-3-flash-preview'));
// Extra safety: strip quotes just in case
modelName = modelName.replace(/['"]/g, '');
@@ -278,7 +278,7 @@ class GeminiService {
const ai = this.getAi();
return this.retryOperation(async () => {
const response = await ai.models.generateContent({
- model: 'gemini-2.5-flash',
+ model: 'gemini-3-flash-preview',
contents: {
parts: [
{ inlineData: { mimeType: 'audio/wav', data: audioBase64 } },
@@ -368,7 +368,7 @@ class GeminiService {
return this.retryOperation(async () => {
const response = await ai.models.generateContent({
- model: 'gemini-2.5-flash',
+ model: 'gemini-3-flash-preview',
contents: {
parts: [{ inlineData: { mimeType: 'audio/wav', data: audioBase64 } }, { text: prompt }]
},
@@ -401,7 +401,7 @@ class GeminiService {
return this.retryOperation(async () => {
const response = await ai.models.generateContent({
- model: 'gemini-2.5-flash',
+ model: 'gemini-3-flash-preview',
contents: { parts: [{ text: prompt }] },
config: {
responseMimeType: "application/json",
@@ -439,7 +439,7 @@ class GeminiService {
return this.retryOperation(async () => {
const response = await ai.models.generateContent({
- model: 'gemini-2.5-flash',
+ model: 'gemini-3-flash-preview',
contents: { parts: [{ text: prompt }] },
config: {
responseMimeType: "application/json",
@@ -481,7 +481,7 @@ class GeminiService {
const prompt = `Tutor for text "${lesson.title}". Question: "${question}". History: ${history}. Explain in ${LANGUAGE_MAP[language]}.`;
return this.retryOperation(async () => {
const res = await ai.models.generateContent({
- model: 'gemini-2.5-flash',
+ model: 'gemini-3-flash-preview',
contents: { parts: [{ text: prompt }] }
});
return res.text || "";
@@ -492,7 +492,7 @@ class GeminiService {
const ai = this.getAi();
return this.retryOperation(async () => {
const res = await ai.models.generateContent({
- model: 'gemini-2.5-flash',
+ model: 'gemini-3-flash-preview',
contents: { parts: [{ text: `Translate the following text from ${source} to ${target}.` }, { text: text }] },
config: {
responseMimeType: "application/json",
@@ -512,7 +512,7 @@ class GeminiService {
const cleanBase64 = base64.replace(/^data:image\/(png|jpeg|jpg|webp|heic|heif);base64,/i, "");
return this.retryOperation(async () => {
const res = await ai.models.generateContent({
- model: 'gemini-2.5-flash',
+ model: 'gemini-3-flash-preview',
contents: {
parts: [{ inlineData: { mimeType: 'image/jpeg', data: cleanBase64 } }, { text: `Extract text (Language: ${source}) and translate to ${target}. JSON output: original, translated.` }]
},
@@ -537,7 +537,7 @@ class GeminiService {
return this.retryOperation(async () => {
const res = await ai.models.generateContent({
- model: 'gemini-2.5-flash',
+ model: 'gemini-3-flash-preview',
contents: {
parts: [{ inlineData: { mimeType: 'image/jpeg', data: cleanBase64 } }, { text: prompt }]
},
diff --git a/types.ts b/types.ts
index 65a9cef..0e00be2 100644
--- a/types.ts
+++ b/types.ts
@@ -51,18 +51,18 @@ export type Language = 'en' | 'ja' | 'zh';
// Specific Gemini Models
export enum ModelNames {
- TEXT_FAST = 'gemini-2.5-flash',
+ TEXT_FAST = 'gemini-3-flash-preview',
TEXT_REASONING = 'gemini-3-pro-preview',
TTS = 'gemini-2.5-flash-preview-tts',
IMAGE_GEN = 'imagen-4.0-generate-001',
IMAGE_EDIT = 'gemini-2.5-flash-image', // Nano Banana
VIDEO_GEN = 'veo-3.1-fast-generate-preview',
- TRANSCRIPTION = 'gemini-2.5-flash',
+ TRANSCRIPTION = 'gemini-3-flash-preview',
}
export const AVAILABLE_CHAT_MODELS = [
{ id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro (Default - Best Reasoning)' },
- { id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash (Fast & Balanced)' }
+ { id: 'gemini-3-flash-preview', name: 'Gemini 3 Flash (Fast & Balanced)' }
];
// Speaking Mode Types
diff --git a/utils/localization.ts b/utils/localization.ts
index 0db96fc..cfd683b 100644
--- a/utils/localization.ts
+++ b/utils/localization.ts
@@ -218,7 +218,8 @@ export const translations = {
today: "Today",
yesterday: "Yesterday",
storageFull: "Storage full. History may not be saved.",
- storageOptimized: "Storage full. Cleared audio cache to save text."
+ storageOptimized: "Storage full. Cleared audio cache to save text.",
+ dataCleared: "All data has been cleared."
},
onboarding: {
welcome: "Welcome to Sakura Sensei!",
@@ -420,7 +421,11 @@ export const translations = {
keyRemoved: "Settings cleared.",
modelTitle: "AI Model",
modelDesc: "Select model for chat/reasoning.",
- modelSaved: "Model updated!"
+ modelSaved: "Model updated!",
+ clearDataTitle: "Danger Zone",
+ clearDataBtn: "Clear All Data",
+ clearDataDesc: "Deletes all chats and history. Configuration is preserved.",
+ clearDataConfirm: "Are you sure? This will delete ALL chat history, lessons, and scans.\n\n⚠️ WARNING: This action is irreversible.\n👉 TIP: Please BACKUP your data before proceeding!"
},
recorder: {
start: "Start Mic",
@@ -472,7 +477,8 @@ export const translations = {
today: "今日",
yesterday: "昨日",
storageFull: "保存容量がいっぱいです。履歴が保存されない可能性があります。",
- storageOptimized: "保存容量がいっぱいのため、音声キャッシュを削除してテキストのみ保存しました。"
+ storageOptimized: "保存容量がいっぱいのため、音声キャッシュを削除してテキストのみ保存しました。",
+ dataCleared: "すべてのデータが消去されました。"
},
onboarding: {
welcome: "さくら先生へようこそ!",
@@ -674,7 +680,11 @@ export const translations = {
keyRemoved: "設定をクリアしました。",
modelTitle: "AIモデル",
modelDesc: "チャット/推論用のモデルを選択。",
- modelSaved: "モデルを更新しました!"
+ modelSaved: "モデルを更新しました!",
+ clearDataTitle: "危険エリア",
+ clearDataBtn: "すべてのデータを消去",
+ clearDataDesc: "チャットや履歴をすべて削除します。設定は保持されます。",
+ clearDataConfirm: "本当に実行しますか?すべてのチャット履歴、レッスン、スキャン記録が削除されます。\n\n⚠️ 警告:この操作は取り消せません。\n👉 ヒント:実行前にバックアップを作成することを強く推奨します!"
},
recorder: {
start: "マイク開始",
@@ -726,7 +736,8 @@ export const translations = {
today: "今天",
yesterday: "昨天",
storageFull: "存储空间已满,历史记录可能无法保存。",
- storageOptimized: "存储空间已满,已自动清除音频缓存以保存文本。"
+ storageOptimized: "存储空间已满,已自动清除音频缓存以保存文本。",
+ dataCleared: "所有数据已清除。"
},
onboarding: {
welcome: "欢迎来到樱花老师!",
@@ -928,7 +939,11 @@ export const translations = {
keyRemoved: "设置已清除。",
modelTitle: "AI模型",
modelDesc: "选择聊天/推理模型。",
- modelSaved: "模型已更新!"
+ modelSaved: "模型已更新!",
+ clearDataTitle: "危险区域",
+ clearDataBtn: "清除所有数据",
+ clearDataDesc: "删除所有聊天和历史记录。保留设置。",
+ clearDataConfirm: "您确定吗?这将删除所有聊天记录、课程和扫描记录。\n\n⚠️ 警告:此操作无法撤销。\n👉 提示:请在继续之前先备份您的数据!"
},
recorder: {
start: "开始录音",
diff --git a/views/ListeningView.tsx b/views/ListeningView.tsx
index d790308..e545863 100644
--- a/views/ListeningView.tsx
+++ b/views/ListeningView.tsx
@@ -746,23 +746,23 @@ const ListeningView: React.FC = ({ language, history, onSave
)}
-
- {/* Floating Ask Button */}
- {selectedText && (
-
-
-
- )}
)}
+
+ {/* Floating Ask Button */}
+ {selectedText && (
+
+
+
+ )}
{/* Right: Tutor Chat */}
diff --git a/views/OCRView.tsx b/views/OCRView.tsx
index 9944cc5..a9e377a 100644
--- a/views/OCRView.tsx
+++ b/views/OCRView.tsx
@@ -359,7 +359,7 @@ const OCRView: React.FC = ({ language, history, onSaveToHistory, o
{/* LEFT: Main Content (Image, Text, Notes, Vocab) */}
-
+
{/* Header */}
@@ -402,108 +402,109 @@ const OCRView: React.FC = ({ language, history, onSaveToHistory, o
{/* Content Scroll Area */}
-
-
- {/* 1. Image & Extracted Text */}
-
-
-
- {imagePreview ? (
-

- ) : (
-
-
-
- )}
-
-
-
-
-
{t.extractedTitle}
-
-
-
+
+
+ {/* 1. Image & Extracted Text */}
+
+
+
+ {imagePreview ? (
+

+ ) : (
+
+
+
+ )}
-
- {analysis?.extractedText || ''}
-
-
-
-
- {/* 2. Summary */}
-
-
{t.summaryHeader}
-
{analysis?.summary || ''}
-
-
- {/* 3. Vocabulary */}
-
-
{t.vocabHeader}
-
- {analysis?.vocabulary?.map((v, i) => (
- v ? (
-
-
-
- {v.word || ''}
- ({v.reading || ''})
-
+
+
+
{t.extractedTitle}
+
+
-
{v.meaning || ''}
- ) : null
- ))}
+
+ {analysis?.extractedText || ''}
+
+
-
- {/* 4. Grammar */}
- {analysis?.grammarPoints && analysis.grammarPoints?.length > 0 && (
-
-
{t.grammarHeader}
-
- {analysis.grammarPoints.map((g, i) => (
- g ? (
-
-
{g.point || ''}
-
{g.explanation || ''}
+ {/* 2. Summary */}
+
+
{t.summaryHeader}
+
{analysis?.summary || ''}
+
+
+ {/* 3. Vocabulary */}
+
+
{t.vocabHeader}
+
+ {analysis?.vocabulary?.map((v, i) => (
+ v ? (
+
+
+
+ {v.word || ''}
+ ({v.reading || ''})
+
+
+
+
{v.meaning || ''}
) : null
))}
- )}
- {/* Floating Ask Button */}
- {selectedText && (
-
-
-
- )}
+ {/* 4. Grammar */}
+ {analysis?.grammarPoints && analysis.grammarPoints?.length > 0 && (
+
+
{t.grammarHeader}
+
+ {analysis.grammarPoints.map((g, i) => (
+ g ? (
+
+
{g.point || ''}
+
{g.explanation || ''}
+
+ ) : null
+ ))}
+
+
+ )}
+
+
+ {/* Floating Ask Button - Fixed in parent */}
+ {selectedText && (
+
+
+
+ )}
{/* RIGHT: Tutor Chat (Tab: tutor) */}
diff --git a/views/ReadingView.tsx b/views/ReadingView.tsx
index bc35067..230d958 100644
--- a/views/ReadingView.tsx
+++ b/views/ReadingView.tsx
@@ -644,20 +644,20 @@ const ReadingView: React.FC
= ({ language, history, onSaveToHi
)}
-
- {/* Floating Ask Button */}
- {selectedText && (
-
-
-
- )}
+
+ {/* Floating Ask Button - Fixed Position in Parent */}
+ {selectedText && (
+
+
+
+ )}
{/* Right: Tutor Chat (Only visible in lesson mode) */}