初始化项目;更新至 v0.1.0_20251225 版本
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
21
README.md
21
README.md
@@ -1,3 +1,20 @@
|
||||
# ai-app-ckg
|
||||
<div align="center">
|
||||
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||
</div>
|
||||
|
||||
使用 Google AI Studio 构建的计算机科学与技术学习应用——BitSage - AI CS Tutor
|
||||
# Run and deploy your AI Studio app
|
||||
|
||||
This contains everything you need to run your app locally.
|
||||
|
||||
View your app in AI Studio: https://ai.studio/apps/drive/1wH16LSTzg8m7RgPeCzYP99HiinOHmyij
|
||||
|
||||
## Run Locally
|
||||
|
||||
**Prerequisites:** Node.js
|
||||
|
||||
|
||||
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`
|
||||
|
||||
122
index.html
Normal file
122
index.html
Normal file
@@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>BitSage - CS Learning Companion</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
200: '#bfdbfe',
|
||||
300: '#93c5fd',
|
||||
400: '#60a5fa',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
800: '#1e40af',
|
||||
900: '#1e3a8a',
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
'fade-in': 'fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards',
|
||||
'slide-up': 'slideUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards',
|
||||
'bounce-in': 'bounceIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
slideUp: {
|
||||
'0%': { transform: 'translateY(20px)', opacity: '0' },
|
||||
'100%': { transform: 'translateY(0)', opacity: '1' },
|
||||
},
|
||||
bounceIn: {
|
||||
'0%': { transform: 'scale(0.9)', opacity: '0' },
|
||||
'100%': { transform: 'scale(1)', opacity: '1' },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</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" />
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="data:application/json;base64,ewogICJuYW1lIjogIkJpdFNhZ2UgLSBDUyBMZWFybmluZyBDb21wYW5pb24iLAogICJzaG9ydF9uYW1lIjogIkJpdFNhZ2UiLAogICJzdGFydF91cmwiOiAiLyIsCiAgImRpc3BsYXkiOiAiYnJvd3NlciIsCiAgImJhY2tncm91bmRfY29xvciI6ICIjZmZmZmZmIiwKICAidGhlbWVfY29xvciI6ICIjMjU2M2ViIiwKICAiaWNvbnMiOiBbCiAgICB7CiAgICAgICJzcmMiOiAiZGF0YTppbWFnZS9zdmcreG1sLCUzQ3N2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCcgZmlsbD0nbm9uZScgc3Ryb2tlPSclMjMyNTYzZWInIHN0cm9rZS13aWR0aD0nMicgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJ%2BJTNDcGF0aCBkPSdNOS41IDJBMi41IDIuNSAwIDAgMSAxMiA0LjV2MTVhMi41IDIuNSAwIDAgMS00Ljk2LjQ0IDIuNSAyLjUgMCAwIDEtMi45Ni0zLjA4IDMgMyAwIDAgMS0uMzQtNS41OCAyLjUgMi41IDAgMCAxIDEuMzItNC4yNCAyLjUgMi41IDAgMCAxIDEuMzItNC4yNCAyLjUgMi41IDAgMCAxIDEuOTgtM0EyLjUgMi41IDAgMCAxIDkuNSAyWicvJTNFJTNDcGF0aCBkPSdNMTQuNSAyQTIuNSAyLjUgMCAwIDAgMTIgNC41djE1YTIuNSAyLjUgMCAwIDAgNC45Ni40NCAyLjUgMi41IDAgMCAwIDIuOTYtMy4wOCAzIDMgMCAwIDAgLjM0LTUuNTggMi41IDIuNSAwIDAgMC0xLjMyLTQuMjQgMi41IDIuNSAwIDAgMC0xLjk4LTNBMi41IDIuNSAwIDAgMCAxNC41IDJaJy8lM0UlM0Mvc3ZnJTNFLg0KIiwKICAgICAgInR5cGUiOiAiaW1hZ2Uvc3ZnK3htbCIsCiAgICAgICJzaXplcyI6ICIxOTJ4MTkyIDUxMng1MTIiCiAgICB9CiAgXQp9" />
|
||||
|
||||
<style>
|
||||
/* Custom scrollbar for webkit */
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
|
||||
.dark ::-webkit-scrollbar-thumb { background: #475569; }
|
||||
|
||||
/* Markdown content styles */
|
||||
.markdown-body p { margin-bottom: 0.75em; line-height: 1.6; }
|
||||
.markdown-body ul { list-style-type: disc; padding-left: 1.5em; margin-bottom: 0.75em; }
|
||||
.markdown-body ol { list-style-type: decimal; padding-left: 1.5em; margin-bottom: 0.75em; }
|
||||
.markdown-body pre { background: #f1f5f9; padding: 1em; border-radius: 0.5em; overflow-x: auto; margin-bottom: 1em; }
|
||||
.dark .markdown-body pre { background: #1e293b; }
|
||||
.markdown-body code { font-family: monospace; background: #e2e8f0; padding: 0.1em 0.3em; border-radius: 0.25em; font-size: 0.9em; }
|
||||
.dark .markdown-body code { background: #334155; color: #e2e8f0; }
|
||||
.markdown-body pre code { background: transparent; padding: 0; color: inherit; }
|
||||
.markdown-body h1, .markdown-body h2, .markdown-body h3 { font-weight: 700; margin-top: 1.5em; margin-bottom: 0.5em; }
|
||||
.markdown-body h1 { font-size: 1.5em; }
|
||||
.markdown-body h2 { font-size: 1.25em; }
|
||||
.markdown-body a { color: #3b82f6; text-decoration: underline; }
|
||||
|
||||
/* Typing Indicator Animation */
|
||||
.typing-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.typing-indicator span {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #94a3b8; /* slate-400 */
|
||||
border-radius: 50%;
|
||||
animation: typingBounce 1.4s infinite ease-in-out both;
|
||||
}
|
||||
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
|
||||
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
|
||||
|
||||
@keyframes typingBounce {
|
||||
0%, 80%, 100% { transform: scale(0); opacity: 0.6; }
|
||||
40% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"react": "https://esm.sh/react@^19.2.3",
|
||||
"react-dom/": "https://esm.sh/react-dom@^19.2.3/",
|
||||
"react/": "https://esm.sh/react@^19.2.3/",
|
||||
"@google/genai": "https://esm.sh/@google/genai@^1.34.0",
|
||||
"lucide-react": "https://esm.sh/lucide-react@^0.562.0",
|
||||
"react-markdown": "https://esm.sh/react-markdown@^10.1.0",
|
||||
"remark-gfm": "https://esm.sh/remark-gfm@^4.0.1",
|
||||
"html2canvas": "https://esm.sh/html2canvas@^1.4.1",
|
||||
"vite": "https://esm.sh/vite@^7.3.0",
|
||||
"@vitejs/plugin-react": "https://esm.sh/@vitejs/plugin-react@^5.1.2"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
15
index.tsx
Normal file
15
index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
if (!rootElement) {
|
||||
throw new Error("Could not find root element to mount to");
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
8
metadata.json
Normal file
8
metadata.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "BitSage - AI CS Tutor",
|
||||
"description": "An intelligent companion for Computer Science & Technology learning. Features Q&A, Deep Thinking, Research, and Multimedia generation.",
|
||||
"requestFramePermissions": [
|
||||
"camera",
|
||||
"microphone"
|
||||
]
|
||||
}
|
||||
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "bitsage",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"start": "serve -s dist -l tcp://0.0.0.0:${PORT:-8080}"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.34.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"vite": "^5.3.4",
|
||||
"serve": "^14.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
}
|
||||
BIN
releases/HTY1024-APP-CKG-0.1.0_20251225.zip
Normal file
BIN
releases/HTY1024-APP-CKG-0.1.0_20251225.zip
Normal file
Binary file not shown.
210
services/gemini.ts
Normal file
210
services/gemini.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { GoogleGenAI, Modality } from "@google/genai";
|
||||
import { AppModule, ImageConfig, VeoConfig } from '../types';
|
||||
|
||||
// Models
|
||||
const MODEL_CHAT_PRO = 'gemini-3-pro-preview';
|
||||
const MODEL_RESEARCH = 'gemini-3-flash-preview';
|
||||
const MODEL_IMAGE = 'gemini-3-pro-image-preview';
|
||||
const MODEL_VIDEO = 'veo-3.1-fast-generate-preview';
|
||||
const MODEL_AUDIO_TTS = 'gemini-2.5-flash-preview-tts';
|
||||
const MODEL_AUDIO_TRANS = 'gemini-3-flash-preview';
|
||||
|
||||
export class GeminiService {
|
||||
private ai: GoogleGenAI | null = null;
|
||||
private apiKey: string;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
this.apiKey = apiKey;
|
||||
if (apiKey) {
|
||||
this.ai = new GoogleGenAI({ apiKey });
|
||||
}
|
||||
}
|
||||
|
||||
updateKey(apiKey: string) {
|
||||
this.apiKey = apiKey;
|
||||
this.ai = new GoogleGenAI({ apiKey });
|
||||
}
|
||||
|
||||
private getClient() {
|
||||
if (!this.ai) throw new Error("API Key not set");
|
||||
return this.ai;
|
||||
}
|
||||
|
||||
async generateText(
|
||||
prompt: string,
|
||||
module: AppModule,
|
||||
history: {role: string, parts: any[]}[],
|
||||
media?: { data: string, mimeType: string }[]
|
||||
) {
|
||||
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: {} }];
|
||||
break;
|
||||
case AppModule.VISION:
|
||||
model = MODEL_CHAT_PRO; // For analysis
|
||||
break;
|
||||
case AppModule.STUDIO:
|
||||
model = MODEL_CHAT_PRO; // For analysis
|
||||
break;
|
||||
case AppModule.AUDIO:
|
||||
model = MODEL_AUDIO_TRANS; // For transcription/analysis
|
||||
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.
|
||||
|
||||
// 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
|
||||
}));
|
||||
|
||||
const chat = ai.chats.create({
|
||||
model: model,
|
||||
config: config,
|
||||
history: sdkHistory
|
||||
});
|
||||
|
||||
// Prepare message content
|
||||
let messageContent: any = { role: 'user', parts: [{ text: prompt }] };
|
||||
if (media && media.length > 0) {
|
||||
messageContent.parts = [
|
||||
...media.map(m => ({ inlineData: { mimeType: m.mimeType, data: m.data } })),
|
||||
{ text: prompt }
|
||||
];
|
||||
}
|
||||
|
||||
// For Streaming
|
||||
// We will return the stream iterator
|
||||
const resultStream = await chat.sendMessageStream({ message: messageContent });
|
||||
return resultStream;
|
||||
}
|
||||
|
||||
async generateImage(prompt: string, config: ImageConfig) {
|
||||
const ai = this.getClient();
|
||||
// Using generateContent for nano banana series (gemini-3-pro-image-preview)
|
||||
const response = await ai.models.generateContent({
|
||||
model: MODEL_IMAGE,
|
||||
contents: {
|
||||
parts: [{ text: prompt }]
|
||||
},
|
||||
config: {
|
||||
imageConfig: {
|
||||
aspectRatio: config.aspectRatio,
|
||||
imageSize: config.size
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Extract image
|
||||
for (const part of response.candidates?.[0]?.content?.parts || []) {
|
||||
if (part.inlineData) {
|
||||
return `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`;
|
||||
}
|
||||
}
|
||||
throw new Error("No image generated");
|
||||
}
|
||||
|
||||
async generateVideo(prompt: string, config: VeoConfig) {
|
||||
const ai = this.getClient();
|
||||
// Veo check for key selection (browser env only)
|
||||
if (typeof window !== 'undefined' && (window as any).aistudio) {
|
||||
try {
|
||||
const hasKey = await (window as any).aistudio.hasSelectedApiKey();
|
||||
if (!hasKey) {
|
||||
await (window as any).aistudio.openSelectKey();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Veo key selection check failed, proceeding with env key", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
config: {
|
||||
numberOfVideos: 1,
|
||||
resolution: config.resolution,
|
||||
aspectRatio: config.aspectRatio
|
||||
}
|
||||
});
|
||||
|
||||
while (!operation.done) {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
operation = await ai.operations.getVideosOperation({ operation: operation });
|
||||
}
|
||||
|
||||
const videoUri = operation.response?.generatedVideos?.[0]?.video?.uri;
|
||||
if (!videoUri) throw new Error("No video URI returned");
|
||||
|
||||
// Fetch the video bytes
|
||||
const vidResponse = await fetch(`${videoUri}&key=${this.apiKey}`);
|
||||
const blob = await vidResponse.blob();
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
async transcribeAudio(base64Audio: string, mimeType: string) {
|
||||
const ai = this.getClient();
|
||||
const response = await ai.models.generateContent({
|
||||
model: MODEL_AUDIO_TRANS,
|
||||
contents: {
|
||||
parts: [
|
||||
{ inlineData: { mimeType: mimeType, data: base64Audio } },
|
||||
{ text: "Transcribe this audio exactly." }
|
||||
]
|
||||
}
|
||||
});
|
||||
return response.text;
|
||||
}
|
||||
|
||||
async generateSpeech(text: string, voice: string = 'Kore') {
|
||||
const ai = this.getClient();
|
||||
const response = await ai.models.generateContent({
|
||||
model: MODEL_AUDIO_TTS,
|
||||
contents: [{ parts: [{ text }] }],
|
||||
config: {
|
||||
responseModalities: [Modality.AUDIO],
|
||||
speechConfig: {
|
||||
voiceConfig: {
|
||||
prebuiltVoiceConfig: { voiceName: voice },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const base64Audio = response.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data;
|
||||
if (!base64Audio) throw new Error("No audio generated");
|
||||
return base64Audio;
|
||||
}
|
||||
}
|
||||
365
services/i18n.ts
Normal file
365
services/i18n.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
type Language = 'en' | 'ja' | 'zh-CN' | 'zh-TW';
|
||||
|
||||
const translations: Record<Language, Record<string, string>> = {
|
||||
'en': {
|
||||
'app.name': 'BitSage',
|
||||
'menu.home': 'Home',
|
||||
'group.learning': 'Learning & Research',
|
||||
'group.creation': 'Creative Studio',
|
||||
'module.tutor': 'CS Tutor',
|
||||
'module.thinker': 'Deep Thinker',
|
||||
'module.research': 'Research',
|
||||
'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.research': 'Web-grounded academic research',
|
||||
'desc.vision': 'Image analysis and generation',
|
||||
'desc.studio': 'AI video generation studio',
|
||||
'desc.audio': 'Speech-to-text and Text-to-speech',
|
||||
|
||||
'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.',
|
||||
|
||||
'settings.title': 'Settings',
|
||||
'settings.apiKey': 'API Key',
|
||||
'settings.language': 'Language',
|
||||
'settings.theme': 'Theme',
|
||||
'settings.data': 'Data Management',
|
||||
'settings.export': 'Export Data',
|
||||
'settings.import': 'Import Data',
|
||||
'settings.clear_data': 'Clear All Data',
|
||||
'settings.danger_zone': 'Danger Zone',
|
||||
'settings.key_notice': 'Stored locally in your browser.',
|
||||
|
||||
'action.send': 'Send',
|
||||
'action.upload': 'Upload',
|
||||
'action.record': 'Record',
|
||||
'action.stop': 'Stop',
|
||||
'action.generate': 'Generate',
|
||||
'action.new_chat': 'New Chat',
|
||||
'action.install': 'Install App',
|
||||
|
||||
'history.title': 'History',
|
||||
'history.empty': 'No history for this module.',
|
||||
|
||||
'prompt.placeholder': 'Ask me anything about CS...',
|
||||
'status.thinking': 'Thinking deeply...',
|
||||
'status.generating': 'Generating...',
|
||||
'status.recording': 'Recording...',
|
||||
|
||||
'msg.sources': 'Sources:',
|
||||
'msg.thinking': 'Thinking...',
|
||||
'disclaimer': 'Gemini can make mistakes. Check important info.',
|
||||
|
||||
'error.no_key': 'Please set your API Key in settings.',
|
||||
'confirm.delete': 'Are you sure you want to delete this chat?',
|
||||
'confirm.clear_data': 'WARNING: This will delete ALL your chat history and settings. This action cannot be undone. Are you sure?',
|
||||
'alert.import_success': 'Import successful!',
|
||||
'alert.invalid_file': 'Invalid file format',
|
||||
|
||||
'opt.landscape': 'Landscape (16:9)',
|
||||
'opt.portrait': 'Portrait (9:16)',
|
||||
'opt.square': 'Square (1:1)',
|
||||
'opt.wide': 'Wide (16:9)',
|
||||
|
||||
'veo.prompt': 'Describe the video you want to generate...',
|
||||
'img.prompt': 'Describe the image you want to generate...',
|
||||
'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.',
|
||||
'guide.vision.tip2': 'Upload a UML diagram to get a code implementation.',
|
||||
|
||||
'guide.studio.title': 'Video Studio',
|
||||
'guide.studio.desc': 'Create short, high-quality videos using the Veo model. Perfect for demos or visual concepts.',
|
||||
'guide.studio.tip1': 'Specify movement and camera angles (e.g., "Drone shot of...").',
|
||||
'guide.studio.tip2': 'Video generation takes a few minutes. Please be patient.',
|
||||
|
||||
'guide.audio.title': 'Audio Lab',
|
||||
'guide.audio.desc': 'Convert text to natural-sounding speech or transcribe recordings.',
|
||||
'guide.audio.tip1': 'Enter text to generate speech (TTS).',
|
||||
'guide.audio.tip2': 'Future update: Upload audio for transcription.',
|
||||
|
||||
'ui.workbench': 'Workbench',
|
||||
'ui.gallery': 'Results',
|
||||
'ui.config': 'Configuration',
|
||||
},
|
||||
'zh-CN': {
|
||||
'app.name': '比特智者',
|
||||
'menu.home': '首页',
|
||||
'group.learning': '学习与研究',
|
||||
'group.creation': '创意工作室',
|
||||
'module.tutor': 'CS 导师',
|
||||
'module.thinker': '深度思考',
|
||||
'module.research': '学术搜索',
|
||||
'module.vision': '视觉实验室',
|
||||
'module.studio': '视频工作室',
|
||||
'module.audio': '音频实验室',
|
||||
|
||||
'desc.tutor': '专家级问答与代码辅助',
|
||||
'desc.thinker': '针对复杂问题的深度推理',
|
||||
'desc.research': '基于网络的学术研究',
|
||||
'desc.vision': '图像分析与生成',
|
||||
'desc.studio': 'AI 视频生成工作室',
|
||||
'desc.audio': '语音转文字与文字转语音',
|
||||
|
||||
'welcome.title': '欢迎使用比特智者',
|
||||
'welcome.subtitle': '您的计算机科学与技术学习 AI 助手。',
|
||||
'welcome.setup': '请输入您的 Gemini API Key 以开始使用。',
|
||||
|
||||
'settings.title': '设置',
|
||||
'settings.apiKey': 'API 密钥',
|
||||
'settings.language': '语言',
|
||||
'settings.theme': '主题',
|
||||
'settings.data': '数据管理',
|
||||
'settings.export': '导出数据',
|
||||
'settings.import': '导入数据',
|
||||
'settings.clear_data': '清除所有数据',
|
||||
'settings.danger_zone': '危险区域',
|
||||
'settings.key_notice': '仅保存在您的浏览器本地。',
|
||||
|
||||
'action.send': '发送',
|
||||
'action.upload': '上传',
|
||||
'action.record': '录音',
|
||||
'action.stop': '停止',
|
||||
'action.generate': '生成',
|
||||
'action.new_chat': '新会话',
|
||||
'action.install': '安装应用',
|
||||
|
||||
'history.title': '历史记录',
|
||||
'history.empty': '暂无该模块的历史记录',
|
||||
|
||||
'prompt.placeholder': '问我任何关于计算机科学的问题...',
|
||||
'status.thinking': '深度思考中...',
|
||||
'status.generating': '生成中...',
|
||||
'status.recording': '录音中...',
|
||||
|
||||
'msg.sources': '参考来源:',
|
||||
'msg.thinking': '思考中...',
|
||||
'disclaimer': 'Gemini 可能会犯错,请核实重要信息。',
|
||||
|
||||
'error.no_key': '请在设置中配置您的 API Key。',
|
||||
'confirm.delete': '确定要删除此会话吗?',
|
||||
'confirm.clear_data': '警告:此操作将删除您所有的聊天记录和设置,且无法撤销。确定要继续吗?',
|
||||
'alert.import_success': '导入成功!',
|
||||
'alert.invalid_file': '无效的文件格式',
|
||||
|
||||
'opt.landscape': '横屏 (16:9)',
|
||||
'opt.portrait': '竖屏 (9:16)',
|
||||
'opt.square': '方形 (1:1)',
|
||||
'opt.wide': '宽屏 (16:9)',
|
||||
|
||||
'veo.prompt': '描述您想生成的视频...',
|
||||
'img.prompt': '描述您想生成的图片...',
|
||||
'audio.prompt': '请输入文本以生成语音...',
|
||||
'btn.start': '开始体验',
|
||||
|
||||
'guide.vision.title': '视觉实验室',
|
||||
'guide.vision.desc': '根据文本描述生成高质量图像,或分析上传的图像(如UML图、代码截图)。',
|
||||
'guide.vision.tip1': '详细描述场景、风格和光照效果以获得最佳结果。',
|
||||
'guide.vision.tip2': '上传架构图可让 AI 辅助生成代码实现。',
|
||||
|
||||
'guide.studio.title': '视频工作室',
|
||||
'guide.studio.desc': '使用 Veo 模型生成短视频。非常适合演示、概念可视化。',
|
||||
'guide.studio.tip1': '指定动作和镜头角度(例如“无人机拍摄...”)。',
|
||||
'guide.studio.tip2': '视频生成需要几分钟时间,请耐心等待。',
|
||||
|
||||
'guide.audio.title': '音频实验室',
|
||||
'guide.audio.desc': '将文本转换为自然流畅的语音,或将录音转录为文本。',
|
||||
'guide.audio.tip1': '输入文本以生成语音 (TTS)。',
|
||||
'guide.audio.tip2': '后续更新:支持上传音频进行转录。',
|
||||
|
||||
'ui.workbench': '工作台',
|
||||
'ui.gallery': '生成结果',
|
||||
'ui.config': '参数配置',
|
||||
},
|
||||
'ja': {
|
||||
'app.name': 'BitSage',
|
||||
'menu.home': 'ホーム',
|
||||
'group.learning': '学習と研究',
|
||||
'group.creation': 'クリエイティブスタジオ',
|
||||
'module.tutor': 'CS 講師',
|
||||
'module.thinker': '深い思考',
|
||||
'module.research': '研究',
|
||||
'module.vision': 'ビジョンラボ',
|
||||
'module.studio': 'ビデオスタジオ',
|
||||
'module.audio': 'オーディオラボ',
|
||||
|
||||
'desc.tutor': '専門的なQ&Aとコーディング支援',
|
||||
'desc.thinker': '複雑な問題に対する深い推論',
|
||||
'desc.research': 'Webに基づく学術研究',
|
||||
'desc.vision': '画像分析と生成',
|
||||
'desc.studio': 'AIビデオ生成スタジオ',
|
||||
'desc.audio': '音声認識と音声合成',
|
||||
|
||||
'welcome.title': 'BitSageへようこそ',
|
||||
'welcome.subtitle': 'コンピュータサイエンス学習のためのAIパートナー。',
|
||||
'welcome.setup': '開始するにはGemini APIキーを入力してください。',
|
||||
|
||||
'settings.title': '設定',
|
||||
'settings.apiKey': 'APIキー',
|
||||
'settings.language': '言語',
|
||||
'settings.theme': 'テーマ',
|
||||
'settings.data': 'データ管理',
|
||||
'settings.export': 'エクスポート',
|
||||
'settings.import': 'インポート',
|
||||
'settings.clear_data': '全データを消去',
|
||||
'settings.danger_zone': '危険地帯',
|
||||
'settings.key_notice': 'ブラウザにローカル保存されます。',
|
||||
|
||||
'action.send': '送信',
|
||||
'action.upload': 'アップロード',
|
||||
'action.record': '録音',
|
||||
'action.stop': '停止',
|
||||
'action.generate': '生成',
|
||||
'action.new_chat': '新しいチャット',
|
||||
'action.install': 'アプリをインストール',
|
||||
|
||||
'history.title': '履歴',
|
||||
'history.empty': 'このモジュールの履歴はありません。',
|
||||
|
||||
'prompt.placeholder': 'CSについて何でも聞いてください...',
|
||||
'status.thinking': '深く考えています...',
|
||||
'status.generating': '生成中...',
|
||||
'status.recording': '録音中...',
|
||||
|
||||
'msg.sources': '情報源:',
|
||||
'msg.thinking': '思考中...',
|
||||
'disclaimer': 'Geminiは間違いを犯す可能性があります。',
|
||||
|
||||
'error.no_key': '設定でAPIキーを設定してください。',
|
||||
'confirm.delete': 'このチャットを削除してもよろしいですか?',
|
||||
'confirm.clear_data': '警告:これにより、すべてのチャット履歴と設定が削除されます。元に戻すことはできません。よろしいですか?',
|
||||
'alert.import_success': 'インポートに成功しました!',
|
||||
'alert.invalid_file': '無効なファイル形式です',
|
||||
|
||||
'opt.landscape': '横向き (16:9)',
|
||||
'opt.portrait': '縦向き (9:16)',
|
||||
'opt.square': '正方形 (1:1)',
|
||||
'opt.wide': 'ワイド (16:9)',
|
||||
|
||||
'veo.prompt': '生成したい動画を説明してください...',
|
||||
'img.prompt': '生成したい画像を説明してください...',
|
||||
'audio.prompt': '音声を生成するためのテキストを入力...',
|
||||
'btn.start': '始める',
|
||||
|
||||
'guide.vision.title': 'ビジョンラボ',
|
||||
'guide.vision.desc': 'テキストから高品質な画像を生成、または画像の分析。',
|
||||
'guide.vision.tip1': '詳細なシーン、スタイル、照明を記述してください。',
|
||||
'guide.vision.tip2': '図をアップロードしてコードを生成できます。',
|
||||
|
||||
'guide.studio.title': 'ビデオスタジオ',
|
||||
'guide.studio.desc': 'Veoモデルを使用して短いビデオを生成します。',
|
||||
'guide.studio.tip1': '動きとカメラアングルを指定してください。',
|
||||
'guide.studio.tip2': '生成には数分かかります。',
|
||||
|
||||
'guide.audio.title': 'オーディオラボ',
|
||||
'guide.audio.desc': 'テキストを自然な音声に変換します。',
|
||||
'guide.audio.tip1': 'テキストを入力して音声を生成 (TTS)。',
|
||||
'guide.audio.tip2': '将来の更新:文字起こし。',
|
||||
|
||||
'ui.workbench': 'ワークベンチ',
|
||||
'ui.gallery': '生成結果',
|
||||
'ui.config': '設定',
|
||||
},
|
||||
'zh-TW': {
|
||||
'app.name': '比特智者',
|
||||
'menu.home': '首頁',
|
||||
'group.learning': '學習與研究',
|
||||
'group.creation': '創意工作室',
|
||||
'module.tutor': 'CS 導師',
|
||||
'module.thinker': '深度思考',
|
||||
'module.research': '學術搜尋',
|
||||
'module.vision': '視覺實驗室',
|
||||
'module.studio': '影片工作室',
|
||||
'module.audio': '音訊實驗室',
|
||||
|
||||
'desc.tutor': '專家級問答與程式碼輔助',
|
||||
'desc.thinker': '針對複雜問題的深度推理',
|
||||
'desc.research': '基於網路的學術研究',
|
||||
'desc.vision': '圖像分析與生成',
|
||||
'desc.studio': 'AI 影片生成工作室',
|
||||
'desc.audio': '語音轉文字與文字轉語音',
|
||||
|
||||
'welcome.title': '歡迎使用比特智者',
|
||||
'welcome.subtitle': '您的計算機科學與技術學習 AI 助手。',
|
||||
'welcome.setup': '請輸入您的 Gemini API Key 以開始使用。',
|
||||
|
||||
'settings.title': '設定',
|
||||
'settings.apiKey': 'API 金鑰',
|
||||
'settings.language': '語言',
|
||||
'settings.theme': '主題',
|
||||
'settings.data': '資料管理',
|
||||
'settings.export': '匯出資料',
|
||||
'settings.import': '匯入資料',
|
||||
'settings.clear_data': '清除所有資料',
|
||||
'settings.danger_zone': '危險區域',
|
||||
'settings.key_notice': '僅保存在您的瀏覽器本地。',
|
||||
|
||||
'action.send': '傳送',
|
||||
'action.upload': '上傳',
|
||||
'action.record': '錄音',
|
||||
'action.stop': '停止',
|
||||
'action.generate': '生成',
|
||||
'action.new_chat': '新對話',
|
||||
'action.install': '安裝應用',
|
||||
|
||||
'history.title': '歷史記錄',
|
||||
'history.empty': '暫無該模組的歷史記錄',
|
||||
|
||||
'prompt.placeholder': '問我任何關於計算機科學的問題...',
|
||||
'status.thinking': '深度思考中...',
|
||||
'status.generating': '生成中...',
|
||||
'status.recording': '錄音中...',
|
||||
|
||||
'msg.sources': '參考來源:',
|
||||
'msg.thinking': '思考中...',
|
||||
'disclaimer': 'Gemini 可能會犯錯,請核實重要資訊。',
|
||||
|
||||
'error.no_key': '請在設定中配置您的 API Key。',
|
||||
'confirm.delete': '確定要刪除此對話嗎?',
|
||||
'confirm.clear_data': '警告:此操作將刪除您所有的聊天記錄和設定,且無法復原。確定要繼續嗎?',
|
||||
'alert.import_success': '匯入成功!',
|
||||
'alert.invalid_file': '無效的檔案格式',
|
||||
|
||||
'opt.landscape': '橫向 (16:9)',
|
||||
'opt.portrait': '直向 (9:16)',
|
||||
'opt.square': '方形 (1:1)',
|
||||
'opt.wide': '寬螢幕 (16:9)',
|
||||
|
||||
'veo.prompt': '描述您想生成的影片...',
|
||||
'img.prompt': '描述您想生成的圖片...',
|
||||
'audio.prompt': '請輸入文字以生成語音...',
|
||||
'btn.start': '開始體驗',
|
||||
|
||||
'guide.vision.title': '視覺實驗室',
|
||||
'guide.vision.desc': '根據文字描述生成高品質圖像,或分析上傳的圖像。',
|
||||
'guide.vision.tip1': '詳細描述場景、風格和光照效果以獲得最佳結果。',
|
||||
'guide.vision.tip2': '上傳架構圖可讓 AI 輔助生成程式碼實作。',
|
||||
|
||||
'guide.studio.title': '影片工作室',
|
||||
'guide.studio.desc': '使用 Veo 模型生成短影片。非常適合演示、概念視覺化。',
|
||||
'guide.studio.tip1': '指定動作和鏡頭角度(例如「無人機拍攝...」)。',
|
||||
'guide.studio.tip2': '影片生成需要幾分鐘時間,請耐心等待。',
|
||||
|
||||
'guide.audio.title': '音訊實驗室',
|
||||
'guide.audio.desc': '將文字轉換為自然流暢的語音,或將錄音轉錄為文字。',
|
||||
'guide.audio.tip1': '輸入文字以生成語音 (TTS)。',
|
||||
'guide.audio.tip2': '後續更新:支援上傳音訊進行轉錄。',
|
||||
|
||||
'ui.workbench': '工作台',
|
||||
'ui.gallery': '生成結果',
|
||||
'ui.config': '參數配置',
|
||||
}
|
||||
};
|
||||
|
||||
export const t = (key: string, lang: Language): string => {
|
||||
return translations[lang]?.[key] || translations['en'][key] || key;
|
||||
};
|
||||
22
tsconfig.json
Normal file
22
tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["vite.config.ts", "node_modules"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
10
tsconfig.node.json
Normal file
10
tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
53
types.ts
Normal file
53
types.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
export enum AppModule {
|
||||
TUTOR = 'tutor', // Q&A
|
||||
THINKER = 'thinker', // Deep Thinking
|
||||
RESEARCH = 'research', // Search Grounding
|
||||
VISION = 'vision', // Image Gen & Analysis
|
||||
STUDIO = 'studio', // Video Gen & Analysis
|
||||
AUDIO = 'audio' // TTS & Transcribe
|
||||
}
|
||||
|
||||
export enum MessageRole {
|
||||
USER = 'user',
|
||||
MODEL = 'model',
|
||||
SYSTEM = 'system'
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: string;
|
||||
role: MessageRole;
|
||||
text?: string;
|
||||
images?: string[]; // base64
|
||||
audio?: string; // base64
|
||||
video?: string; // uri or base64
|
||||
timestamp: number;
|
||||
isThinking?: boolean;
|
||||
thoughtProcess?: string;
|
||||
sources?: { uri: string; title: string }[];
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
id: string;
|
||||
title: string;
|
||||
module: AppModule;
|
||||
messages: Message[];
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface AppSettings {
|
||||
apiKey: string;
|
||||
language: 'en' | 'ja' | 'zh-CN' | 'zh-TW';
|
||||
theme: 'light' | 'dark' | 'system';
|
||||
hasCompletedOnboarding: boolean;
|
||||
}
|
||||
|
||||
export interface VeoConfig {
|
||||
aspectRatio: '16:9' | '9:16';
|
||||
resolution: '720p' | '1080p';
|
||||
}
|
||||
|
||||
export interface ImageConfig {
|
||||
size: '1K' | '2K' | '4K';
|
||||
aspectRatio: '1:1' | '3:4' | '4:3' | '16:9' | '9:16';
|
||||
}
|
||||
10
vite.config.ts
Normal file
10
vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 8080,
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user