Compare commits
2 Commits
v0.1.0_202
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bc88f7d6a | |||
| efa471aa2e |
505
App.tsx
505
App.tsx
@@ -1,10 +1,12 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import {
|
||||
Settings, MessageSquare, Brain, Search, Image as ImageIcon,
|
||||
Settings, Brain, Search, Image as ImageIcon,
|
||||
Video, Mic, Send, Upload, Download, Copy, Share2,
|
||||
Menu, X, Sun, Moon, Volume2, Globe, Trash2, Plus, Info,
|
||||
PanelRight, PanelRightClose, History, Home as HomeIcon,
|
||||
Sparkles, Layers, Sliders, MonitorDown, AlertTriangle
|
||||
Sparkles, Layers, Sliders, MonitorDown, AlertTriangle,
|
||||
Sigma, Cpu, Code, Box, Network, Bot, Binary, FileText, Database, ChevronDown,
|
||||
CheckCircle, AlertCircle, Languages
|
||||
} from 'lucide-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
@@ -20,7 +22,7 @@ import { GenerateContentResponse } from '@google/genai';
|
||||
// Button Component
|
||||
const Button: React.FC<React.ButtonHTMLAttributes<HTMLButtonElement> & { variant?: 'primary' | 'secondary' | 'ghost' | 'danger' }> =
|
||||
({ className = '', variant = 'primary', children, ...props }) => {
|
||||
const baseStyle = "px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed active:scale-95";
|
||||
const baseStyle = "px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed active:scale-95 disabled:active:scale-100";
|
||||
const variants = {
|
||||
primary: "bg-blue-600 text-white hover:bg-blue-700 shadow-md hover:shadow-lg hover:-translate-y-0.5",
|
||||
secondary: "bg-white dark:bg-slate-800 text-gray-700 dark:text-gray-200 border border-gray-200 dark:border-slate-700 hover:bg-gray-50 dark:hover:bg-slate-700 hover:-translate-y-0.5 shadow-sm",
|
||||
@@ -46,14 +48,32 @@ const Modal: React.FC<{ isOpen: boolean; onClose: () => void; title: string; chi
|
||||
);
|
||||
};
|
||||
|
||||
// Toast Type
|
||||
interface ToastItem {
|
||||
id: number;
|
||||
message: string;
|
||||
type: 'success' | 'error' | 'info' | 'warning';
|
||||
}
|
||||
|
||||
// Constants for UI
|
||||
const SIDEBAR_GROUPS = [
|
||||
{
|
||||
title: 'group.learning',
|
||||
title: 'group.cs',
|
||||
modules: [
|
||||
{ id: AppModule.MATH, icon: Sigma, label: 'module.math', desc: 'desc.math' },
|
||||
{ id: AppModule.THEORY, icon: Binary, label: 'module.theory', desc: 'desc.theory' },
|
||||
{ id: AppModule.PRINCIPLES, icon: Cpu, label: 'module.principles', desc: 'desc.principles' },
|
||||
{ id: AppModule.SOFT_ENG, icon: Code, label: 'module.soft_eng', desc: 'desc.soft_eng' },
|
||||
{ id: AppModule.GRAPHICS, icon: Box, label: 'module.graphics', desc: 'desc.graphics' },
|
||||
{ id: AppModule.NETWORK, icon: Network, label: 'module.network', desc: 'desc.network' },
|
||||
{ id: AppModule.AI_LAB, icon: Bot, label: 'module.ai_lab', desc: 'desc.ai_lab' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'group.tools',
|
||||
modules: [
|
||||
{ id: AppModule.TUTOR, icon: MessageSquare, label: 'module.tutor', desc: 'desc.tutor' },
|
||||
{ id: AppModule.THINKER, icon: Brain, label: 'module.thinker', desc: 'desc.thinker' },
|
||||
{ id: AppModule.RESEARCH, icon: Search, label: 'module.research', desc: 'desc.research' },
|
||||
{ id: AppModule.SQL, icon: Database, label: 'module.sql', desc: 'desc.sql' },
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -66,16 +86,29 @@ const SIDEBAR_GROUPS = [
|
||||
}
|
||||
];
|
||||
|
||||
// Helper for safe URL parsing
|
||||
const getHostname = (url: string) => {
|
||||
try {
|
||||
if (!url) return '';
|
||||
return new URL(url).hostname;
|
||||
} catch (e) {
|
||||
return url;
|
||||
}
|
||||
};
|
||||
|
||||
// Main App
|
||||
export default function App() {
|
||||
// --- State ---
|
||||
const [settings, setSettings] = useState<AppSettings>(() => {
|
||||
const saved = localStorage.getItem('bitsage_settings');
|
||||
return saved ? JSON.parse(saved) : {
|
||||
const parsed = saved ? JSON.parse(saved) : {};
|
||||
return {
|
||||
apiKey: '',
|
||||
language: 'zh-CN',
|
||||
theme: 'system',
|
||||
hasCompletedOnboarding: false
|
||||
hasCompletedOnboarding: false,
|
||||
aiResponseLanguage: 'system', // Default: Follow system language
|
||||
...parsed
|
||||
};
|
||||
});
|
||||
|
||||
@@ -85,7 +118,7 @@ export default function App() {
|
||||
});
|
||||
|
||||
const [currentSessionId, setCurrentSessionId] = useState<string | null>(null);
|
||||
const [currentModule, setCurrentModule] = useState<AppModule>(AppModule.TUTOR);
|
||||
const [currentModule, setCurrentModule] = useState<AppModule>(AppModule.MATH);
|
||||
const [isHome, setIsHome] = useState(true);
|
||||
|
||||
const [inputText, setInputText] = useState('');
|
||||
@@ -93,9 +126,16 @@ export default function App() {
|
||||
const [isHistoryOpen, setIsHistoryOpen] = useState(() => typeof window !== 'undefined' && window.innerWidth >= 1024); // Right Sidebar
|
||||
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||
const [isShareModalOpen, setIsShareModalOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loadingText, setLoadingText] = useState('');
|
||||
|
||||
// Toast State
|
||||
const [toasts, setToasts] = useState<ToastItem[]>([]);
|
||||
|
||||
// Thinking Toggle State
|
||||
const [isThinkingMode, setIsThinkingMode] = useState(false);
|
||||
|
||||
// PWA Install Prompt
|
||||
const [installPrompt, setInstallPrompt] = useState<any>(null);
|
||||
|
||||
@@ -106,6 +146,13 @@ export default function App() {
|
||||
const [veoConfig, setVeoConfig] = useState<VeoConfig>({ aspectRatio: '16:9', resolution: '720p' });
|
||||
const [imgConfig, setImgConfig] = useState<ImageConfig>({ size: '1K', aspectRatio: '1:1' });
|
||||
|
||||
// SQL Tool State
|
||||
const [sqlInput, setSqlInput] = useState('');
|
||||
const [sqlOutput, setSqlOutput] = useState('');
|
||||
const [sqlTargetDB, setSqlTargetDB] = useState('MySQL');
|
||||
const [isCustomSqlOpen, setIsCustomSqlOpen] = useState(false);
|
||||
const [sqlCustomPrompt, setSqlCustomPrompt] = useState('');
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const geminiRef = useRef<GeminiService | null>(null);
|
||||
|
||||
@@ -131,23 +178,31 @@ export default function App() {
|
||||
}, [settings]);
|
||||
|
||||
useEffect(() => {
|
||||
// Only save sessions that are NOT from Creative Studio modules
|
||||
// Only save sessions that are NOT from Custom View modules
|
||||
const sessionsToSave = sessions.filter(s =>
|
||||
![AppModule.VISION, AppModule.STUDIO, AppModule.AUDIO].includes(s.module)
|
||||
![AppModule.VISION, AppModule.STUDIO, AppModule.AUDIO, AppModule.SQL].includes(s.module)
|
||||
);
|
||||
localStorage.setItem('bitsage_sessions', JSON.stringify(sessionsToSave));
|
||||
}, [sessions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isHome && !isCreativeModule(currentModule)) {
|
||||
if (!isHome && !isCustomViewModule(currentModule)) {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}, [sessions, currentSessionId, isLoading, isHome]);
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
const isCreativeModule = (mod: AppModule) => {
|
||||
return [AppModule.VISION, AppModule.STUDIO, AppModule.AUDIO].includes(mod);
|
||||
const showToast = (message: string, type: ToastItem['type'] = 'info') => {
|
||||
const id = Date.now();
|
||||
setToasts(prev => [...prev, { id, message, type }]);
|
||||
setTimeout(() => {
|
||||
setToasts(prev => prev.filter(t => t.id !== id));
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const isCustomViewModule = (mod: AppModule) => {
|
||||
return [AppModule.VISION, AppModule.STUDIO, AppModule.AUDIO, AppModule.SQL].includes(mod);
|
||||
};
|
||||
|
||||
const getCurrentSession = useCallback(() => {
|
||||
@@ -160,8 +215,8 @@ export default function App() {
|
||||
setCurrentSessionId(null); // Draft mode
|
||||
if (window.innerWidth < 768) setIsSidebarOpen(false);
|
||||
|
||||
// Auto-open history on large screens ONLY if NOT creative module
|
||||
if (isCreativeModule(module)) {
|
||||
// Auto-open history on large screens ONLY if NOT custom view module
|
||||
if (isCustomViewModule(module)) {
|
||||
setIsHistoryOpen(false);
|
||||
} else if (window.innerWidth >= 1024) {
|
||||
setIsHistoryOpen(true);
|
||||
@@ -239,9 +294,9 @@ export default function App() {
|
||||
const data = JSON.parse(evt.target?.result as string);
|
||||
if (data.settings) setSettings(data.settings);
|
||||
if (data.sessions) setSessions(data.sessions);
|
||||
alert(t('alert.import_success', settings.language));
|
||||
showToast(t('success.data_imported', settings.language), 'success');
|
||||
} catch (err) {
|
||||
alert(t('alert.invalid_file', settings.language));
|
||||
showToast(t('alert.invalid_file', settings.language), 'error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
@@ -263,11 +318,175 @@ export default function App() {
|
||||
}
|
||||
};
|
||||
|
||||
// --- SQL Tool Actions ---
|
||||
|
||||
const handleSqlFormat = async () => {
|
||||
if(!sqlInput.trim()) {
|
||||
showToast(t('warning.no_sql', settings.language), 'warning');
|
||||
return;
|
||||
}
|
||||
if(!settings.apiKey) {
|
||||
setIsSettingsOpen(true);
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
setLoadingText(t('sql.processing', settings.language));
|
||||
try {
|
||||
const res = await geminiRef.current!.toolsSql(sqlInput, 'format');
|
||||
setSqlOutput(res);
|
||||
} catch (e) {
|
||||
setSqlOutput("Error: " + (e as Error).message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setLoadingText('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSqlConvert = async () => {
|
||||
if(!sqlInput.trim()) {
|
||||
showToast(t('warning.no_sql', settings.language), 'warning');
|
||||
return;
|
||||
}
|
||||
if(!settings.apiKey) {
|
||||
setIsSettingsOpen(true);
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
setLoadingText(t('sql.processing', settings.language));
|
||||
try {
|
||||
const res = await geminiRef.current!.toolsSql(sqlInput, 'convert', sqlTargetDB);
|
||||
setSqlOutput(res);
|
||||
} catch (e) {
|
||||
setSqlOutput("Error: " + (e as Error).message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setLoadingText('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSqlReplace = () => {
|
||||
if(!sqlInput.trim()) {
|
||||
showToast(t('warning.no_sql', settings.language), 'warning');
|
||||
return;
|
||||
}
|
||||
let counter = 1;
|
||||
// Regex to find AS followed by whitespace and an identifier
|
||||
// Handles quoted identifiers roughly
|
||||
const regex = /\bAS\s+((?:`[^`]+`)|(?:"[^"]+")|(?:'[^']+')|(?:\w+))/gi;
|
||||
const result = sqlInput.replace(regex, () => {
|
||||
return `AS ${counter++}`;
|
||||
});
|
||||
setSqlOutput(result);
|
||||
};
|
||||
|
||||
const handleSqlMinify = () => {
|
||||
if(!sqlInput.trim()) {
|
||||
showToast(t('warning.no_sql', settings.language), 'warning');
|
||||
return;
|
||||
}
|
||||
// Basic minification: remove comments, collapse whitespace
|
||||
let res = sqlInput
|
||||
.replace(/--.*$/gm, '') // remove single line comments
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '') // remove multi line comments
|
||||
.replace(/\s+/g, ' ') // collapse whitespace
|
||||
.trim();
|
||||
setSqlOutput(res);
|
||||
};
|
||||
|
||||
const handleSqlCustom = async () => {
|
||||
// Allow empty sqlInput if user is asking for generation
|
||||
if(!sqlCustomPrompt.trim()) {
|
||||
showToast("Please describe what you want the AI to do.", 'warning');
|
||||
return;
|
||||
}
|
||||
if(!settings.apiKey) {
|
||||
setIsSettingsOpen(true);
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
setLoadingText(t('sql.processing', settings.language));
|
||||
try {
|
||||
const res = await geminiRef.current!.toolsSql(sqlInput, 'custom', undefined, sqlCustomPrompt);
|
||||
setSqlOutput(res);
|
||||
} catch (e) {
|
||||
setSqlOutput("Error: " + (e as Error).message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setLoadingText('');
|
||||
setIsCustomSqlOpen(false); // Auto close after run
|
||||
}
|
||||
};
|
||||
|
||||
// --- Share & Download Actions ---
|
||||
|
||||
const getModelDisplayName = (module: AppModule) => {
|
||||
if (module === AppModule.STUDIO) return 'Veo 3.1';
|
||||
if (module === AppModule.RESEARCH || module === AppModule.AUDIO) return 'Gemini 3 Flash';
|
||||
return 'Gemini 3 Pro';
|
||||
};
|
||||
|
||||
const handleCopySession = () => {
|
||||
const session = getCurrentSession();
|
||||
if (!session) return;
|
||||
|
||||
const text = session.messages.map(m => {
|
||||
const role = m.role === MessageRole.USER ? t('role.user', settings.language) : getModelDisplayName(session.module);
|
||||
const time = new Date(m.timestamp).toLocaleString();
|
||||
return `[${role} - ${time}]\n${m.text || '[Media]'}\n`;
|
||||
}).join('\n-------------------\n');
|
||||
|
||||
navigator.clipboard.writeText(text);
|
||||
showToast(t('success.copy', settings.language), 'success');
|
||||
setIsShareModalOpen(false);
|
||||
};
|
||||
|
||||
const handleDownloadText = () => {
|
||||
const session = getCurrentSession();
|
||||
if (!session) return;
|
||||
|
||||
const text = session.messages.map(m => {
|
||||
const role = m.role === MessageRole.USER ? t('role.user', settings.language) : getModelDisplayName(session.module);
|
||||
const time = new Date(m.timestamp).toLocaleString();
|
||||
return `[${role} - ${time}]\n${m.text || '[Media]'}\n`;
|
||||
}).join('\n-------------------\n');
|
||||
|
||||
const blob = new Blob([text], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `bitsage-chat-${session.id}.txt`;
|
||||
a.click();
|
||||
setIsShareModalOpen(false);
|
||||
};
|
||||
|
||||
const handleDownloadImage = async () => {
|
||||
const el = document.getElementById('chat-content');
|
||||
if (!el) return;
|
||||
|
||||
try {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
const canvas = await html2canvas(el, {
|
||||
backgroundColor: isDark ? '#0f172a' : '#f9fafb', // slate-900 or gray-50
|
||||
scale: 2, // High res
|
||||
});
|
||||
const link = document.createElement('a');
|
||||
link.download = `bitsage-chat-${Date.now()}.png`;
|
||||
link.href = canvas.toDataURL();
|
||||
link.click();
|
||||
setIsShareModalOpen(false);
|
||||
} catch (e) {
|
||||
console.error("Screenshot failed", e);
|
||||
showToast(t('error.screenshot', settings.language), 'error');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- Core Actions ---
|
||||
|
||||
const handleSend = async () => {
|
||||
if ((!inputText.trim() && attachments.length === 0) || isLoading) return;
|
||||
if (!settings.apiKey) {
|
||||
showToast(t('error.no_key', settings.language), 'error');
|
||||
setIsSettingsOpen(true);
|
||||
return;
|
||||
}
|
||||
@@ -279,7 +498,7 @@ export default function App() {
|
||||
if (!activeSessionId) {
|
||||
const newSession: Session = {
|
||||
id: Date.now().toString(),
|
||||
title: isCreativeModule(currentModule) ? inputText.slice(0, 20) : t('action.new_chat', settings.language),
|
||||
title: isCustomViewModule(currentModule) ? inputText.slice(0, 20) : t('action.new_chat', settings.language),
|
||||
module: currentModule,
|
||||
messages: [],
|
||||
createdAt: Date.now(),
|
||||
@@ -318,7 +537,7 @@ export default function App() {
|
||||
role: MessageRole.MODEL,
|
||||
timestamp: Date.now(),
|
||||
text: '',
|
||||
isThinking: currentModule === AppModule.THINKER
|
||||
isThinking: isThinkingMode && !isCustomViewModule(currentModule)
|
||||
}]);
|
||||
|
||||
try {
|
||||
@@ -336,7 +555,7 @@ export default function App() {
|
||||
}]);
|
||||
} else {
|
||||
// Text/Chat Generation
|
||||
if (currentModule === AppModule.THINKER) setLoadingText(t('status.thinking', settings.language));
|
||||
if (isThinkingMode) setLoadingText(t('status.thinking', settings.language));
|
||||
else setLoadingText(t('status.generating', settings.language));
|
||||
|
||||
// Prepare history for API
|
||||
@@ -347,7 +566,16 @@ export default function App() {
|
||||
return { role: m.role, parts };
|
||||
});
|
||||
|
||||
const stream = await geminiRef.current!.generateText(inputText, currentModule, historyParts, attachments);
|
||||
// Pass isThinkingMode to the service
|
||||
const stream = await geminiRef.current!.generateText(
|
||||
inputText,
|
||||
currentModule,
|
||||
historyParts,
|
||||
settings.language,
|
||||
attachments,
|
||||
isThinkingMode,
|
||||
settings.aiResponseLanguage
|
||||
);
|
||||
|
||||
let fullText = '';
|
||||
let sources: any[] = [];
|
||||
@@ -363,7 +591,7 @@ export default function App() {
|
||||
const lastMsg = msgs[msgs.length - 1];
|
||||
if (lastMsg.id === botMsgId) {
|
||||
lastMsg.text = fullText;
|
||||
lastMsg.isThinking = false;
|
||||
lastMsg.isThinking = false; // Turn off thinking indicator once text starts arriving (simplification)
|
||||
}
|
||||
return { ...s, messages: msgs };
|
||||
}
|
||||
@@ -409,7 +637,7 @@ export default function App() {
|
||||
const audio = new Audio(`data:audio/mp3;base64,${audioBase64}`);
|
||||
audio.play();
|
||||
} catch (e) {
|
||||
alert('TTS Error: ' + (e as Error).message);
|
||||
showToast(t('error.tts', settings.language) + ': ' + (e as Error).message, 'error');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -417,18 +645,9 @@ export default function App() {
|
||||
|
||||
const handleCopy = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
showToast(t('success.copy', settings.language), 'success');
|
||||
};
|
||||
|
||||
const handleShare = async () => {
|
||||
const el = document.getElementById('chat-container');
|
||||
if (el) {
|
||||
const canvas = await html2canvas(el);
|
||||
const link = document.createElement('a');
|
||||
link.download = `bitsage-share-${Date.now()}.png`;
|
||||
link.href = canvas.toDataURL();
|
||||
link.click();
|
||||
}
|
||||
};
|
||||
|
||||
// --- Renderers ---
|
||||
|
||||
@@ -495,8 +714,8 @@ export default function App() {
|
||||
);
|
||||
|
||||
const renderHistorySidebar = () => {
|
||||
// Hide history on Home page or Creative Modules
|
||||
if (isHome || isCreativeModule(currentModule)) return null;
|
||||
// Hide history on Home page or Custom View Modules
|
||||
if (isHome || isCustomViewModule(currentModule)) return null;
|
||||
|
||||
const moduleSessions = sessions.filter(s => s.module === currentModule);
|
||||
|
||||
@@ -572,8 +791,8 @@ export default function App() {
|
||||
};
|
||||
|
||||
const renderHome = () => (
|
||||
<div className="flex flex-col items-center min-h-full p-4 animate-fade-in">
|
||||
<div className="max-w-4xl w-full my-auto py-10">
|
||||
<div className="h-full w-full overflow-y-auto flex flex-col items-center p-4 animate-fade-in">
|
||||
<div className="max-w-4xl w-full my-auto py-10 pb-24">
|
||||
<div className="text-center mb-12 animate-slide-up">
|
||||
<div className="bg-blue-100 dark:bg-blue-900/30 p-6 rounded-full inline-block mb-6 shadow-lg shadow-blue-500/20">
|
||||
<Brain size={64} className="text-blue-600 dark:text-blue-400" />
|
||||
@@ -614,9 +833,117 @@ export default function App() {
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderSQLTool = () => {
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-gray-50 dark:bg-slate-900">
|
||||
<div className="bg-white dark:bg-slate-800 border-b dark:border-slate-700 p-6 shrink-0 z-10 shadow-sm transition-all">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-bold text-blue-600 uppercase tracking-wider">{t('module.sql', settings.language)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex flex-wrap items-center gap-2 mb-4 p-2 bg-gray-100 dark:bg-slate-700 rounded-lg">
|
||||
<Button onClick={handleSqlFormat} disabled={isLoading} variant="ghost" className="text-sm h-9">
|
||||
{t('sql.format', settings.language)}
|
||||
</Button>
|
||||
<div className="w-px bg-gray-300 dark:bg-slate-600 mx-1 self-center h-6"></div>
|
||||
<Button onClick={handleSqlReplace} disabled={isLoading} variant="ghost" className="text-sm h-9">
|
||||
{t('sql.replace', settings.language)}
|
||||
</Button>
|
||||
<Button onClick={handleSqlMinify} disabled={isLoading} variant="ghost" className="text-sm h-9">
|
||||
{t('sql.minify', settings.language)}
|
||||
</Button>
|
||||
<div className="w-px bg-gray-300 dark:bg-slate-600 mx-1 self-center h-6"></div>
|
||||
<div className="flex items-center gap-2 pl-2 border-r border-gray-300 dark:border-slate-600 pr-2">
|
||||
<select
|
||||
className="bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-600 rounded text-sm h-8 px-2 outline-none"
|
||||
value={sqlTargetDB} onChange={e => setSqlTargetDB(e.target.value)}
|
||||
>
|
||||
<option value="MySQL">MySQL</option>
|
||||
<option value="PostgreSQL">PostgreSQL</option>
|
||||
<option value="Oracle">Oracle</option>
|
||||
<option value="SQL Server">SQL Server</option>
|
||||
<option value="SQLite">SQLite</option>
|
||||
</select>
|
||||
<Button onClick={handleSqlConvert} disabled={isLoading} variant="primary" className="text-sm h-8 px-3">
|
||||
{t('sql.convert', settings.language)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={() => setIsCustomSqlOpen(!isCustomSqlOpen)}
|
||||
disabled={isLoading}
|
||||
variant={isCustomSqlOpen ? "primary" : "ghost"}
|
||||
className="text-sm h-9 ml-auto"
|
||||
>
|
||||
<Sparkles size={14} /> {t('sql.custom', settings.language)} <ChevronDown size={14} className={`transform transition-transform ${isCustomSqlOpen ? 'rotate-180' : ''}`}/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Custom Prompt Area */}
|
||||
{isCustomSqlOpen && (
|
||||
<div className="mb-4 animate-slide-up flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
className="flex-1 bg-gray-50 dark:bg-slate-900 border border-gray-200 dark:border-slate-700 rounded-lg px-4 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder={t('sql.custom_prompt', settings.language)}
|
||||
value={sqlCustomPrompt}
|
||||
onChange={e => setSqlCustomPrompt(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && handleSqlCustom()}
|
||||
/>
|
||||
<Button onClick={handleSqlCustom} disabled={!sqlCustomPrompt.trim() || isLoading} variant="primary" className="text-sm h-10">
|
||||
{t('sql.run', settings.language)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Editor Area */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 h-[calc(100vh-280px)]">
|
||||
<div className="flex flex-col">
|
||||
<label className="text-xs font-semibold text-gray-500 mb-2 uppercase">{t('sql.input', settings.language)}</label>
|
||||
<textarea
|
||||
className="flex-1 w-full bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-xl p-4 font-mono text-sm resize-none focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
placeholder={t('sql.placeholder', settings.language)}
|
||||
value={sqlInput}
|
||||
onChange={e => setSqlInput(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<label className="text-xs font-semibold text-gray-500 mb-2 uppercase flex justify-between">
|
||||
{t('sql.output', settings.language)}
|
||||
{sqlOutput && (
|
||||
<button onClick={() => handleCopy(sqlOutput)} className="text-blue-500 hover:text-blue-600 flex items-center gap-1">
|
||||
<Copy size={12}/> Copy
|
||||
</button>
|
||||
)}
|
||||
</label>
|
||||
<div className="flex-1 w-full bg-gray-100 dark:bg-slate-900/50 border border-gray-200 dark:border-slate-700 rounded-xl p-4 font-mono text-sm overflow-auto whitespace-pre">
|
||||
{sqlOutput || <span className="text-gray-400 italic">...</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderMessage = (msg: Message) => (
|
||||
<div key={msg.id} className={`flex ${msg.role === MessageRole.USER ? 'justify-end' : 'justify-start'} mb-6 group animate-slide-up`}>
|
||||
<div className={`max-w-[85%] md:max-w-[75%] rounded-2xl p-4 transition-all duration-300 hover:shadow-md ${msg.role === MessageRole.USER ? 'bg-blue-600 text-white' : 'bg-white dark:bg-slate-800 border dark:border-slate-700 shadow-sm'}`}>
|
||||
<div key={msg.id} className={`flex flex-col mb-6 group animate-slide-up ${msg.role === MessageRole.USER ? 'items-end' : 'items-start'}`}>
|
||||
|
||||
{/* Message Metadata */}
|
||||
<div className={`flex items-center gap-2 mb-1 text-xs text-gray-400 ${msg.role === MessageRole.USER ? 'flex-row-reverse' : 'flex-row'}`}>
|
||||
<span className="font-semibold text-gray-500 dark:text-gray-400">
|
||||
{msg.role === MessageRole.USER ? t('role.user', settings.language) : getModelDisplayName(currentModule)}
|
||||
</span>
|
||||
<span className="w-1 h-1 rounded-full bg-gray-300 dark:bg-gray-600"></span>
|
||||
<span>{new Date(msg.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
|
||||
</div>
|
||||
|
||||
<div className={`max-w-[85%] md:max-w-[75%] rounded-2xl p-4 transition-all duration-300 hover:shadow-md ${msg.role === MessageRole.USER ? 'bg-blue-600 text-white rounded-tr-sm' : 'bg-white dark:bg-slate-800 border dark:border-slate-700 shadow-sm rounded-tl-sm'}`}>
|
||||
|
||||
{msg.images && msg.images.map((img, i) => (
|
||||
<img key={i} src={`data:image/jpeg;base64,${img}`} alt="Generated" className="rounded-lg mb-2 max-h-64 object-contain bg-black/5" />
|
||||
@@ -652,7 +979,7 @@ export default function App() {
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{msg.sources.map((src, i) => (
|
||||
<a key={i} href={src.uri} target="_blank" rel="noopener noreferrer" className="flex items-center gap-1 text-xs bg-gray-100 dark:bg-slate-700 px-2 py-1 rounded hover:bg-gray-200 dark:hover:bg-slate-600 transition-colors truncate max-w-[200px]">
|
||||
<Globe size={10} /> {src.title || new URL(src.uri).hostname}
|
||||
<Globe size={10} /> {src.title || getHostname(src.uri)}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
@@ -906,9 +1233,22 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-gray-50 dark:bg-slate-900 overflow-hidden">
|
||||
<div className="flex h-screen bg-gray-50 dark:bg-slate-900 overflow-hidden relative">
|
||||
{renderSidebar()}
|
||||
|
||||
{/* Toast Container */}
|
||||
<div className="fixed top-6 left-1/2 -translate-x-1/2 z-[100] flex flex-col gap-2 w-full max-w-sm px-4 pointer-events-none">
|
||||
{toasts.map(toast => (
|
||||
<div key={toast.id} className={`pointer-events-auto bg-white dark:bg-slate-800 text-gray-800 dark:text-gray-100 px-4 py-3 rounded-lg shadow-xl border border-gray-100 dark:border-slate-700 flex items-center gap-3 animate-slide-up`}>
|
||||
{toast.type === 'success' && <CheckCircle className="text-green-500 shrink-0" size={20} />}
|
||||
{toast.type === 'error' && <AlertCircle className="text-red-500 shrink-0" size={20} />}
|
||||
{toast.type === 'warning' && <AlertTriangle className="text-yellow-500 shrink-0" size={20} />}
|
||||
{toast.type === 'info' && <Info className="text-blue-500 shrink-0" size={20} />}
|
||||
<span className="text-sm font-medium">{toast.message}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Overlay for mobile sidebar */}
|
||||
{isSidebarOpen && <div onClick={() => setIsSidebarOpen(false)} className="fixed inset-0 bg-black/50 z-30 md:hidden backdrop-blur-sm" />}
|
||||
|
||||
@@ -925,19 +1265,23 @@ export default function App() {
|
||||
{isHome ? t('app.name', settings.language) : (
|
||||
<>
|
||||
<span className="text-gray-400 hidden sm:inline">{t(`module.${currentModule}`, settings.language)} /</span>
|
||||
{getCurrentSession()?.title || (isCreativeModule(currentModule) ? t('ui.workbench', settings.language) : t('action.new_chat', settings.language))}
|
||||
{getCurrentSession()?.title || (isCustomViewModule(currentModule) ? t('ui.workbench', settings.language) : t('action.new_chat', settings.language))}
|
||||
</>
|
||||
)}
|
||||
</h2>
|
||||
{isLoading && !isCreativeModule(currentModule) && (
|
||||
{isLoading && !isCustomViewModule(currentModule) && (
|
||||
<span className="text-xs text-blue-500 animate-pulse bg-blue-50 dark:bg-blue-900/20 px-2 py-1 rounded-full whitespace-nowrap">
|
||||
{loadingText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{!isHome && currentSessionId && !isCreativeModule(currentModule) && <button onClick={handleShare} className="p-2 text-gray-500 hover:bg-gray-100 dark:hover:bg-slate-800 rounded-lg"><Share2 size={20}/></button>}
|
||||
{!isHome && !isHistoryOpen && !isCreativeModule(currentModule) && (
|
||||
{!isHome && currentSessionId && !isCustomViewModule(currentModule) && (
|
||||
<button onClick={() => setIsShareModalOpen(true)} className="p-2 text-gray-500 hover:bg-gray-100 dark:hover:bg-slate-800 rounded-lg">
|
||||
<Share2 size={20}/>
|
||||
</button>
|
||||
)}
|
||||
{!isHome && !isHistoryOpen && !isCustomViewModule(currentModule) && (
|
||||
<button onClick={() => setIsHistoryOpen(true)} className="p-2 text-gray-500 hover:bg-gray-100 dark:hover:bg-slate-800 rounded-lg">
|
||||
<PanelRight size={20} />
|
||||
</button>
|
||||
@@ -948,21 +1292,32 @@ export default function App() {
|
||||
{/* Viewport - Main Scrolling Area */}
|
||||
<main className="flex-1 overflow-hidden relative bg-gray-50 dark:bg-slate-900" id="chat-container">
|
||||
{isHome ? renderHome() : (
|
||||
isCreativeModule(currentModule) ? renderCreativeStudio() : (
|
||||
currentModule === AppModule.SQL ? renderSQLTool() :
|
||||
isCustomViewModule(currentModule) ? renderCreativeStudio() : (
|
||||
<div className="h-full flex flex-col overflow-y-auto scroll-smooth">
|
||||
{!currentSessionId ? (
|
||||
<div className="flex-1 flex items-center justify-center p-8 text-center text-gray-400">
|
||||
<div className="animate-bounce-in">
|
||||
<div className="w-16 h-16 bg-gray-100 dark:bg-slate-800 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
{AppModule.TUTOR === currentModule && <MessageSquare size={32} />}
|
||||
{AppModule.THINKER === currentModule && <Brain size={32} />}
|
||||
{AppModule.MATH === currentModule && <Sigma size={32} />}
|
||||
{AppModule.THEORY === currentModule && <Binary size={32} />}
|
||||
{AppModule.PRINCIPLES === currentModule && <Cpu size={32} />}
|
||||
{AppModule.SOFT_ENG === currentModule && <Code size={32} />}
|
||||
{AppModule.GRAPHICS === currentModule && <Box size={32} />}
|
||||
{AppModule.NETWORK === currentModule && <Network size={32} />}
|
||||
{AppModule.AI_LAB === currentModule && <Bot size={32} />}
|
||||
{AppModule.RESEARCH === currentModule && <Search size={32} />}
|
||||
</div>
|
||||
<p>{t('prompt.placeholder', settings.language)}</p>
|
||||
<h3 className="text-xl font-semibold mb-2 text-gray-800 dark:text-gray-200">
|
||||
{t(`hello.${currentModule}`, settings.language)}
|
||||
</h3>
|
||||
<p className="text-sm max-w-sm mx-auto">
|
||||
{t(`desc.${currentModule}`, settings.language)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 p-4 md:p-8 max-w-4xl mx-auto w-full">
|
||||
<div className="flex-1 p-4 md:p-8 max-w-4xl mx-auto w-full" id="chat-content">
|
||||
{getCurrentSession()?.messages.map(renderMessage)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
@@ -973,7 +1328,7 @@ export default function App() {
|
||||
</main>
|
||||
|
||||
{/* Input Area (Only for Chat Modules) */}
|
||||
{!isHome && !isCreativeModule(currentModule) && (
|
||||
{!isHome && !isCustomViewModule(currentModule) && (
|
||||
<div className="bg-white dark:bg-slate-900 border-t dark:border-slate-700 p-4 shrink-0 z-20">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
|
||||
@@ -996,6 +1351,29 @@ export default function App() {
|
||||
<Plus size={20} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Deep Thinking Toggle */}
|
||||
<button
|
||||
onClick={() => setIsThinkingMode(!isThinkingMode)}
|
||||
className={`p-2 rounded-lg transition-colors pb-3 ${isThinkingMode ? 'text-blue-600 bg-blue-100 dark:bg-blue-900/30' : 'text-gray-400 hover:bg-gray-200 dark:hover:bg-slate-700'}`}
|
||||
title={t('action.toggle_think', settings.language)}
|
||||
>
|
||||
<Brain size={20} />
|
||||
</button>
|
||||
|
||||
{/* AI Language Response Mode Selector */}
|
||||
<div className="relative group flex items-center pb-3">
|
||||
<Languages size={18} className="text-gray-400 absolute left-2 pointer-events-none" />
|
||||
<select
|
||||
value={settings.aiResponseLanguage}
|
||||
onChange={(e) => setSettings({ ...settings, aiResponseLanguage: e.target.value as any })}
|
||||
className="bg-transparent text-xs font-medium text-gray-500 hover:text-blue-600 outline-none cursor-pointer pl-8 appearance-none w-[110px]"
|
||||
title={t('action.lang_mode', settings.language)}
|
||||
>
|
||||
<option value="system">{t('action.lang_system', settings.language)}</option>
|
||||
<option value="input">{t('action.lang_input', settings.language)}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
value={inputText}
|
||||
@@ -1006,7 +1384,7 @@ export default function App() {
|
||||
handleSend();
|
||||
}
|
||||
}}
|
||||
placeholder={t('prompt.placeholder', settings.language)}
|
||||
placeholder={t(`placeholder.${currentModule}`, settings.language)}
|
||||
className="flex-1 bg-transparent border-none outline-none resize-none max-h-32 py-3 text-sm"
|
||||
rows={1}
|
||||
/>
|
||||
@@ -1028,6 +1406,24 @@ export default function App() {
|
||||
{renderHistorySidebar()}
|
||||
</div>
|
||||
|
||||
{/* Share Modal */}
|
||||
<Modal isOpen={isShareModalOpen} onClose={() => setIsShareModalOpen(false)} title={t('share.title', settings.language)}>
|
||||
<div className="space-y-4">
|
||||
<Button variant="secondary" onClick={handleCopySession} className="w-full justify-start h-12">
|
||||
<Copy size={20} className="text-gray-500" />
|
||||
<span className="flex-1 text-left">{t('share.copy', settings.language)}</span>
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleDownloadText} className="w-full justify-start h-12">
|
||||
<FileText size={20} className="text-gray-500" />
|
||||
<span className="flex-1 text-left">{t('share.txt', settings.language)}</span>
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleDownloadImage} className="w-full justify-start h-12">
|
||||
<ImageIcon size={20} className="text-gray-500" />
|
||||
<span className="flex-1 text-left">{t('share.img', settings.language)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* Settings Modal */}
|
||||
<Modal isOpen={isSettingsOpen} onClose={() => setIsSettingsOpen(false)} title={t('settings.title', settings.language)}>
|
||||
<div className="space-y-6">
|
||||
@@ -1037,6 +1433,9 @@ export default function App() {
|
||||
type="password"
|
||||
value={settings.apiKey}
|
||||
onChange={(e) => setSettings({...settings, apiKey: e.target.value})}
|
||||
onBlur={() => {
|
||||
if (settings.apiKey) showToast(t('success.apikey_updated', settings.language), 'success');
|
||||
}}
|
||||
className="w-full px-3 py-2 border rounded-lg bg-gray-50 dark:bg-slate-800 dark:border-slate-700 outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Expires monthly..."
|
||||
/>
|
||||
|
||||
127
README.md
127
README.md
@@ -1,20 +1,121 @@
|
||||
<div align="center">
|
||||
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||
</div>
|
||||
# BitSage
|
||||
|
||||
# Run and deploy your AI Studio app
|
||||
<p align="center">
|
||||
<img src="public/icon.svg" alt="BitSage Logo" width="100" />
|
||||
</p>
|
||||
|
||||
This contains everything you need to run your app locally.
|
||||
BitSage is an intelligent AI companion designed specifically for learning Computer Science and Technology. It leverages advanced Gemini models to provide expert Q&A, deep reasoning, academic research, and multimedia generation capabilities.
|
||||
|
||||
View your app in AI Studio: https://ai.studio/apps/drive/1wH16LSTzg8m7RgPeCzYP99HiinOHmyij
|
||||
---
|
||||
|
||||
## Run Locally
|
||||
### 🌐 Language / 语言 / 言語
|
||||
- [English](#english)
|
||||
- [简体中文](#简体中文)
|
||||
- [繁體中文](#繁體中文)
|
||||
- [日本語](#日本語)
|
||||
|
||||
**Prerequisites:** Node.js
|
||||
---
|
||||
|
||||
## <a id="english"></a>English
|
||||
|
||||
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`
|
||||
### Features
|
||||
|
||||
1. **Specialized CS Domains**: Dedicated modules for Mathematics, Computer Principles, Software Engineering, Computer Graphics, Computer Networks, and Artificial Intelligence.
|
||||
2. **Deep Thinking Mode**: A toggleable "Thinking" mode powered by Gemini's reasoning capabilities for complex problem solving.
|
||||
3. **Academic Research**: Web-grounded search for finding up-to-date papers and technical documentation.
|
||||
4. **Creative Studio**:
|
||||
* **Vision Lab**: Generate diagrams or analyze code screenshots.
|
||||
* **Video Studio**: Create concept videos using Veo.
|
||||
* **Audio Lab**: Text-to-Speech and audio transcription.
|
||||
5. **PWA Support**: Installable on PC and Mobile for a native app experience.
|
||||
6. **Privacy Focused**: All API keys and chat history are stored locally in your browser.
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. Open the application.
|
||||
2. Click "Get Started" or the Settings icon.
|
||||
3. Enter your Google Gemini API Key.
|
||||
4. Select a module from the sidebar and start learning!
|
||||
|
||||
---
|
||||
|
||||
## <a id="简体中文"></a>简体中文
|
||||
|
||||
### 功能特性
|
||||
|
||||
1. **计算机科学细分领域**:包含数学基础、计算机组成原理、软件工程、图形学、网络和人工智能等专属模块。
|
||||
2. **深度思考模式**:在对话框中可一键开启“深度思考”,利用 Gemini 的推理能力解决复杂算法或架构问题。
|
||||
3. **学术搜索**:基于网络搜索的问答,适合查找最新的技术文档和学术论文。
|
||||
4. **创意工作室**:
|
||||
* **视觉实验室**:生成技术架构图或分析代码截图。
|
||||
* **视频工作室**:使用 Veo 模型生成概念演示视频。
|
||||
* **音频实验室**:文字转语音及音频转录。
|
||||
5. **PWA 支持**:支持在 PC 和手机端安装,提供原生应用般的体验。
|
||||
6. **隐私保护**:所有的 API Key 和聊天记录仅保存在您的浏览器本地。
|
||||
|
||||
### 快速开始
|
||||
|
||||
1. 打开应用。
|
||||
2. 点击“开始体验”或设置图标。
|
||||
3. 输入您的 Google Gemini API Key。
|
||||
4. 在左侧栏选择一个模块,开始您的学习之旅!
|
||||
|
||||
---
|
||||
|
||||
## <a id="繁體中文"></a>繁體中文
|
||||
|
||||
### 功能特性
|
||||
|
||||
1. **計算機科學細分領域**:包含數學基礎、計算機組成原理、軟體工程、圖形學、網路和人工智慧等專屬模組。
|
||||
2. **深度思考模式**:在對話框中可一鍵開啟「深度思考」,利用 Gemini 的推理能力解決複雜演算法或架構問題。
|
||||
3. **學術搜尋**:基於網路搜尋的問答,適合尋找最新的技術文件和學術論文。
|
||||
4. **創意工作室**:
|
||||
* **視覺實驗室**:生成技術架構圖或分析程式碼截圖。
|
||||
* **影片工作室**:使用 Veo 模型生成概念演示影片。
|
||||
* **音訊實驗室**:文字轉語音及音訊轉錄。
|
||||
5. **PWA 支援**:支援在 PC 和手機端安裝,提供原生應用般的體驗。
|
||||
6. **隱私保護**:所有的 API Key 和聊天記錄僅保存在您的瀏覽器本地。
|
||||
|
||||
### 快速開始
|
||||
|
||||
1. 打開應用。
|
||||
2. 點擊「開始體驗」或設定圖示。
|
||||
3. 輸入您的 Google Gemini API Key。
|
||||
4. 在左側欄選擇一個模組,開始您的學習之旅!
|
||||
|
||||
---
|
||||
|
||||
## <a id="日本語"></a>日本語
|
||||
|
||||
### 機能
|
||||
|
||||
1. **CS専門分野**: 数学基礎、計算機原理、ソフトウェア工学、CG、ネットワーク、AIラボなど、分野ごとの専用モジュール。
|
||||
2. **深い思考モード**: 会話画面で「深い思考」をオンにすると、Geminiの推論能力を使って複雑な問題を解決できます。
|
||||
3. **学術検索**: 最新の技術文書や論文を見つけるためのWeb検索機能。
|
||||
4. **クリエイティブスタジオ**:
|
||||
* **ビジョンラボ**: 図の生成やコードのスクリーンショット分析。
|
||||
* **ビデオスタジオ**: Veoモデルを使用したコンセプトビデオの生成。
|
||||
* **オーディオラボ**: テキスト読み上げと音声文字起こし。
|
||||
5. **PWA対応**: PCやモバイルにインストールして、ネイティブアプリのように使用できます。
|
||||
6. **プライバシー重視**: APIキーとチャット履歴はブラウザにローカル保存されます。
|
||||
|
||||
### 始め方
|
||||
|
||||
1. アプリを開きます。
|
||||
2. 「始める」または設定アイコンをクリックします。
|
||||
3. Google Gemini APIキーを入力します。
|
||||
4. サイドバーからモジュールを選択して、学習を始めましょう!
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack / 技术栈
|
||||
|
||||
* React 18 + TypeScript
|
||||
* Vite
|
||||
* Tailwind CSS
|
||||
* Google GenAI SDK (Gemini 2.5/3.0 Models)
|
||||
* Lucide React Icons
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
15
index.html
15
index.html
@@ -49,10 +49,10 @@
|
||||
}
|
||||
</script>
|
||||
<!-- Direct Favicon Link -->
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232563eb' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z'/%3E%3Cpath d='M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z'/%3E%3C/svg%3E" />
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232563eb' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z'/%3E%3Cpath d='M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z'/%3E%3C/svg%3E" />
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="data:application/json;base64,ewogICJuYW1lIjogIkJpdFNhZ2UgLSBDUyBMZWFybmluZyBDb21wYW5pb24iLAogICJzaG9ydF9uYW1lIjogIkJpdFNhZ2UiLAogICJzdGFydF91cmwiOiAiLyIsCiAgImRpc3BsYXkiOiAiYnJvd3NlciIsCiAgImJhY2tncm91bmRfY29xvciI6ICIjZmZmZmZmIiwKICAidGhlbWVfY29xvciI6ICIjMjU2M2ViIiwKICAiaWNvbnMiOiBbCiAgICB7CiAgICAgICJzcmMiOiAiZGF0YTppbWFnZS9zdmcreG1sLCUzQ3N2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCcgZmlsbD0nbm9uZScgc3Ryb2tlPSclMjMyNTYzZWInIHN0cm9rZS13aWR0aD0nMicgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJ%2BJTNDcGF0aCBkPSdNOS41IDJBMi41IDIuNSAwIDAgMSAxMiA0LjV2MTVhMi41IDIuNSAwIDAgMS00Ljk2LjQ0IDIuNSAyLjUgMCAwIDEtMi45Ni0zLjA4IDMgMyAwIDAgMS0uMzQtNS41OCAyLjUgMi41IDAgMCAxIDEuMzItNC4yNCAyLjUgMi41IDAgMCAxIDEuMzItNC4yNCAyLjUgMi41IDAgMCAxIDEuOTgtM0EyLjUgMi41IDAgMCAxIDkuNSAyWicvJTNFJTNDcGF0aCBkPSdNMTQuNSAyQTIuNSAyLjUgMCAwIDAgMTIgNC41djE1YTIuNSAyLjUgMCAwIDAgNC45Ni40NCAyLjUgMi41IDAgMCAwIDIuOTYtMy4wOCAzIDMgMCAwIDAgLjM0LTUuNTggMi41IDIuNSAwIDAgMC0xLjMyLTQuMjQgMi41IDIuNSAwIDAgMC0xLjk4LTNBMi41IDIuNSAwIDAgMCAxNC41IDJaJy8lM0UlM0Mvc3ZnJTNFLg0KIiwKICAgICAgInR5cGUiOiAiaW1hZ2Uvc3ZnK3htbCIsCiAgICAgICJzaXplcyI6ICIxOTJ4MTkyIDUxMng1MTIiCiAgICB9CiAgXQp9" />
|
||||
<!-- PWA Manifest (display: standalone is critical for installability) -->
|
||||
<link rel="manifest" href='data:application/json;charset=utf-8,{"name":"BitSage - CS Learning Companion","short_name":"BitSage","start_url":"/","display":"standalone","background_color":"#ffffff","theme_color":"#2563eb","icons":[{"src":"data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%232563eb%27 stroke-width=%272%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z%27/%3E%3Cpath d=%27M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z%27/%3E%3C/svg%3E","type":"image/svg+xml","sizes":"192x192 512x512"}]}' />
|
||||
|
||||
<style>
|
||||
/* Custom scrollbar for webkit */
|
||||
@@ -118,5 +118,14 @@
|
||||
<body class="bg-gray-50 dark:bg-slate-900 text-gray-900 dark:text-gray-100 h-[100dvh] overflow-hidden">
|
||||
<div id="root" class="h-full"></div>
|
||||
<script type="module" src="/index.tsx"></script>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('./sw.js').catch(err => {
|
||||
console.error('SW registration failed:', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
releases/HTY1024-APP-CKG-0.2.0_20251225.zip
Normal file
BIN
releases/HTY1024-APP-CKG-0.2.0_20251225.zip
Normal file
Binary file not shown.
BIN
releases/HTY1024-APP-CKG-0.3.0_20251226.zip
Normal file
BIN
releases/HTY1024-APP-CKG-0.3.0_20251226.zip
Normal file
Binary file not shown.
@@ -34,26 +34,16 @@ export class GeminiService {
|
||||
prompt: string,
|
||||
module: AppModule,
|
||||
history: {role: string, parts: any[]}[],
|
||||
media?: { data: string, mimeType: string }[]
|
||||
language: string,
|
||||
media?: { data: string, mimeType: string }[],
|
||||
enableThinking: boolean = false,
|
||||
responseLangMode: 'system' | 'input' = 'system'
|
||||
) {
|
||||
const ai = this.getClient();
|
||||
let model = MODEL_CHAT_PRO;
|
||||
let config: any = {};
|
||||
|
||||
switch (module) {
|
||||
case AppModule.TUTOR:
|
||||
// Use fast model for simple queries if possible, but user wants options.
|
||||
// We default to Pro for quality in Tutor, but could swap.
|
||||
// Requirement says: "Use Pro for complex tasks and Flash or Flash-Lite for tasks that should happen fast."
|
||||
// We'll stick to Pro for general "Tutor" advice as it implies teaching.
|
||||
model = MODEL_CHAT_PRO;
|
||||
break;
|
||||
case AppModule.THINKER:
|
||||
model = MODEL_CHAT_PRO;
|
||||
config.thinkingConfig = { thinkingBudget: 32768 };
|
||||
// config.maxOutputTokens should NOT be set when using max thinking budget if not careful,
|
||||
// but recommendation says: "Avoid setting this if not required".
|
||||
break;
|
||||
case AppModule.RESEARCH:
|
||||
model = MODEL_RESEARCH;
|
||||
config.tools = [{ googleSearch: {} }];
|
||||
@@ -67,18 +57,67 @@ export class GeminiService {
|
||||
case AppModule.AUDIO:
|
||||
model = MODEL_AUDIO_TRANS; // For transcription/analysis
|
||||
break;
|
||||
default:
|
||||
// Math, Principles, SoftEng, Graphics, Network, AI_LAB
|
||||
// Use Pro for these complex domains
|
||||
model = MODEL_CHAT_PRO;
|
||||
if (enableThinking) {
|
||||
config.thinkingConfig = { thinkingBudget: 32768 };
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Build contents
|
||||
// Chat history + new prompt
|
||||
// Note: @google/genai chat history format differs slightly from simple array.
|
||||
// For simplicity in this single-file service, we'll use `generateContent` with a constructed history
|
||||
// OR just use `chats.create`. `chats.create` is better for history.
|
||||
// Construct System Instruction based on module and language
|
||||
const langMap: Record<string, string> = {
|
||||
'en': 'English',
|
||||
'zh-CN': 'Simplified Chinese',
|
||||
'zh-TW': 'Traditional Chinese',
|
||||
'ja': 'Japanese'
|
||||
};
|
||||
|
||||
let languageInstruction = '';
|
||||
if (responseLangMode === 'input') {
|
||||
languageInstruction = `- Detect the language of the user's input and reply exclusively in that language.`;
|
||||
} else {
|
||||
const targetLang = langMap[language] || 'English';
|
||||
languageInstruction = `- Please provide your response in ${targetLang}.
|
||||
- However, if the user explicitly asks a question in a different language, you should adapt and reply in the language of the question to ensure effective communication.`;
|
||||
}
|
||||
|
||||
const contextMap: Record<string, string> = {
|
||||
[AppModule.MATH]: 'Discrete Mathematics, Calculus, Linear Algebra, and Logic for Computer Science.',
|
||||
[AppModule.THEORY]: 'Theory of Computation, Automata, Complexity Theory, and Computability.',
|
||||
[AppModule.PRINCIPLES]: 'Computer Architecture, Organization, Digital Logic, and Assembly.',
|
||||
[AppModule.SOFT_ENG]: 'Software Engineering, Design Patterns, Architecture, and DevOps.',
|
||||
[AppModule.GRAPHICS]: 'Computer Graphics, Rendering, WebGL, and Linear Algebra for Graphics.',
|
||||
[AppModule.NETWORK]: 'Computer Networks, Protocols (TCP/IP), Security, and Distributed Systems.',
|
||||
[AppModule.AI_LAB]: 'Artificial Intelligence, Machine Learning, Deep Learning, and Neural Networks.',
|
||||
[AppModule.RESEARCH]: 'Academic Research, Paper Search, and Citations.',
|
||||
[AppModule.VISION]: 'Computer Vision, Image Analysis, and Generation.',
|
||||
[AppModule.STUDIO]: 'Video Generation and Multimedia Processing.',
|
||||
[AppModule.AUDIO]: 'Audio Processing, Speech Synthesis, and Transcription.',
|
||||
[AppModule.SQL]: 'SQL Database Administration and Query Optimization.'
|
||||
};
|
||||
|
||||
const domain = contextMap[module] || 'Computer Science';
|
||||
|
||||
const systemInstruction = `You are BitSage, an expert tutor and companion specializing in ${domain}.
|
||||
|
||||
Primary Goal: Help the user learn, understand, and explore concepts in this domain.
|
||||
|
||||
Language Preference:
|
||||
${languageInstruction}
|
||||
|
||||
Style:
|
||||
- Be precise, educational, and helpful.
|
||||
- Use code blocks for code snippets.
|
||||
- Use Markdown for formatting.
|
||||
${enableThinking ? '- Thinking process is enabled. Use it to break down complex problems.' : ''}
|
||||
`;
|
||||
|
||||
config.systemInstruction = systemInstruction;
|
||||
|
||||
// Convert generic history to SDK format
|
||||
// The SDK `sendMessage` handles the current turn.
|
||||
// We need to initialize history first.
|
||||
|
||||
const sdkHistory = history.map(h => ({
|
||||
role: h.role,
|
||||
parts: h.parts
|
||||
@@ -144,12 +183,6 @@ export class GeminiService {
|
||||
}
|
||||
}
|
||||
|
||||
// Creating a NEW instance for Veo calls to ensure latest key if using the selection dialog flow?
|
||||
// The prompt says "Create a new GoogleGenAI instance right before making an API call...".
|
||||
// Since we are using our own stored key primarily, we stick to `this.ai`.
|
||||
// If the user used the dialog, that key isn't automatically in our `this.apiKey`.
|
||||
// We will assume `this.apiKey` (user entered) is the paid key required.
|
||||
|
||||
let operation = await ai.models.generateVideos({
|
||||
model: MODEL_VIDEO,
|
||||
prompt: prompt,
|
||||
@@ -207,4 +240,29 @@ export class GeminiService {
|
||||
if (!base64Audio) throw new Error("No audio generated");
|
||||
return base64Audio;
|
||||
}
|
||||
|
||||
async toolsSql(text: string, type: 'format' | 'convert' | 'custom', target?: string, instruction?: string) {
|
||||
const ai = this.getClient();
|
||||
let prompt = "";
|
||||
|
||||
if (type === 'custom') {
|
||||
if (!text.trim()) {
|
||||
// Generation mode
|
||||
prompt = `You are a SQL expert. The user wants you to generate SQL code based on this request: "${instruction}".\n\nReturn ONLY the generated SQL code. Do not wrap in markdown backticks unless asked for explanation.`;
|
||||
} else {
|
||||
// Manipulation mode
|
||||
prompt = `You are a SQL expert. The user wants you to perform the following action on the SQL code provided: "${instruction}".\n\nReturn ONLY the processed SQL code (or the answer if it's an analysis). Do not wrap in markdown backticks unless asked for explanation.\n\nSQL Code:\n${text}`;
|
||||
}
|
||||
} else if (type === 'format') {
|
||||
prompt = `You are a SQL formatter. Format the following SQL code to be readable, with proper indentation and uppercased keywords. Return ONLY the formatted SQL code, no markdown backticks.\n\n${text}`;
|
||||
} else if (type === 'convert') {
|
||||
prompt = `You are a SQL converter. Convert the following SQL code to ${target} dialect. Return ONLY the converted SQL code, no markdown backticks.\n\n${text}`;
|
||||
}
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model: MODEL_CHAT_PRO,
|
||||
contents: [{ parts: [{ text: prompt }] }]
|
||||
});
|
||||
return response.text?.trim() || "";
|
||||
}
|
||||
}
|
||||
329
services/i18n.ts
329
services/i18n.ts
@@ -4,22 +4,57 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'en': {
|
||||
'app.name': 'BitSage',
|
||||
'menu.home': 'Home',
|
||||
'group.learning': 'Learning & Research',
|
||||
|
||||
'group.cs': 'Computer Science Domains',
|
||||
'group.tools': 'Research Tools',
|
||||
'group.creation': 'Creative Studio',
|
||||
'module.tutor': 'CS Tutor',
|
||||
'module.thinker': 'Deep Thinker',
|
||||
'module.research': 'Research',
|
||||
|
||||
'module.math': 'Mathematics',
|
||||
'module.theory': 'Theory of Computation',
|
||||
'module.principles': 'Comp. Architecture',
|
||||
'module.soft_eng': 'Software Eng.',
|
||||
'module.graphics': 'Comp. Graphics',
|
||||
'module.network': 'Comp. Network',
|
||||
'module.ai_lab': 'AI Laboratory',
|
||||
'module.research': 'Academic Search',
|
||||
'module.sql': 'SQL Toolbox',
|
||||
'module.vision': 'Vision Lab',
|
||||
'module.studio': 'Video Studio',
|
||||
'module.audio': 'Audio Lab',
|
||||
|
||||
'desc.tutor': 'Expert Q&A and coding help',
|
||||
'desc.thinker': 'Deep reasoning for complex problems',
|
||||
'desc.math': 'Discrete math, calculus & logic',
|
||||
'desc.theory': 'Automata, computability & complexity',
|
||||
'desc.principles': 'ISA, pipelining & memory hierarchy',
|
||||
'desc.soft_eng': 'Design patterns & DevOps',
|
||||
'desc.graphics': 'Rendering, OpenGL & WebGL',
|
||||
'desc.network': 'Protocols, security & distributed systems',
|
||||
'desc.ai_lab': 'ML, DL & Neural Networks',
|
||||
'desc.research': 'Web-grounded academic research',
|
||||
'desc.sql': 'Format, convert & manipulate SQL',
|
||||
'desc.vision': 'Image analysis and generation',
|
||||
'desc.studio': 'AI video generation studio',
|
||||
'desc.audio': 'Speech-to-text and Text-to-speech',
|
||||
|
||||
// Module specific greetings
|
||||
'hello.math': 'Mathematics Workspace',
|
||||
'hello.theory': 'Theoretical Computer Science',
|
||||
'hello.principles': 'Computer Architecture',
|
||||
'hello.soft_eng': 'Software Engineering',
|
||||
'hello.graphics': 'Graphics & Rendering',
|
||||
'hello.network': 'Network Engineering',
|
||||
'hello.ai_lab': 'AI & Machine Learning',
|
||||
'hello.research': 'Academic Research',
|
||||
|
||||
// Module specific placeholders
|
||||
'placeholder.math': 'Ask about discrete math, linear algebra, or proofs...',
|
||||
'placeholder.theory': 'Ask about Turing machines, P vs NP, or automata...',
|
||||
'placeholder.principles': 'Ask about RISC-V, cache coherence, or digital logic...',
|
||||
'placeholder.soft_eng': 'Ask about design patterns, agile, or system architecture...',
|
||||
'placeholder.graphics': 'Ask about ray tracing, shaders, or WebGL...',
|
||||
'placeholder.network': 'Ask about TCP/IP, OSI model, or network security...',
|
||||
'placeholder.ai_lab': 'Ask about transformers, backpropagation, or PyTorch...',
|
||||
'placeholder.research': 'Search for papers, technical docs, or citations...',
|
||||
|
||||
'welcome.title': 'Welcome to BitSage',
|
||||
'welcome.subtitle': 'Your AI companion for Computer Science & Technology.',
|
||||
'welcome.setup': 'Please enter your Gemini API Key to get started.',
|
||||
@@ -42,11 +77,15 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'action.generate': 'Generate',
|
||||
'action.new_chat': 'New Chat',
|
||||
'action.install': 'Install App',
|
||||
'action.toggle_think': 'Deep Thinking Mode',
|
||||
'action.lang_mode': 'AI Language',
|
||||
'action.lang_system': 'System Lang',
|
||||
'action.lang_input': 'Follow Input',
|
||||
|
||||
'history.title': 'History',
|
||||
'history.empty': 'No history for this module.',
|
||||
|
||||
'prompt.placeholder': 'Ask me anything about CS...',
|
||||
'prompt.placeholder': 'Ask me anything about this domain...',
|
||||
'status.thinking': 'Thinking deeply...',
|
||||
'status.generating': 'Generating...',
|
||||
'status.recording': 'Recording...',
|
||||
@@ -71,7 +110,6 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'audio.prompt': 'Enter text to generate speech...',
|
||||
'btn.start': 'Get Started',
|
||||
|
||||
// New Creative Guide Strings
|
||||
'guide.vision.title': 'Vision Lab',
|
||||
'guide.vision.desc': 'Generate high-quality images from text or analyze uploaded images for code and diagrams.',
|
||||
'guide.vision.tip1': 'Describe the scene, style, and lighting in detail.',
|
||||
@@ -90,26 +128,91 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'ui.workbench': 'Workbench',
|
||||
'ui.gallery': 'Results',
|
||||
'ui.config': 'Configuration',
|
||||
|
||||
// Share & Metadata
|
||||
'share.title': 'Share Session',
|
||||
'share.copy': 'Copy Full Chat',
|
||||
'share.txt': 'Download Text',
|
||||
'share.img': 'Download Image',
|
||||
'success.copy': 'Copied to clipboard!',
|
||||
'role.user': 'You',
|
||||
'role.model': 'Gemini',
|
||||
'role.veo': 'Veo',
|
||||
|
||||
// SQL Tool
|
||||
'sql.format': 'Format SQL',
|
||||
'sql.convert': 'Convert Dialect',
|
||||
'sql.replace': 'Anonymize AS',
|
||||
'sql.minify': 'Minify (Single Line)',
|
||||
'sql.input': 'Input SQL',
|
||||
'sql.output': 'Output SQL',
|
||||
'sql.target_db': 'Target Database',
|
||||
'sql.placeholder': 'Paste your SQL here...',
|
||||
'sql.processing': 'Processing...',
|
||||
'sql.custom': 'Other / AI Assist',
|
||||
'sql.custom_prompt': 'Describe operation or generation needed...',
|
||||
'sql.run': 'Run',
|
||||
|
||||
// New Toasts
|
||||
'warning.no_sql': 'Please enter SQL code first.',
|
||||
'success.apikey_updated': 'API Key configuration updated.',
|
||||
'success.data_imported': 'Data imported successfully.',
|
||||
'error.invalid_file': 'Invalid file format.',
|
||||
'error.screenshot': 'Failed to generate screenshot.',
|
||||
'error.tts': 'TTS Generation failed.',
|
||||
},
|
||||
'zh-CN': {
|
||||
'app.name': '比特智者',
|
||||
'menu.home': '首页',
|
||||
'group.learning': '学习与研究',
|
||||
|
||||
'group.cs': '计算机科学领域',
|
||||
'group.tools': '研究工具',
|
||||
'group.creation': '创意工作室',
|
||||
'module.tutor': 'CS 导师',
|
||||
'module.thinker': '深度思考',
|
||||
|
||||
'module.math': '数学基础',
|
||||
'module.theory': '计算理论',
|
||||
'module.principles': '计算机体系结构',
|
||||
'module.soft_eng': '软件工程',
|
||||
'module.graphics': '计算机图形学',
|
||||
'module.network': '计算机网络',
|
||||
'module.ai_lab': '人工智能',
|
||||
'module.research': '学术搜索',
|
||||
'module.sql': 'SQL 工具箱',
|
||||
'module.vision': '视觉实验室',
|
||||
'module.studio': '视频工作室',
|
||||
'module.audio': '音频实验室',
|
||||
|
||||
'desc.tutor': '专家级问答与代码辅助',
|
||||
'desc.thinker': '针对复杂问题的深度推理',
|
||||
'desc.math': '离散数学、微积分与逻辑',
|
||||
'desc.theory': '自动机、可计算性与复杂性',
|
||||
'desc.principles': '指令集、流水线与存储层次',
|
||||
'desc.soft_eng': '设计模式与 DevOps',
|
||||
'desc.graphics': '渲染、OpenGL 与 WebGL',
|
||||
'desc.network': '协议、安全与分布式系统',
|
||||
'desc.ai_lab': '机器学习、深度学习与神经网络',
|
||||
'desc.research': '基于网络的学术研究',
|
||||
'desc.sql': 'SQL 格式化、转换与处理',
|
||||
'desc.vision': '图像分析与生成',
|
||||
'desc.studio': 'AI 视频生成工作室',
|
||||
'desc.audio': '语音转文字与文字转语音',
|
||||
|
||||
'hello.math': 'CS 数学基础',
|
||||
'hello.theory': '计算机理论基础',
|
||||
'hello.principles': '计算机体系结构',
|
||||
'hello.soft_eng': '软件工程与架构',
|
||||
'hello.graphics': '图形学与渲染',
|
||||
'hello.network': '网络工程与安全',
|
||||
'hello.ai_lab': 'AI 与深度学习',
|
||||
'hello.research': '学术研究助手',
|
||||
|
||||
'placeholder.math': '询问关于离散数学、线性代数或证明的问题...',
|
||||
'placeholder.theory': '询问关于图灵机、P vs NP 或有限自动机...',
|
||||
'placeholder.principles': '询问关于RISC-V、缓存一致性或数字逻辑...',
|
||||
'placeholder.soft_eng': '询问关于设计模式、敏捷开发或系统架构...',
|
||||
'placeholder.graphics': '询问关于光线追踪、着色器或WebGL...',
|
||||
'placeholder.network': '询问关于TCP/IP、OSI模型或网络安全...',
|
||||
'placeholder.ai_lab': '询问关于Transformer、反向传播或PyTorch...',
|
||||
'placeholder.research': '搜索论文、技术文档或引用...',
|
||||
|
||||
'welcome.title': '欢迎使用比特智者',
|
||||
'welcome.subtitle': '您的计算机科学与技术学习 AI 助手。',
|
||||
'welcome.setup': '请输入您的 Gemini API Key 以开始使用。',
|
||||
@@ -132,11 +235,15 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'action.generate': '生成',
|
||||
'action.new_chat': '新会话',
|
||||
'action.install': '安装应用',
|
||||
'action.toggle_think': '深度思考模式',
|
||||
'action.lang_mode': 'AI 语言',
|
||||
'action.lang_system': '跟随系统',
|
||||
'action.lang_input': '跟随输入',
|
||||
|
||||
'history.title': '历史记录',
|
||||
'history.empty': '暂无该模块的历史记录',
|
||||
|
||||
'prompt.placeholder': '问我任何关于计算机科学的问题...',
|
||||
'prompt.placeholder': '在此领域提问...',
|
||||
'status.thinking': '深度思考中...',
|
||||
'status.generating': '生成中...',
|
||||
'status.recording': '录音中...',
|
||||
@@ -179,26 +286,90 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'ui.workbench': '工作台',
|
||||
'ui.gallery': '生成结果',
|
||||
'ui.config': '参数配置',
|
||||
|
||||
// Share & Metadata
|
||||
'share.title': '分享会话',
|
||||
'share.copy': '复制完整对话',
|
||||
'share.txt': '下载文本 (.txt)',
|
||||
'share.img': '下载长图',
|
||||
'success.copy': '已复制到剪贴板!',
|
||||
'role.user': '你',
|
||||
'role.model': 'Gemini',
|
||||
'role.veo': 'Veo',
|
||||
|
||||
// SQL Tool
|
||||
'sql.format': '格式化 SQL',
|
||||
'sql.convert': '方言转换',
|
||||
'sql.replace': 'AS 序号替换',
|
||||
'sql.minify': '单行压缩',
|
||||
'sql.input': '输入 SQL',
|
||||
'sql.output': '输出 SQL',
|
||||
'sql.target_db': '目标数据库',
|
||||
'sql.placeholder': '在此粘贴您的 SQL...',
|
||||
'sql.processing': '处理中...',
|
||||
'sql.custom': '其他 / AI 助手',
|
||||
'sql.custom_prompt': '描述您的需求(例如“提取表名”或“生成建表语句”)',
|
||||
'sql.run': '执行',
|
||||
|
||||
// New Toasts
|
||||
'warning.no_sql': '请先输入 SQL 代码。',
|
||||
'success.apikey_updated': 'API Key 配置已更新。',
|
||||
'success.data_imported': '数据导入成功。',
|
||||
'error.invalid_file': '无效的文件格式。',
|
||||
'error.screenshot': '截图生成失败。',
|
||||
'error.tts': '语音生成失败。',
|
||||
},
|
||||
'ja': {
|
||||
'app.name': 'BitSage',
|
||||
'menu.home': 'ホーム',
|
||||
'group.learning': '学習と研究',
|
||||
'group.cs': 'コンピュータサイエンス分野',
|
||||
'group.tools': '研究ツール',
|
||||
'group.creation': 'クリエイティブスタジオ',
|
||||
'module.tutor': 'CS 講師',
|
||||
'module.thinker': '深い思考',
|
||||
'module.research': '研究',
|
||||
|
||||
'module.math': '数学基礎',
|
||||
'module.theory': '計算理論',
|
||||
'module.principles': 'コンピュータ・アーキテクチャ',
|
||||
'module.soft_eng': 'ソフトウェア工学',
|
||||
'module.graphics': 'CG・グラフィックス',
|
||||
'module.network': 'コンピュータネットワーク',
|
||||
'module.ai_lab': '人工知能ラボ',
|
||||
'module.research': '学術検索',
|
||||
'module.sql': 'SQLツール',
|
||||
'module.vision': 'ビジョンラボ',
|
||||
'module.studio': 'ビデオスタジオ',
|
||||
'module.audio': 'オーディオラボ',
|
||||
|
||||
'desc.tutor': '専門的なQ&Aとコーディング支援',
|
||||
'desc.thinker': '複雑な問題に対する深い推論',
|
||||
'desc.math': '離散数学、微積分、論理学',
|
||||
'desc.theory': 'オートマトン、計算可能性、複雑性',
|
||||
'desc.principles': '命令セット、パイプライン、メモリ階層',
|
||||
'desc.soft_eng': 'デザインパターン、DevOps',
|
||||
'desc.graphics': 'レンダリング、OpenGL、WebGL',
|
||||
'desc.network': 'プロトコル、セキュリティ、分散システム',
|
||||
'desc.ai_lab': '機械学習、深層学習、ニューラルネットワーク',
|
||||
'desc.research': 'Webに基づく学術研究',
|
||||
'desc.sql': 'SQLのフォーマット、変換、操作',
|
||||
'desc.vision': '画像分析と生成',
|
||||
'desc.studio': 'AIビデオ生成スタジオ',
|
||||
'desc.audio': '音声認識と音声合成',
|
||||
|
||||
'hello.math': 'CS 数学',
|
||||
'hello.theory': '計算機科学の理論',
|
||||
'hello.principles': 'コンピュータ・アーキテクチャ',
|
||||
'hello.soft_eng': 'CG & レンダリング',
|
||||
'hello.graphics': 'CG & レンダリング',
|
||||
'hello.network': 'ネットワーク工学',
|
||||
'hello.ai_lab': 'AI & 機械学習',
|
||||
'hello.research': '学術研究アシスタント',
|
||||
|
||||
'placeholder.math': '離散数学、線形代数、証明について質問する...',
|
||||
'placeholder.theory': 'チューリングマシン、P vs NP、有限オートマトンについて質問する...',
|
||||
'placeholder.principles': 'RISC-V、キャッシュコヒーレンス、デジタル論理について質問する...',
|
||||
'placeholder.soft_eng': 'デザインパターン、アジャイル、システムアーキテクチャについて質問する...',
|
||||
'placeholder.graphics': 'レイトレーシング、シェーダー、WebGLについて質問する...',
|
||||
'placeholder.network': 'TCP/IP、OSIモデル、ネットワークセキュリティについて質問する...',
|
||||
'placeholder.ai_lab': 'Transformer、バックプロパゲーション、PyTorchについて質問する...',
|
||||
'placeholder.research': '論文、技術文書、引用を検索...',
|
||||
|
||||
'welcome.title': 'BitSageへようこそ',
|
||||
'welcome.subtitle': 'コンピュータサイエンス学習のためのAIパートナー。',
|
||||
'welcome.setup': '開始するにはGemini APIキーを入力してください。',
|
||||
@@ -221,11 +392,15 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'action.generate': '生成',
|
||||
'action.new_chat': '新しいチャット',
|
||||
'action.install': 'アプリをインストール',
|
||||
'action.toggle_think': '深い思考モード',
|
||||
'action.lang_mode': 'AI 言語',
|
||||
'action.lang_system': 'システム言語',
|
||||
'action.lang_input': '入力に合わせる',
|
||||
|
||||
'history.title': '履歴',
|
||||
'history.empty': 'このモジュールの履歴はありません。',
|
||||
|
||||
'prompt.placeholder': 'CSについて何でも聞いてください...',
|
||||
'prompt.placeholder': 'この分野について質問してください...',
|
||||
'status.thinking': '深く考えています...',
|
||||
'status.generating': '生成中...',
|
||||
'status.recording': '録音中...',
|
||||
@@ -268,26 +443,90 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'ui.workbench': 'ワークベンチ',
|
||||
'ui.gallery': '生成結果',
|
||||
'ui.config': '設定',
|
||||
|
||||
// Share & Metadata
|
||||
'share.title': 'セッションを共有',
|
||||
'share.copy': '会話をコピー',
|
||||
'share.txt': 'テキストをダウンロード',
|
||||
'share.img': '長い画像を保存',
|
||||
'success.copy': 'クリップボードにコピーしました!',
|
||||
'role.user': 'あなた',
|
||||
'role.model': 'Gemini',
|
||||
'role.veo': 'Veo',
|
||||
|
||||
// SQL Tool
|
||||
'sql.format': 'SQL整形',
|
||||
'sql.convert': '方言変換',
|
||||
'sql.replace': 'AS番号置換',
|
||||
'sql.minify': '一行化',
|
||||
'sql.input': '入力SQL',
|
||||
'sql.output': '出力SQL',
|
||||
'sql.target_db': 'ターゲットDB',
|
||||
'sql.placeholder': 'ここにSQLを貼り付けてください...',
|
||||
'sql.processing': '処理中...',
|
||||
'sql.custom': 'その他 / AI アシスト',
|
||||
'sql.custom_prompt': '操作を説明してください(例:「テーブル名を抽出」)',
|
||||
'sql.run': '実行',
|
||||
|
||||
// New Toasts
|
||||
'warning.no_sql': '先にSQLコードを入力してください。',
|
||||
'success.apikey_updated': 'APIキーの設定が更新されました。',
|
||||
'success.data_imported': 'データのインポートが完了しました。',
|
||||
'error.invalid_file': '無効なファイル形式です。',
|
||||
'error.screenshot': 'スクリーンショットの生成に失敗しました。',
|
||||
'error.tts': '音声生成に失敗しました。',
|
||||
},
|
||||
'zh-TW': {
|
||||
'app.name': '比特智者',
|
||||
'menu.home': '首頁',
|
||||
'group.learning': '學習與研究',
|
||||
'group.cs': '計算機科學領域',
|
||||
'group.tools': '研究工具',
|
||||
'group.creation': '創意工作室',
|
||||
'module.tutor': 'CS 導師',
|
||||
'module.thinker': '深度思考',
|
||||
|
||||
'module.math': '數學基礎',
|
||||
'module.theory': '計算理論',
|
||||
'module.principles': '電腦體系結構',
|
||||
'module.soft_eng': '軟體工程',
|
||||
'module.graphics': '計算機圖形學',
|
||||
'module.network': '計算機網路',
|
||||
'module.ai_lab': '人工智慧',
|
||||
'module.research': '學術搜尋',
|
||||
'module.sql': 'SQL 工具箱',
|
||||
'module.vision': '視覺實驗室',
|
||||
'module.studio': '影片工作室',
|
||||
'module.audio': '音訊實驗室',
|
||||
|
||||
'desc.tutor': '專家級問答與程式碼輔助',
|
||||
'desc.thinker': '針對複雜問題的深度推理',
|
||||
'desc.math': '離散數學、微積分與邏輯',
|
||||
'desc.theory': '自動機、可計算性與複雜性',
|
||||
'desc.principles': '指令集、管線化與儲存層次',
|
||||
'desc.soft_eng': '設計模式與 DevOps',
|
||||
'desc.graphics': '渲染、OpenGL 與 WebGL',
|
||||
'desc.network': '通訊協定、資安與分散式系統',
|
||||
'desc.ai_lab': '機器學習、深度學習與神經網路',
|
||||
'desc.research': '基於網路的學術研究',
|
||||
'desc.sql': 'SQL 格式化、轉換與處理',
|
||||
'desc.vision': '圖像分析與生成',
|
||||
'desc.studio': 'AI 影片生成工作室',
|
||||
'desc.audio': '語音轉文字與文字轉語音',
|
||||
|
||||
'hello.math': 'CS 數學基礎',
|
||||
'hello.theory': '電腦理論基礎',
|
||||
'hello.principles': '電腦體系結構',
|
||||
'hello.soft_eng': '軟體工程與架構',
|
||||
'hello.graphics': '圖形學與渲染',
|
||||
'hello.network': '網路工程與資安',
|
||||
'hello.ai_lab': 'AI 與深度學習',
|
||||
'hello.research': '學術研究助手',
|
||||
|
||||
'placeholder.math': '詢問關於離散數學、線性代數或證明的問題...',
|
||||
'placeholder.theory': '詢問關於圖靈機、P vs NP 或有限自動機...',
|
||||
'placeholder.principles': '詢問關於RISC-V、快取一致性或數位邏輯...',
|
||||
'placeholder.soft_eng': '詢問關於設計模式、敏捷開發或系統架構...',
|
||||
'placeholder.graphics': '詢問關於光線追蹤、著色器或WebGL...',
|
||||
'placeholder.network': '詢問關於TCP/IP、OSI模型或網路安全...',
|
||||
'placeholder.ai_lab': '詢問關於Transformer、反向傳播或PyTorch...',
|
||||
'placeholder.research': '搜尋論文、技術文件或引用...',
|
||||
|
||||
'welcome.title': '歡迎使用比特智者',
|
||||
'welcome.subtitle': '您的計算機科學與技術學習 AI 助手。',
|
||||
'welcome.setup': '請輸入您的 Gemini API Key 以開始使用。',
|
||||
@@ -310,11 +549,15 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'action.generate': '生成',
|
||||
'action.new_chat': '新對話',
|
||||
'action.install': '安裝應用',
|
||||
'action.toggle_think': '深度思考模式',
|
||||
'action.lang_mode': 'AI 語言',
|
||||
'action.lang_system': '跟隨系統',
|
||||
'action.lang_input': '跟隨輸入',
|
||||
|
||||
'history.title': '歷史記錄',
|
||||
'history.empty': '暫無該模組的歷史記錄',
|
||||
|
||||
'prompt.placeholder': '問我任何關於計算機科學的問題...',
|
||||
'prompt.placeholder': '在此領域提問...',
|
||||
'status.thinking': '深度思考中...',
|
||||
'status.generating': '生成中...',
|
||||
'status.recording': '錄音中...',
|
||||
@@ -357,6 +600,38 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'ui.workbench': '工作台',
|
||||
'ui.gallery': '生成結果',
|
||||
'ui.config': '參數配置',
|
||||
|
||||
// Share & Metadata
|
||||
'share.title': '分享會話',
|
||||
'share.copy': '複製完整對話',
|
||||
'share.txt': '下載文字 (.txt)',
|
||||
'share.img': '下載長圖',
|
||||
'success.copy': '已複製到剪貼簿!',
|
||||
'role.user': '你',
|
||||
'role.model': 'Gemini',
|
||||
'role.veo': 'Veo',
|
||||
|
||||
// SQL Tool
|
||||
'sql.format': '格式化 SQL',
|
||||
'sql.convert': '方言轉換',
|
||||
'sql.replace': 'AS 序號替換',
|
||||
'sql.minify': '單行壓縮',
|
||||
'sql.input': '輸入 SQL',
|
||||
'sql.output': '輸出 SQL',
|
||||
'sql.target_db': '目標資料庫',
|
||||
'sql.placeholder': '在此貼上您的 SQL...',
|
||||
'sql.processing': '處理中...',
|
||||
'sql.custom': '其他 / AI 助手',
|
||||
'sql.custom_prompt': '描述您的需求(例如「提取表名」或「生成建表語句」)',
|
||||
'sql.run': '執行',
|
||||
|
||||
// New Toasts
|
||||
'warning.no_sql': '請先輸入 SQL 程式碼。',
|
||||
'success.apikey_updated': 'API Key 設定已更新。',
|
||||
'success.data_imported': '資料匯入成功。',
|
||||
'error.invalid_file': '無效的檔案格式。',
|
||||
'error.screenshot': '截圖生成失敗。',
|
||||
'error.tts': '語音生成失敗。',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
31
sw.js
Normal file
31
sw.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// BitSage Service Worker
|
||||
const CACHE_NAME = 'bitsage-v1';
|
||||
const ASSETS_TO_CACHE = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/index.tsx'
|
||||
];
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then((cache) => {
|
||||
// Best effort caching
|
||||
return cache.addAll(ASSETS_TO_CACHE).catch(() => {});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
// Simple pass-through fetch handler to satisfy PWA requirements
|
||||
// In a full production app, you might want more robust offline caching
|
||||
event.respondWith(
|
||||
fetch(event.request).catch(() => {
|
||||
return caches.match(event.request);
|
||||
})
|
||||
);
|
||||
});
|
||||
16
types.ts
16
types.ts
@@ -1,7 +1,18 @@
|
||||
export enum AppModule {
|
||||
TUTOR = 'tutor', // Q&A
|
||||
THINKER = 'thinker', // Deep Thinking
|
||||
// CS Domains
|
||||
MATH = 'math', // Mathematics
|
||||
THEORY = 'theory', // Theory of Computation
|
||||
PRINCIPLES = 'principles', // Computer Architecture (formerly Principles)
|
||||
SOFT_ENG = 'soft_eng', // Software Engineering
|
||||
GRAPHICS = 'graphics', // Computer Graphics
|
||||
NETWORK = 'network', // Computer Networks
|
||||
AI_LAB = 'ai_lab', // Artificial Intelligence
|
||||
|
||||
// Tools
|
||||
RESEARCH = 'research', // Search Grounding
|
||||
SQL = 'sql', // SQL Tools
|
||||
|
||||
// Creative
|
||||
VISION = 'vision', // Image Gen & Analysis
|
||||
STUDIO = 'studio', // Video Gen & Analysis
|
||||
AUDIO = 'audio' // TTS & Transcribe
|
||||
@@ -40,6 +51,7 @@ export interface AppSettings {
|
||||
language: 'en' | 'ja' | 'zh-CN' | 'zh-TW';
|
||||
theme: 'light' | 'dark' | 'system';
|
||||
hasCompletedOnboarding: boolean;
|
||||
aiResponseLanguage: 'system' | 'input'; // 'system' = use App Language, 'input' = Match User Input
|
||||
}
|
||||
|
||||
export interface VeoConfig {
|
||||
|
||||
Reference in New Issue
Block a user