feat: Initialize AgentBlock project structure

Sets up the foundational files and dependencies for the AgentBlock scientific modeling platform. Includes project metadata, React app structure, Vite configuration, and essential dependencies for AI integration, UI components, and styling.
This commit is contained in:
jerryW123
2026-05-06 00:40:25 +08:00
parent 2da7196efc
commit 2814ec412d
23 changed files with 7885 additions and 8 deletions
+9
View File
@@ -0,0 +1,9 @@
# GEMINI_API_KEY: Required for Gemini AI API calls.
# AI Studio automatically injects this at runtime from user secrets.
# Users configure this via the Secrets panel in the AI Studio UI.
GEMINI_API_KEY="MY_GEMINI_API_KEY"
# APP_URL: The URL where this applet is hosted.
# AI Studio automatically injects this at runtime with the Cloud Run service URL.
# Used for self-referential links, OAuth callbacks, and API endpoints.
APP_URL="MY_APP_URL"
+8
View File
@@ -0,0 +1,8 @@
node_modules/
build/
dist/
coverage/
.DS_Store
*.log
.env*
!.env.example
+17 -8
View File
@@ -1,11 +1,20 @@
<div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
<h1>Built with AI Studio</h2>
<p>The fastest path from prompt to production with Gemini.</p>
<a href="https://aistudio.google.com/apps">Start building</a>
</div>
# 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/b6ca5081-8525-4b56-8fbc-92c242c9ae8a
## 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`
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Google AI Studio App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
{
"name": "AgentBlock",
"description": "A comprehensive scientific modeling and simulation platform featuring AI-assisted research, node-based visual editing, and real-time data analysis.",
"requestFramePermissions": []
}
+5104
View File
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
{
"name": "react-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --port=3000 --host=0.0.0.0",
"build": "vite build",
"preview": "vite preview",
"clean": "rm -rf dist",
"lint": "tsc --noEmit"
},
"dependencies": {
"@google/genai": "^1.29.0",
"@tailwindcss/vite": "^4.1.14",
"@vitejs/plugin-react": "^5.0.4",
"clsx": "^2.1.1",
"d3": "^7.9.0",
"dotenv": "^17.2.3",
"express": "^4.21.2",
"lucide-react": "^0.546.0",
"motion": "^12.37.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.13.1",
"recharts": "^3.8.0",
"tailwind-merge": "^3.5.0",
"vite": "^6.2.0"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^22.14.0",
"autoprefixer": "^10.4.21",
"tailwindcss": "^4.1.14",
"tsx": "^4.21.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"
}
}
+34
View File
@@ -0,0 +1,34 @@
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import Landing from './pages/Landing';
import Login from './pages/Login';
import SciHub from './pages/SciHub';
import OmniEditor from './pages/OmniEditor';
import SciDashboard from './pages/SciDashboard';
import SimuAnalysis from './pages/SimuAnalysis';
import { LanguageProvider } from './context/LanguageContext';
import LanguageSwitcher from './components/LanguageSwitcher';
export default function App() {
return (
<LanguageProvider>
<Router>
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/login" element={<Login />} />
<Route path="/hub" element={<SciHub />} />
<Route path="/editor" element={<OmniEditor />} />
<Route path="/dashboard" element={<SciDashboard />} />
<Route path="/analysis" element={<SimuAnalysis />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
<LanguageSwitcher />
</Router>
</LanguageProvider>
);
}
+26
View File
@@ -0,0 +1,26 @@
import React from 'react';
import { useLanguage } from '../context/LanguageContext';
import { Languages } from 'lucide-react';
import { motion } from 'motion/react';
const LanguageSwitcher: React.FC = () => {
const { language, setLanguage, t } = useLanguage();
const toggleLanguage = () => {
setLanguage(language === 'en' ? 'zh' : 'en');
};
return (
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={toggleLanguage}
className="fixed bottom-6 right-6 z-[100] bg-white text-slate-900 px-4 py-2 rounded-full shadow-xl border border-slate-200 flex items-center gap-2 font-medium hover:bg-slate-50 transition-colors"
>
<Languages className="w-4 h-4 text-indigo-600" />
<span>{language === 'en' ? '中文' : 'English'}</span>
</motion.button>
);
};
export default LanguageSwitcher;
+69
View File
@@ -0,0 +1,69 @@
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { LayoutGrid, Home, Edit3, Table, Activity, ChevronDown, Globe } from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
const ModuleSwitcher: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
const location = useLocation();
const modules = [
{ name: 'Landing', path: '/', icon: Globe, desc: 'Home' },
{ name: 'OmniEditor', path: '/editor', icon: Edit3, desc: 'Visual Modeling' },
{ name: 'SciDashboard', path: '/dashboard', icon: Table, desc: 'Parameters' },
{ name: 'SimuAnalysis', path: '/analysis', icon: Activity, desc: 'Simulation' },
{ name: 'SciHub', path: '/hub', icon: Home, desc: 'Knowledge Base' },
];
const currentModule = modules.find(m => m.path === location.pathname) || modules[0];
return (
<div className="relative z-[100]">
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 px-3 py-1.5 bg-white hover:bg-slate-50 rounded-xl border border-slate-200 shadow-sm transition-all text-slate-800 active:scale-95"
>
<LayoutGrid className="w-4 h-4 text-blue-600" />
<span className="text-sm font-black tracking-tight">{currentModule.name}</span>
<ChevronDown className={`w-3 h-3 text-slate-400 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
</button>
<AnimatePresence>
{isOpen && (
<>
<div className="fixed inset-0" onClick={() => setIsOpen(false)} />
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
className="absolute top-full left-0 mt-2 w-64 bg-white border border-slate-200 rounded-2xl shadow-2xl overflow-hidden p-2"
>
<div className="grid gap-1">
{modules.map((mod) => (
<Link
key={mod.path}
to={mod.path}
onClick={() => setIsOpen(false)}
className={`flex items-center gap-3 p-3 rounded-xl transition-all ${
location.pathname === mod.path
? 'bg-blue-600 text-white shadow-lg shadow-blue-500/20'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
}`}
>
<mod.icon className={`w-5 h-5 ${location.pathname === mod.path ? 'text-white' : 'text-slate-400'}`} />
<div>
<div className="text-sm font-black tracking-tight">{mod.name}</div>
<div className={`text-[10px] font-bold uppercase tracking-widest ${location.pathname === mod.path ? 'text-white/80' : 'text-slate-400'}`}>{mod.desc}</div>
</div>
</Link>
))}
</div>
</motion.div>
</>
)}
</AnimatePresence>
</div>
);
};
export default ModuleSwitcher;
+32
View File
@@ -0,0 +1,32 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
import { Language, translations } from '../translations';
interface LanguageContextType {
language: Language;
setLanguage: (lang: Language) => void;
t: (key: keyof typeof translations.en) => string;
}
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
export const LanguageProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [language, setLanguage] = useState<Language>('en');
const t = (key: keyof typeof translations.en): string => {
return translations[language][key] || translations.en[key] || key;
};
return (
<LanguageContext.Provider value={{ language, setLanguage, t }}>
{children}
</LanguageContext.Provider>
);
};
export const useLanguage = () => {
const context = useContext(LanguageContext);
if (context === undefined) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};
+30
View File
@@ -0,0 +1,30 @@
@import "tailwindcss";
@layer base {
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
}
.canvas-grid {
background-image: radial-gradient(#e2e8f0 1px, transparent 1px);
background-size: 20px 20px;
}
.vertical-text {
writing-mode: vertical-rl;
text-orientation: mixed;
transform: rotate(180deg);
}
+10
View File
@@ -0,0 +1,10 @@
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);
+148
View File
@@ -0,0 +1,148 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { ChevronRight, Activity, Globe, Zap, Shield } from 'lucide-react';
import { motion } from 'motion/react';
import { useLanguage } from '../context/LanguageContext';
const Landing: React.FC = () => {
const { t } = useLanguage();
const navigate = useNavigate();
return (
<div className="min-h-screen bg-white text-slate-900 font-sans selection:bg-indigo-100">
{/* Header */}
<header className="fixed top-0 left-0 right-0 bg-white/80 backdrop-blur-md z-50 border-b border-slate-100">
<div className="max-w-7xl mx-auto px-4 h-16 flex items-center justify-between">
<div className="flex items-center gap-8">
<div className="flex items-center gap-2 cursor-pointer" onClick={() => navigate('/')}>
<div className="w-8 h-8 bg-indigo-600 rounded flex items-center justify-center shadow-lg shadow-indigo-200">
<Activity className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-bold tracking-tight text-slate-800">AgentBlock</span>
</div>
<nav className="hidden lg:flex items-center gap-6 text-sm font-medium text-slate-600">
<a href="#" className="hover:text-indigo-600 transition-colors">{t('landing_product')}</a>
<a href="#" className="hover:text-indigo-600 transition-colors">{t('landing_enterprise')}</a>
<a href="#" className="hover:text-indigo-600 transition-colors">{t('landing_industries')}</a>
<a href="#" className="hover:text-indigo-600 transition-colors">{t('landing_solutions')}</a>
<a href="#" className="hover:text-indigo-600 transition-colors">{t('landing_pricing')}</a>
</nav>
</div>
<div className="flex items-center gap-4">
<button className="hidden md:block text-sm font-medium text-slate-600 hover:text-indigo-600 transition-colors">
{t('landing_contact_sales')}
</button>
<button
onClick={() => navigate('/login')}
className="text-sm font-medium text-slate-600 hover:text-indigo-600 transition-colors"
>
{t('landing_my_account')}
</button>
<button
onClick={() => navigate('/editor', { state: { fromLanding: true } })}
className="bg-amber-500 hover:bg-amber-400 text-slate-900 px-5 py-2 rounded-md text-sm font-bold flex items-center gap-2 transition-all shadow-lg shadow-amber-200 active:scale-95"
>
{t('landing_launch_app')}
<ChevronRight className="w-4 h-4" />
</button>
</div>
</div>
</header>
{/* Hero Section */}
<main className="pt-32 pb-20 relative overflow-hidden">
{/* Background Pattern */}
<div className="absolute inset-0 z-0 pointer-events-none opacity-[0.03]">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="100" height="100" patternUnits="userSpaceOnUse">
<path d="M 100 0 L 0 0 0 100" fill="none" stroke="currentColor" strokeWidth="1"/>
<rect width="20" height="20" x="40" y="40" fill="none" stroke="currentColor" strokeWidth="0.5" />
<line x1="60" y1="50" x2="100" y2="50" stroke="currentColor" strokeWidth="0.5" />
<line x1="50" y1="60" x2="50" y2="100" stroke="currentColor" strokeWidth="0.5" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
<div className="max-w-5xl mx-auto px-4 text-center relative z-10">
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-5xl md:text-7xl font-extrabold text-slate-900 mb-6 tracking-tight leading-tight"
>
{t('landing_hero_title')}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-xl md:text-2xl text-slate-500 mb-10 max-w-3xl mx-auto leading-relaxed"
>
{t('landing_hero_subtitle')}
</motion.p>
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
<button
onClick={() => navigate('/editor', { state: { fromLanding: true } })}
className="bg-amber-500 hover:bg-amber-400 text-slate-900 px-8 py-4 rounded-md text-lg font-bold flex items-center gap-2 mx-auto transition-all shadow-xl shadow-amber-200 active:scale-95 group"
>
{t('landing_cta')}
<ChevronRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
</button>
</motion.div>
</div>
{/* Feature Cards (Subtle) */}
<div className="max-w-7xl mx-auto px-4 mt-32 grid grid-cols-1 md:grid-cols-3 gap-8">
{[
{ icon: Globe, title: 'Global Collaboration', desc: 'Connect with researchers worldwide in real-time.' },
{ icon: Zap, title: 'High Performance', desc: 'Run complex simulations with our optimized VISA engine.' },
{ icon: Shield, title: 'Secure & Private', desc: 'Your research data is encrypted and protected.' }
].map((feature, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + i * 0.1 }}
className="p-8 rounded-2xl border border-slate-100 bg-white shadow-sm hover:shadow-md transition-shadow"
>
<div className="w-12 h-12 bg-indigo-50 rounded-xl flex items-center justify-center mb-6">
<feature.icon className="w-6 h-6 text-indigo-600" />
</div>
<h3 className="text-xl font-bold text-slate-800 mb-3">{feature.title}</h3>
<p className="text-slate-500 leading-relaxed">{feature.desc}</p>
</motion.div>
))}
</div>
</main>
{/* Footer */}
<footer className="bg-slate-50 border-t border-slate-200 py-12">
<div className="max-w-7xl mx-auto px-4 flex flex-col md:flex-row justify-between items-center gap-8">
<div className="flex items-center gap-2">
<Activity className="w-6 h-6 text-indigo-600" />
<span className="text-lg font-bold text-slate-800">AgentBlock</span>
</div>
<div className="flex gap-8 text-sm text-slate-500">
<a href="#" className="hover:text-indigo-600 transition-colors">Privacy Policy</a>
<a href="#" className="hover:text-indigo-600 transition-colors">Terms of Service</a>
<a href="#" className="hover:text-indigo-600 transition-colors">Cookie Policy</a>
</div>
<p className="text-sm text-slate-400">© 2026 AgentBlock Platform. All rights reserved.</p>
</div>
</footer>
</div>
);
};
export default Landing;
+158
View File
@@ -0,0 +1,158 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Activity, ArrowLeft, Github, Chrome } from 'lucide-react';
import { motion } from 'motion/react';
import { useLanguage } from '../context/LanguageContext';
const Login: React.FC = () => {
const { t } = useLanguage();
const navigate = useNavigate();
return (
<div className="min-h-screen bg-white flex font-sans">
{/* Left Side - Visual/Branding */}
<div className="hidden lg:flex lg:w-1/2 bg-slate-50 relative overflow-hidden items-center justify-center p-12 border-r border-slate-100">
<div className="absolute inset-0 opacity-40">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid-login" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#e2e8f0" strokeWidth="1"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid-login)" />
</svg>
</div>
<div className="relative z-10 max-w-lg text-slate-800">
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }}
className="w-16 h-16 bg-white shadow-xl shadow-blue-500/10 rounded-2xl flex items-center justify-center mb-8 border border-white"
>
<Activity className="w-10 h-10 text-blue-600" />
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
className="text-4xl font-extrabold mb-6 leading-tight text-slate-900 tracking-tight"
>
Empowering the next generation of scientific discovery.
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
className="text-slate-500 text-lg leading-relaxed mb-12 font-medium"
>
Join thousands of researchers using SciHub to build, simulate, and share complex scientific models in VISA format.
</motion.p>
<div className="grid grid-cols-2 gap-6">
<div className="p-5 bg-white rounded-2xl border border-slate-200 shadow-sm">
<div className="text-3xl font-black mb-1 text-slate-900">1.2M+</div>
<div className="text-slate-400 text-[10px] font-bold uppercase tracking-widest">Papers Indexed</div>
</div>
<div className="p-5 bg-white rounded-2xl border border-slate-200 shadow-sm">
<div className="text-3xl font-black mb-1 text-slate-900">50k+</div>
<div className="text-slate-400 text-[10px] font-bold uppercase tracking-widest">Active Models</div>
</div>
</div>
</div>
{/* Decorative elements */}
<div className="absolute -bottom-24 -left-24 w-64 h-64 bg-blue-100 rounded-full blur-3xl opacity-50"></div>
<div className="absolute -top-24 -right-24 w-64 h-64 bg-indigo-100 rounded-full blur-3xl opacity-30"></div>
</div>
{/* Right Side - Login Form */}
<div className="w-full lg:w-1/2 flex items-center justify-center p-8 bg-white">
<div className="w-full max-w-md">
<button
onClick={() => navigate('/')}
className="flex items-center gap-2 text-slate-400 hover:text-blue-600 transition-colors mb-12 group font-bold"
>
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
<span className="text-xs uppercase tracking-widest">{t('back_to_home')}</span>
</button>
<div className="mb-10">
<h1 className="text-4xl font-black text-slate-900 mb-3 tracking-tighter">{t('login_title')}</h1>
<p className="text-slate-500 font-medium">{t('login_subtitle')}</p>
</div>
<form className="space-y-6" onSubmit={(e) => e.preventDefault()}>
<div>
<label className="block text-xs font-bold text-slate-400 uppercase tracking-widest mb-2" htmlFor="email">
{t('email')}
</label>
<input
type="email"
id="email"
className="w-full px-4 py-3.5 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-4 focus:ring-blue-500/10 transition-all outline-none font-medium text-slate-900"
placeholder="name@university.edu"
/>
</div>
<div>
<div className="flex justify-between mb-2">
<label className="text-xs font-bold text-slate-400 uppercase tracking-widest" htmlFor="password">
{t('password')}
</label>
<a href="#" className="text-xs font-bold text-blue-600 hover:text-blue-700 tracking-tight">
{t('forgot_password')}
</a>
</div>
<input
type="password"
id="password"
className="w-full px-4 py-3.5 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-4 focus:ring-blue-500/10 transition-all outline-none font-medium text-slate-900"
placeholder="••••••••"
/>
</div>
<button
type="submit"
onClick={() => navigate('/editor')}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-black py-4 rounded-xl shadow-lg shadow-blue-500/20 transition-all active:scale-[0.98] uppercase tracking-widest text-sm"
>
{t('sign_in')}
</button>
</form>
<div className="relative my-10">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-slate-100"></div>
</div>
<div className="relative flex justify-center text-[10px] uppercase">
<span className="bg-white px-4 text-slate-400 font-black tracking-widest">{t('or_continue_with')}</span>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<button className="flex items-center justify-center gap-2 py-3.5 px-4 border border-slate-200 rounded-xl hover:bg-slate-50 transition-all font-bold text-slate-700 text-xs shadow-sm active:scale-95">
<Chrome className="w-4 h-4 text-slate-400" />
<span>GOOGLE</span>
</button>
<button className="flex items-center justify-center gap-2 py-3.5 px-4 border border-slate-200 rounded-xl hover:bg-slate-50 transition-all font-bold text-slate-700 text-xs shadow-sm active:scale-95">
<Github className="w-4 h-4 text-slate-400" />
<span>GITHUB</span>
</button>
</div>
<p className="mt-12 text-center text-xs text-slate-500 font-medium">
{t('no_account')}{' '}
<a href="#" className="font-bold text-blue-600 hover:text-blue-700 uppercase tracking-tight">
{t('sign_up')}
</a>
</p>
</div>
</div>
</div>
);
};
export default Login;
+924
View File
@@ -0,0 +1,924 @@
import React, { useState } from 'react';
import {
Play,
Settings,
Plus,
Minus,
Maximize,
Send,
Cpu,
Hexagon,
Book,
ChevronRight,
ChevronLeft,
Zap,
Terminal,
Users,
X,
MessageSquare,
PanelLeftClose,
PanelLeftOpen,
Upload,
Download,
CheckCircle,
LayoutGrid,
Share2,
Move,
Map,
Mic,
Trash2,
Menu,
ChevronDown,
Grid3X3,
Box,
Compass,
AlertCircle,
Link as LinkIcon,
ArrowRight
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import { Link, useLocation } from 'react-router-dom';
import { useLanguage } from '../context/LanguageContext';
import ModuleSwitcher from '../components/ModuleSwitcher';
interface Node {
id: string;
type: 'environment' | 'agent' | 'parameter' | 'space';
x: number;
y: number;
data: any;
}
interface Edge {
id: string;
source: string;
target: string;
direction: 'none' | 'forward' | 'backward' | 'both';
isDashed: boolean;
}
const OmniEditor: React.FC = () => {
const { t, language } = useLanguage();
const location = useLocation();
const [isAssistantCollapsed, setIsAssistantCollapsed] = useState(false);
const [selectedNode, setSelectedNode] = useState<string | null>(null);
const [selectedEdge, setSelectedEdge] = useState<string | null>(null);
const [showInitModal, setShowInitModal] = useState(!!location.state?.fromLanding);
const [selectedSpace, setSelectedSpace] = useState<string | null>('grid');
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
// Connection state
const [linkingFrom, setLinkingFrom] = useState<string | null>(null);
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
// Node dimension helper
const getNodeBounds = (node: Node) => {
const width = 256; // w-64
const height = node.type === 'environment' ? 200 : 100; // Estimated height for nodes
return {
left: node.x,
top: node.y,
width,
height,
centerX: node.x + width / 2,
centerY: node.y + height / 2
};
};
// Helper to calculate line between bounds
const getLineData = (sourceId: string, targetId: string) => {
const sourceNode = nodes.find(n => n.id === sourceId);
const targetNode = nodes.find(n => n.id === targetId);
if (!sourceNode || !targetNode) return null;
const s = getNodeBounds(sourceNode);
const t = getNodeBounds(targetNode);
// Simple connection between centers
// For a cleaner look, we can adjust these to be edge points
return { x1: s.centerX, y1: s.centerY, x2: t.centerX, y2: t.centerY };
};
// ABM Parameters State (from SciDashboard)
const [parameters, setParameters] = useState([
{ id: 1, name: 'agent_density', type: 'Float', dist: 'Constant', val: '0.75', min: 'N/A', max: 'N/A', desc: language === 'en' ? 'Initial population density' : '初始种群密度', error: false },
{ id: 2, name: 'interaction_radius', type: 'Integer', dist: 'Uniform', val: '--', min: '1', max: '10', desc: language === 'en' ? 'Missing initial value' : '缺少初始值', error: true },
{ id: 3, name: 'random_seed', type: 'Integer', dist: 'Constant', val: '42', min: 'N/A', max: 'N/A', desc: language === 'en' ? 'Reproducibility seed' : '可重复性种子', error: false },
]);
// New Node-Edge System State
const [nodes, setNodes] = useState<Node[]>(() => {
const initial: Node[] = [];
if (location.state?.fromLanding) {
initial.push({
id: 'env',
type: 'environment',
x: -250,
y: -100,
data: {
type: 'grid',
width: 40,
height: 40,
hWrap: true,
vWrap: true
}
});
}
return initial;
});
const [edges, setEdges] = useState<Edge[]>([]);
// Derived states for backward compatibility/quick access
const environment = nodes.find(n => n.type === 'environment')?.data || null;
const setEnvironment = (newData: any) => {
setNodes(prev => prev.map(n => n.id === 'env' ? { ...n, data: newData } : n));
};
const updateNodePos = (id: string, x: number, y: number) => {
setNodes(prev => prev.map(n => n.id === id ? { ...n, x, y } : n));
};
const handleCreateEdge = (targetId: string) => {
if (!linkingFrom || linkingFrom === targetId) return;
// Check if edge already exists
if (edges.some(e => (e.source === linkingFrom && e.target === targetId) || (e.source === targetId && e.target === linkingFrom))) {
setLinkingFrom(null);
return;
}
const newEdge: Edge = {
id: `edge-${Date.now()}`,
source: linkingFrom,
target: targetId,
direction: 'forward',
isDashed: true
};
setEdges(prev => [...prev, newEdge]);
setLinkingFrom(null);
};
const [messages, setMessages] = useState([
{
role: 'assistant',
content: language === 'en'
? "I've analyzed the Evolutionary Stable Strategy. Based on your inputs, I recommend adding a \"Cost of Sensing\" parameter to the Agent logic."
: "我分析了演化稳定策略。根据您的输入,我建议在代理逻辑中添加“感知成本”参数。"
}
]);
const [input, setInput] = useState('');
const [showToast, setShowToast] = useState<string | null>(null);
const handleAction = (name: string) => {
setShowToast(name);
setTimeout(() => setShowToast(null), 3000);
// Add logic for "Add Agent" to Node system
if (name === t('add_agent')) {
const id = `agent-${Date.now()}`;
setNodes(prev => [...prev, { id, type: 'agent', x: 200, y: 150, data: {} }]);
}
};
const handleSend = () => {
if (!input.trim()) return;
const userMsg = input.trim();
setMessages([...messages, { role: 'user', content: userMsg }]);
setInput('');
setTimeout(() => {
let aiContent = language === 'en' ? "Logic updated." : "逻辑已更新。";
// Basic pattern recognition for creating space
if (userMsg.toLowerCase().includes('grid') || userMsg.includes('网格')) {
const matches = userMsg.match(/(\d+)\D+(\d+)/);
const w = matches ? parseInt(matches[1]) : 40;
const h = matches ? parseInt(matches[2]) : 40;
setEnvironment({
type: 'grid',
width: w,
height: h,
hWrap: true,
vWrap: true
});
aiContent = language === 'en'
? `Created a ${w}x${h} grid space for your model.`
: `已为您创建了 ${w}x${h} 的网格空间。`;
}
setMessages(prev => [...prev, {
role: 'assistant',
content: aiContent
}]);
}, 800);
};
return (
<div className="bg-white text-slate-900 h-screen flex flex-col overflow-hidden font-sans relative">
<AnimatePresence>
{showToast && (
<motion.div
initial={{ opacity: 0, y: 50, x: '-50%' }}
animate={{ opacity: 1, y: 0, x: '-50%' }}
exit={{ opacity: 0, y: 50, x: '-50%' }}
className="fixed bottom-8 left-1/2 z-[100] bg-white text-slate-900 px-6 py-3 rounded-xl shadow-2xl border border-slate-200 flex items-center gap-3"
>
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
<span className="text-sm font-medium">"{showToast}" {t('coming_soon')}</span>
</motion.div>
)}
</AnimatePresence>
<AnimatePresence>
{showInitModal && (
<div className="fixed inset-0 z-[200] flex items-center justify-center p-4">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 bg-white/60 backdrop-blur-md"
onClick={() => setShowInitModal(false)}
/>
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
className="relative w-full max-w-2xl bg-white border border-slate-200 rounded-3xl shadow-2xl overflow-hidden"
>
<div className="p-8 border-b border-slate-100">
<h2 className="text-2xl font-bold text-slate-900 mb-2">{t('env_modal_title')}</h2>
<p className="text-slate-500 text-sm">{t('env_modal_subtitle')}</p>
</div>
<div className="p-8 grid grid-cols-2 gap-4">
{[
{ id: 'grid', icon: LayoutGrid, title: t('env_grid'), desc: t('env_grid_desc'), color: 'text-blue-600', bg: 'bg-blue-50' },
{ id: 'network', icon: Share2, title: t('env_network'), desc: t('env_network_desc'), color: 'text-purple-600', bg: 'bg-purple-50' },
{ id: 'continuous', icon: Move, title: t('env_continuous'), desc: t('env_continuous_desc'), color: 'text-green-600', bg: 'bg-green-50' },
{ id: 'gis', icon: Map, title: t('env_gis'), desc: t('env_gis_desc'), color: 'text-amber-600', bg: 'bg-amber-50' }
].map((item) => (
<button
key={item.id}
onClick={() => setSelectedSpace(item.id)}
className={`flex flex-col items-start p-4 rounded-2xl border-2 text-left transition-all ${
selectedSpace === item.id
? 'border-blue-500 bg-blue-50 ring-4 ring-blue-500/10'
: 'border-slate-100 hover:border-slate-200 bg-slate-50'
}`}
>
<div className={`p-2 rounded-xl mb-4 ${item.bg} ${item.color}`}>
<item.icon className="w-6 h-6" />
</div>
<h3 className="font-bold mb-1 text-slate-900">{item.title}</h3>
<p className="text-[11px] text-slate-500 leading-tight">{item.desc}</p>
</button>
))}
</div>
<div className="p-8 bg-slate-50 flex items-center justify-between">
<button onClick={() => setShowInitModal(false)} className="text-sm font-medium text-slate-500 hover:text-slate-900">{t('env_skip')}</button>
<button
onClick={() => {
setEnvironment({
type: selectedSpace || 'grid',
width: 40,
height: 40,
hWrap: true,
vWrap: true
});
setShowInitModal(false);
handleAction(language === 'en' ? `Initialized ${selectedSpace}` : `已初始化 ${selectedSpace}`);
}}
className="px-8 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-xl font-bold transition-all shadow-lg shadow-blue-500/20"
>
{t('env_confirm')}
</button>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
<header className="border-b border-slate-200 bg-white flex flex-col z-50 shrink-0">
{/* Top Row: Context & System */}
<div className="h-10 flex items-center justify-between px-4 border-b border-slate-100 text-slate-500">
<div className="flex items-center gap-3">
<ModuleSwitcher />
<div className="h-4 w-px bg-slate-200 mx-1"></div>
<div className="flex items-center gap-2 px-2 py-1 bg-blue-50 text-blue-600 rounded-lg border border-blue-100 text-[10px] font-bold">
<Cpu className="w-3 h-3" />
</div>
<div className="flex items-center gap-2 px-2 py-1 bg-slate-50 rounded-md border border-slate-200 text-xs text-slate-700 font-bold cursor-pointer hover:bg-slate-100 transition-colors">
<span className="opacity-50 font-medium">Schema:</span>
<span>Evolutionary Stable Strategy</span>
<ChevronDown className="w-3 h-3 text-slate-400" />
</div>
</div>
<div className="flex items-center gap-4">
<span className="px-2 py-0.5 bg-emerald-50 text-emerald-600 rounded border border-emerald-100 text-[9px] flex items-center gap-1 font-bold">
<span className="w-1 h-1 bg-emerald-500 rounded-full animate-pulse"></span> {t('visa_connected')}
</span>
</div>
</div>
{/* Bottom Row: ABM Workbench Menus */}
<div className="h-9 flex items-center px-4 gap-2 text-[11px] text-slate-500 font-medium uppercase tracking-wider bg-slate-50/50">
{[
{ label: language === 'en' ? 'Model(M)' : '模型(M)' },
{ label: language === 'en' ? 'Space(S)' : '空间(S)' },
{ label: language === 'en' ? 'Agents(A)' : '代理(A)' },
{ label: language === 'en' ? 'Logic(L)' : '交互(L)' },
{ label: language === 'en' ? 'Data(D)' : '数据(D)' }
].map((item, idx) => (
<button
key={idx}
className={`hover:text-blue-600 hover:bg-white px-3 py-1.5 rounded-lg transition-all ${idx === 0 ? 'text-blue-600 bg-white shadow-sm' : ''}`}
onClick={() => handleAction(item.label)}
>
{item.label}
</button>
))}
<div className="flex-1"></div>
<div className="flex items-center gap-2">
<button
onClick={() => handleAction('Setup Simulation')}
className="flex items-center gap-1.5 bg-white hover:bg-slate-50 text-slate-600 border border-slate-200 px-3 py-1 rounded-lg text-[10px] font-bold transition-all"
>
Setup
</button>
<button
onClick={() => handleAction(t('run_model'))}
className="flex items-center gap-1.5 bg-blue-600 hover:bg-blue-700 text-white px-4 py-1 rounded-lg text-[10px] font-bold shadow-lg shadow-blue-500/20 transition-all active:scale-95"
>
<Play className="w-3 h-3 fill-current" /> {t('run_model')}
</button>
</div>
</div>
</header>
<div className="flex flex-1 overflow-hidden">
<main className="flex-1 flex overflow-hidden">
{/* AI Assistant */}
<motion.section
animate={{ width: isAssistantCollapsed ? '48px' : '25%' }}
className="flex flex-col border-r border-slate-200 bg-slate-50 relative overflow-hidden"
>
<header className="p-3 border-b border-slate-200 bg-white flex justify-between items-center min-h-[56px]">
{!isAssistantCollapsed && <span className="text-[10px] font-bold uppercase tracking-[0.2em] text-slate-400">{t('ai_assistant')}</span>}
<button
onClick={() => setIsAssistantCollapsed(!isAssistantCollapsed)}
className="p-1.5 hover:bg-slate-100 rounded-md text-slate-400 transition-colors"
>
{isAssistantCollapsed ? <PanelLeftOpen className="w-4 h-4" /> : <PanelLeftClose className="w-4 h-4" />}
</button>
</header>
{!isAssistantCollapsed && (
<>
<div className="flex-1 overflow-y-auto p-4 space-y-6 text-sm">
{messages.map((msg, i) => (
<div key={i} className={`flex flex-col gap-1 ${msg.role === 'user' ? 'items-end' : ''}`}>
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">{msg.role === 'user' ? t('user') : t('ai_researcher')}</span>
<div className={`p-3 border shadow-sm ${msg.role === 'assistant' ? 'bg-white border-slate-200 text-slate-600 rounded-2xl rounded-tl-none' : 'bg-blue-600 text-white border-blue-500 rounded-2xl rounded-tr-none'}`}>
{msg.content}
</div>
</div>
))}
</div>
<div className="p-4 border-t border-slate-200 bg-white">
<div className="relative">
<textarea
className="w-full bg-slate-50 border border-slate-200 rounded-2xl p-4 pr-16 text-sm text-slate-700 focus:ring-4 focus:ring-blue-500/10 focus:border-blue-500 outline-none resize-none transition-all shadow-inner"
rows={2}
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Simulate: 'Create agents'..."
/>
<div className="absolute bottom-3 right-3 flex gap-2">
<button onClick={handleSend} className="p-2.5 bg-blue-600 rounded-xl text-white shadow-lg shadow-blue-500/30 hover:bg-blue-700 transition-all active:scale-95">
<Send className="w-4 h-4" />
</button>
</div>
</div>
</div>
</>
)}
</motion.section>
{/* Visual Canvas Area */}
<section className="flex-1 flex flex-col relative bg-white overflow-hidden">
<header className="p-3 border-b border-slate-200 bg-white/80 backdrop-blur-md flex justify-between items-center z-10">
<div className="flex items-center gap-2">
<span className="p-1.5 bg-blue-50 text-blue-600 rounded-lg border border-blue-100"><Hexagon className="w-3.5 h-3.5" /></span>
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-slate-500">Schema Designer</span>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => { handleAction(t('add_agent')); }}
className="px-4 py-1.5 bg-white hover:bg-slate-50 text-blue-600 text-[10px] font-bold rounded-lg border border-slate-200 shadow-sm transition-all uppercase flex items-center gap-2 active:scale-95"
>
<Plus className="w-3.5 h-3.5" /> {t('add_agent')}
</button>
<button
onClick={() => { setShowInitModal(true); handleAction(t('add_space')); }}
className="px-4 py-1.5 bg-white hover:bg-slate-50 text-amber-600 text-[10px] font-bold rounded-lg border border-slate-200 shadow-sm transition-all uppercase flex items-center gap-2 active:scale-95"
>
<Plus className="w-3.5 h-3.5" /> {t('add_space')}
</button>
</div>
</header>
<div
className="flex-1 relative overflow-hidden flex items-center justify-center p-12 bg-white"
style={{
backgroundImage: 'radial-gradient(rgba(0,0,0,0.03) 1px, transparent 1.5px)',
backgroundSize: '32px 32px'
}}
onMouseMove={(e) => {
if (linkingFrom) {
const rect = e.currentTarget.getBoundingClientRect();
setMousePos({
x: e.clientX - rect.left - rect.width / 2,
y: e.clientY - rect.top - rect.height / 2
});
}
}}
onClick={() => { setSelectedNode(null); setSelectedEdge(null); setLinkingFrom(null); }}
>
{/* Connection Layer (SVG) - Orthogonal Paths */}
<div className="absolute inset-0 flex items-center justify-center pointer-events-none overflow-visible">
<svg className="w-full h-full overflow-visible z-0">
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b" />
</marker>
<marker id="arrowhead-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#3B82F6" />
</marker>
</defs>
{/* Active Linking Preview (Straight for preview) */}
{linkingFrom && (
<line
x1={nodes.find(n => n.id === linkingFrom)?.x! + 128}
y1={nodes.find(n => n.id === linkingFrom)?.y!}
x2={mousePos.x}
y2={mousePos.y}
stroke="#3B82F6"
strokeWidth="2"
strokeDasharray="5,5"
className="opacity-50"
/>
)}
{edges.map(edge => {
const source = nodes.find(n => n.id === edge.source);
const target = nodes.find(n => n.id === edge.target);
if (!source || !target) return null;
const isSelected = selectedEdge === edge.id;
// Orthogonal Path Calculation
const x1 = source.x + 128;
const y1 = source.y;
const x2 = target.x - 128;
const y2 = target.y;
const midX = x1 + (x2 - x1) * 0.5;
const path = `M ${x1} ${y1} L ${midX} ${y2} L ${x2} ${y2}`;
return (
<g key={edge.id} className="pointer-events-auto cursor-pointer" onClick={(e) => { e.stopPropagation(); setSelectedEdge(edge.id); setSelectedNode(null); }}>
<path
d={path}
stroke="transparent"
strokeWidth="24"
fill="none"
/>
<motion.path
initial={false}
animate={{ d: path }}
stroke={isSelected ? "#3B82F6" : "#64748b"}
strokeWidth={isSelected ? "3" : "2"}
strokeDasharray={edge.isDashed ? "8,5" : "none"}
fill="none"
markerEnd={edge.direction === 'forward' || edge.direction === 'both' ? (isSelected ? "url(#arrowhead-blue)" : "url(#arrowhead)") : ""}
className="transition-colors duration-200"
/>
</g>
);
})}
</svg>
</div>
{nodes.map(node => (
<motion.div
key={node.id}
drag
dragMomentum={false}
dragElastic={0}
onDrag={(e, info) => {
updateNodePos(node.id, node.x + info.delta.x, node.y + info.delta.y);
}}
layout
initial={false}
animate={{
x: node.x,
y: node.y,
scale: selectedNode === node.id ? 1.02 : 1,
zIndex: selectedNode === node.id ? 40 : 10,
}}
whileDrag={{ cursor: 'grabbing', zIndex: 50 }}
transition={{ type: 'spring', damping: 25, stiffness: 300, mass: 0.1 }}
onClick={(e) => {
e.stopPropagation();
if (linkingFrom) {
handleCreateEdge(node.id);
} else {
setSelectedNode(node.id);
setSelectedEdge(null);
}
}}
className={`absolute w-64 bg-white border border-slate-200 rounded-xl z-20 cursor-grab active:cursor-grabbing shadow-xl ${selectedNode === node.id ? 'ring-2 ring-blue-500' : ''}`}
>
{/* Node Header */}
<div className={`px-4 py-2.5 rounded-t-xl border-b border-slate-100 flex items-center justify-between bg-slate-50`}>
<div className="flex items-center gap-2">
{node.type === 'environment' ? <LayoutGrid className="w-3.5 h-3.5 text-amber-600" /> : <Cpu className="w-3.5 h-3.5 text-blue-600" />}
<span className="text-[10px] font-black uppercase tracking-widest text-slate-700 whitespace-nowrap overflow-hidden text-ellipsis">
{node.id === 'env' ? 'Environment_Space' : (node.id === 'agent' ? 'Core_Agent' : node.id.split('-')[0].toUpperCase())}
</span>
</div>
<div className="flex items-center gap-1.5">
<button
onClick={(e) => {
e.stopPropagation();
setNodes(prev => prev.filter(n => n.id !== node.id));
setEdges(prev => prev.filter(edge => edge.source !== node.id && edge.target !== node.id));
if (selectedNode === node.id) setSelectedNode(null);
}}
className="p-1 hover:bg-red-50 text-slate-400 hover:text-red-500 rounded-md transition-all"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</div>
{/* Node Content - List Style */}
<div className="p-3 space-y-1">
{node.type === 'environment' ? (
<>
<div className="flex items-center justify-between text-[9px] px-2 py-1.5 rounded hover:bg-slate-50 transition-colors group">
<div className="flex items-center gap-2 text-slate-500 uppercase font-bold">
<Grid3X3 className="w-3 h-3 text-slate-300 group-hover:text-amber-500 transition-colors" />
<span>Dimensions</span>
</div>
<span className="text-slate-600 font-mono italic">{node.data.width} × {node.data.height}</span>
</div>
<div className="flex items-center justify-between text-[9px] px-2 py-1.5 rounded hover:bg-slate-50 transition-colors group">
<div className="flex items-center gap-2 text-slate-500 uppercase font-bold">
<Move className="w-3 h-3 text-slate-300 group-hover:text-blue-500 transition-colors" />
<span>Boundary</span>
</div>
<span className="text-blue-600/80 font-mono font-bold">{node.data.hWrap ? 'Toroidal' : 'Hard'}</span>
</div>
</>
) : (
<>
<div className="flex items-center justify-between text-[9px] px-2 py-1.5 rounded hover:bg-slate-50 transition-colors group">
<div className="flex items-center gap-2 text-slate-500 uppercase font-bold">
<CheckCircle className="w-3 h-3 text-slate-300 group-hover:text-emerald-500 transition-colors" />
<span>Health_Initial</span>
</div>
<span className="text-slate-600 font-mono">100.0</span>
</div>
<div className="flex items-center justify-between text-[9px] px-2 py-1.5 rounded hover:bg-slate-50 transition-colors group">
<div className="flex items-center gap-2 text-slate-500 uppercase font-bold">
<Zap className="w-3 h-3 text-slate-300 group-hover:text-purple-500 transition-colors" />
<span>Energy_Sensing</span>
</div>
<span className="text-slate-600 font-mono">0.05</span>
</div>
<div className="flex items-center justify-between text-[9px] px-2 py-1.5 rounded hover:bg-slate-50 transition-colors group">
<div className="flex items-center gap-2 text-slate-500 uppercase font-bold">
<Users className="w-3 h-3 text-slate-300 group-hover:text-blue-500 transition-colors" />
<span>Generation</span>
</div>
<span className="text-slate-600 font-mono">v1.2.4</span>
</div>
</>
)}
</div>
{/* Ports / Connection Handles */}
<div className="absolute -left-1.5 top-1/2 -translate-y-1/2 w-3 h-3 bg-white border border-slate-300 rounded-full cursor-crosshair hover:bg-blue-500 transition-colors" />
<button
onClick={(e) => { e.stopPropagation(); setLinkingFrom(node.id); }}
className={`absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded border bg-white shadow-md flex items-center justify-center transition-all z-30 ${linkingFrom === node.id ? 'border-blue-500 text-blue-500 animate-pulse' : 'border-slate-200 text-slate-400 hover:border-blue-400 hover:text-blue-400'}`}
>
<LinkIcon className="w-3 h-3" />
</button>
</motion.div>
))}
{nodes.length === 0 && (
<div className="text-slate-700 pointer-events-none select-none">
<div className="text-center font-black text-[9px] tracking-[1em] uppercase opacity-20">
Scientific Ledger Empty // Define System Nodes
</div>
</div>
)}
{/* Canvas Controls */}
<div className="absolute bottom-8 right-8 flex flex-col items-center bg-white border border-slate-200 rounded-xl shadow-2xl p-1 z-50">
<button className="p-2.5 text-slate-400 hover:text-slate-900 transition-all active:scale-90" onClick={() => handleAction('Zoom In')}>
<Plus className="w-4 h-4" />
</button>
<button className="p-2.5 text-slate-400 hover:text-slate-900 transition-all active:scale-90" onClick={() => handleAction('Zoom Out')}>
<Minus className="w-4 h-4" />
</button>
</div>
</div>
</section>
{/* Properties Panel Overlay */}
<AnimatePresence>
{(selectedNode || selectedEdge) && (
<motion.section
initial={{ x: '100%', opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: '100%', opacity: 0 }}
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
className="absolute right-0 top-0 bottom-0 w-80 md:w-96 flex flex-col bg-white border-l border-slate-200 shadow-2xl z-40"
>
<header className="p-3 border-b border-slate-100 bg-slate-50/50 flex justify-between items-center shrink-0">
<div className="flex items-center gap-2 font-bold text-[10px] uppercase tracking-[0.2em] text-slate-400">
<Settings className="w-3.5 h-3.5" /> {t('properties_panel')}
</div>
<div className="flex items-center gap-2">
<button
onClick={() => { setSelectedNode(null); setSelectedEdge(null); }}
className="p-1.5 hover:bg-slate-100 rounded-lg text-slate-400 transition-colors"
>
<X className="w-4 h-4" />
</button>
</div>
</header>
<div className="flex-1 overflow-y-auto p-4 space-y-6">
{selectedEdge && (
<div className="space-y-6">
<div className="pb-4 border-b border-slate-100 flex items-center justify-between">
<h3 className="text-xl font-bold text-slate-900 uppercase tracking-tight">{language === 'en' ? 'Connection' : '连线设置'}</h3>
<button
onClick={() => { setEdges(prev => prev.filter(e => e.id !== selectedEdge)); setSelectedEdge(null); }}
className="p-2 bg-red-50 text-red-500 rounded-xl hover:bg-red-100 transition-colors"
>
<Trash2 className="w-5 h-5" />
</button>
</div>
<div className="space-y-4">
<h4 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{language === 'en' ? 'Flow Direction' : '流动方向'}</h4>
<div className="grid grid-cols-2 gap-2">
{[
{ id: 'forward', label: 'Forward' },
{ id: 'backward', label: 'Reverse' },
{ id: 'both', label: 'Bi-directional' },
{ id: 'none', label: 'Passive' }
].map(dir => (
<button
key={dir.id}
onClick={() => setEdges(prev => prev.map(e => e.id === selectedEdge ? { ...e, direction: dir.id as any } : e))}
className={`px-3 py-2 rounded-xl text-xs font-bold border transition-all ${edges.find(e => e.id === selectedEdge)?.direction === dir.id ? 'bg-blue-600 text-white border-blue-600 shadow-md' : 'bg-slate-50 text-slate-500 border-slate-200 hover:bg-white shadow-sm'}`}
>
{dir.label}
</button>
))}
</div>
</div>
<div className="space-y-4">
<h4 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Line Style</h4>
<label className="flex items-center justify-between p-3 bg-slate-50 border border-slate-200 rounded-xl cursor-pointer">
<span className="text-xs text-slate-600 font-medium">Dashed Line</span>
<input
type="checkbox"
checked={edges.find(e => e.id === selectedEdge)?.isDashed}
onChange={(e) => setEdges(prev => prev.map(edge => edge.id === selectedEdge ? { ...edge, isDashed: e.target.checked } : edge))}
className="w-4 h-4 rounded text-blue-600 bg-white border-slate-300"
/>
</label>
</div>
</div>
)}
{selectedNode === 'env' && environment && (
<div className="space-y-6">
<div className="pb-4 border-b border-slate-100 flex items-center justify-between">
<div>
<h3 className="text-xl font-bold text-slate-900 uppercase tracking-tight">{environment.type === 'grid' ? t('env_grid') : t('env_settings')}</h3>
<p className="text-[10px] text-slate-400 mt-1 font-mono uppercase tracking-widest">ID: SPACE_01</p>
</div>
<div className="p-2 bg-blue-50 text-blue-600 rounded-xl">
<Grid3X3 className="w-6 h-6" />
</div>
</div>
<div className="space-y-4">
<h4 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{t('env_dim')}</h4>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-[10px] text-slate-400 font-bold uppercase">{t('env_width')}</label>
<input
type="number"
value={environment.width}
onChange={(e) => setEnvironment({...environment, width: parseInt(e.target.value)||0})}
className="w-full bg-slate-50 border border-slate-200 p-2 text-sm text-blue-600 font-bold rounded-lg outline-none focus:ring-2 focus:ring-blue-500 transition-all font-mono"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] text-slate-400 font-bold uppercase">{t('env_height')}</label>
<input
type="number"
value={environment.height}
onChange={(e) => setEnvironment({...environment, height: parseInt(e.target.value)||0})}
className="w-full bg-slate-50 border border-slate-200 p-2 text-sm text-blue-600 font-bold rounded-lg outline-none focus:ring-2 focus:ring-blue-500 transition-all font-mono"
/>
</div>
</div>
</div>
<div className="space-y-4">
<h4 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{t('env_connections')}</h4>
<label className="flex items-center justify-between p-3 bg-slate-50 border border-slate-200 rounded-xl cursor-pointer hover:bg-white transition-colors shadow-sm">
<span className="text-xs text-slate-600 font-medium">{t('env_horizontal_wrap')}</span>
<input type="checkbox" checked={environment.hWrap} onChange={(e) => setEnvironment({...environment, hWrap: e.target.checked})} className="w-4 h-4 rounded text-blue-600 bg-white border-slate-300" />
</label>
<label className="flex items-center justify-between p-3 bg-slate-50 border border-slate-200 rounded-xl cursor-pointer hover:bg-white transition-colors shadow-sm">
<span className="text-xs text-slate-600 font-medium">{t('env_vertical_wrap')}</span>
<input type="checkbox" checked={environment.vWrap} onChange={(e) => setEnvironment({...environment, vWrap: e.target.checked})} className="w-4 h-4 rounded text-blue-600 bg-white border-slate-300" />
</label>
</div>
<div className="p-4 bg-blue-50 border border-blue-100 rounded-2xl">
<div className="flex gap-3">
<AlertCircle className="w-5 h-5 text-blue-500 shrink-0" />
<div>
<h5 className="text-[10px] font-bold text-blue-800 uppercase tracking-wide">Optimization Tip</h5>
<p className="text-[11px] text-slate-600 mt-1 leading-relaxed font-medium">
{language === 'en'
? 'Grid wrapping (toroidal) simulates infinite space, preventing edge-case biases in segregation models.'
: '开启环绕(环面拓扑)可以模拟无限空间,防止隔离模型中的边界偏差效应。'}
</p>
</div>
</div>
</div>
</div>
)}
{selectedNode === 'agent' && (
<div className="space-y-6">
<div className="pb-4 border-b border-slate-100">
<h3 className="text-xl font-bold text-slate-900 uppercase tracking-tight">Agent: Core_Model</h3>
</div>
{/* Detailed Parameters Table from SciDashboard */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h4 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{t('variable_params')}</h4>
<button onClick={() => handleAction(t('add_row'))} className="p-1 text-blue-600 hover:bg-blue-50 rounded transition-all"><Plus className="w-3.5 h-3.5" /></button>
</div>
<div className="bg-white border border-slate-200 rounded-xl overflow-hidden shadow-sm">
<div className="overflow-x-auto">
<table className="w-full text-[11px] border-collapse">
<thead className="bg-slate-50 border-b border-slate-100">
<tr>
<th className="p-2 text-left text-slate-400 font-bold uppercase tracking-wider">{t('variable_name')}</th>
<th className="p-2 text-left text-slate-400 font-bold uppercase tracking-wider">{t('initial_value')}</th>
<th className="p-2 text-center text-slate-400 font-bold uppercase tracking-wider"><Trash2 className="w-3 h-3 mx-auto" /></th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{parameters.map((param) => (
<React.Fragment key={param.id}>
<tr className={`hover:bg-slate-50 transition-colors ${param.error ? 'bg-red-50/30' : ''}`}>
<td className="p-2 font-mono text-blue-600 font-bold">{param.name}</td>
<td className={`p-2 font-bold ${param.error ? 'text-red-500' : 'text-slate-700'}`}>{param.val}</td>
<td className="p-2 text-center">
<button className="text-slate-300 hover:text-red-500 transition-colors"><X className="w-3 h-3" /></button>
</td>
</tr>
{param.error && (
<tr>
<td colSpan={3} className="p-2 pt-0">
<div className="flex items-center gap-1 text-[9px] text-red-500 font-bold bg-red-100/30 p-1 rounded">
<AlertCircle className="w-2.5 h-2.5" />
{param.desc}
</div>
</td>
</tr>
)}
</React.Fragment>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* Logic Validation Section from SciDashboard */}
<div className="space-y-4">
<h4 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{t('logic_validation')}</h4>
<div className="space-y-3">
<div className="p-3 bg-red-50 border border-red-100 rounded-xl">
<div className="flex items-start gap-2">
<AlertCircle className="w-3.5 h-3.5 text-red-600 mt-0.5" />
<div className="flex-1">
<p className="text-[10px] font-bold text-red-800 uppercase tracking-wide">{t('missing_value')}</p>
<p className="text-[11px] text-red-700 mt-0.5 leading-tight">Variable <span className="font-mono font-bold">interaction_radius</span> requires an initial value.</p>
</div>
</div>
</div>
<div className="p-3 bg-amber-50 border border-amber-100 rounded-xl">
<div className="flex items-start gap-2">
<AlertCircle className="w-3.5 h-3.5 text-amber-600 mt-0.5" />
<div className="flex-1">
<p className="text-[10px] font-bold text-amber-800 uppercase tracking-wide">{t('type_mismatch')}</p>
<p className="text-[11px] text-amber-700 mt-0.5 leading-tight">Potential float-to-int conversion detected in logic flow.</p>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</div>
</motion.section>
)}
</AnimatePresence>
</main>
</div>
{/* Bottom Drawer (Code Preview) - Merged from SciDashboard */}
<div
className={`absolute bottom-6 left-0 right-0 bg-white text-slate-800 z-40 transition-all duration-300 border-t border-slate-200 shadow-[0_-10px_40px_rgba(0,0,0,0.05)] ${isDrawerOpen ? 'translate-y-0 h-[280px]' : 'translate-y-[240px] h-[280px]'}`}
>
<div
className="flex items-center justify-between px-6 py-2 bg-slate-50 cursor-pointer border-b border-slate-100 group"
onClick={() => setIsDrawerOpen(!isDrawerOpen)}
>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2 text-xs font-bold text-slate-500 uppercase tracking-widest">
<Terminal className="w-3.5 h-3.5 text-blue-600" />
{t('agentpy_preview')}
</div>
<span className="px-2 py-0.5 rounded text-[9px] bg-green-50 text-green-600 border border-green-200 font-bold uppercase tracking-tighter">{t('auto_generated')}</span>
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-4 opacity-0 group-hover:opacity-100 transition-opacity">
<button className="text-[10px] font-bold text-slate-400 hover:text-blue-600 transition-colors" onClick={(e) => { e.stopPropagation(); handleAction('Download Code'); }}>DOWNLOAD</button>
<button className="text-[10px] font-bold text-blue-600 hover:text-blue-700 transition-colors" onClick={(e) => { e.stopPropagation(); handleAction(t('copy_code')); }}>{t('copy_code')}</button>
</div>
<ChevronDown className={`h-4 w-4 text-slate-400 transition-transform ${isDrawerOpen ? '' : 'rotate-180'}`} />
</div>
</div>
<div className="p-6 font-mono text-xs overflow-auto h-full pb-12 bg-[#FAFBFC]">
<pre className="text-blue-700 leading-relaxed">
<span className="text-purple-600 italic"># AgentPy Model Implementation</span>{'\n'}
import <span className="text-slate-900 font-bold">agentpy</span> as ap{'\n\n'}
exp_parameters = {'{'}{'\n'}
{parameters.map(p => (
<React.Fragment key={p.id}>
{' '}<span className="text-amber-700">'{p.name}'</span>: <span className={p.error ? 'text-red-500 font-bold' : 'text-slate-900 font-bold'}>{p.val === '--' ? 'None' : p.val}</span>,{'\n'}
</React.Fragment>
))}
{'}'}{'\n\n'}
class <span className="text-slate-900 font-bold underline decoration-blue-500 underline-offset-4 decoration-2">ScientificModel</span>(ap.Model):{'\n'}
{' '}def setup(self):{'\n'}
{' '}self.agents = ap.AgentList(self, self.p.agent_density * 100){'\n'}
{' '}pass{'\n\n'}
{' '}def step(self):{'\n'}
{' '}pass
</pre>
</div>
</div>
<footer className="h-6 bg-white border-t border-slate-200 px-4 flex items-center justify-between text-[10px] text-slate-400 relative z-50">
<div className="flex items-center gap-4">
<span className="flex items-center gap-1.5"><span className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span> {language === 'en' ? 'System Stable' : '系统稳定'}</span>
</div>
<div className="flex items-center gap-3">
<span>OmniEditor v2.4</span>
</div>
</footer>
</div>
);
};
export default OmniEditor;
+243
View File
@@ -0,0 +1,243 @@
import React, { useState } from 'react';
import {
FileText,
Upload,
Database,
Download,
Plus,
Trash2,
CheckCircle,
AlertCircle,
Activity,
ChevronDown,
Copy,
X
} from 'lucide-react';
import { Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'motion/react';
import { useLanguage } from '../context/LanguageContext';
import ModuleSwitcher from '../components/ModuleSwitcher';
const SciDashboard: React.FC = () => {
const { t, language } = useLanguage();
const [isDrawerOpen, setIsDrawerOpen] = useState(true);
const [showToast, setShowToast] = useState<string | null>(null);
const handleAction = (name: string) => {
setShowToast(name);
setTimeout(() => setShowToast(null), 3000);
};
return (
<div className="bg-gray-50 font-sans text-gray-900 h-screen flex flex-col overflow-hidden relative">
{/* Toast Notification */}
<AnimatePresence>
{showToast && (
<motion.div
initial={{ opacity: 0, y: 50, x: '-50%' }}
animate={{ opacity: 1, y: 0, x: '-50%' }}
exit={{ opacity: 0, y: 50, x: '-50%' }}
className="fixed bottom-8 left-1/2 z-[100] bg-white text-slate-900 px-6 py-3 rounded-full shadow-2xl border border-slate-200 flex items-center gap-3"
>
<div className="w-2 h-2 bg-blue-600 rounded-full animate-pulse" />
<span className="text-sm font-medium">"{showToast}" {t('coming_soon')}</span>
<button onClick={() => setShowToast(null)} className="ml-2 hover:text-blue-600 transition-colors">
<X className="w-4 h-4" />
</button>
</motion.div>
)}
</AnimatePresence>
{/* Top Navigation Bar */}
<header className="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-6 z-30 relative shrink-0">
<div className="flex items-center gap-4">
<ModuleSwitcher />
<div className="h-6 w-px bg-gray-200 mx-2"></div>
<Link to="/" className="bg-blue-600 p-2 rounded-lg">
<FileText className="h-6 w-6 text-white" />
</Link>
<h1 className="text-xl font-bold text-gray-800">{t('scidashboard_title')} <span className="font-normal text-gray-500 text-sm">v2.4.0</span></h1>
</div>
<div className="flex items-center gap-3">
<button
onClick={() => handleAction(t('import_data'))}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
>
{t('import_data')}
</button>
<div className="h-6 w-px bg-gray-200 mx-2"></div>
<button
onClick={() => handleAction(t('generate_mock'))}
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 shadow-sm transition-colors"
>
{t('generate_mock')}
</button>
<button
onClick={() => handleAction(t('export_data'))}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 border border-transparent rounded-md hover:bg-gray-200 transition-colors"
>
{t('export_data')}
</button>
</div>
</header>
<main className="flex-1 flex overflow-hidden relative">
{/* Main Content Area (Table) */}
<section className="flex-1 overflow-hidden flex flex-col">
{/* Toolbar for table */}
<div className="p-4 bg-white border-b border-gray-200 flex justify-between items-center">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500">{t('variable_params')}</h2>
<div className="flex gap-2">
<button
onClick={() => handleAction(t('add_row'))}
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
title={t('add_row')}
>
<Plus className="h-5 w-5" />
</button>
<button
onClick={() => handleAction(t('delete_selection'))}
className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
title={t('delete_selection')}
>
<Trash2 className="h-5 w-5" />
</button>
</div>
</div>
{/* Spreadsheet Table */}
<div className="flex-1 overflow-auto bg-white">
<table className="min-w-full divide-y divide-gray-200 border-collapse">
<thead className="bg-gray-50 sticky top-0 z-10">
<tr>
<th className="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-r border-gray-200 w-10">#</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-r border-gray-200">{t('variable_name')}</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-r border-gray-200">{t('type')}</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-r border-gray-200">{t('distribution')}</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-r border-gray-200">{t('initial_value')}</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-r border-gray-200">{t('min')}</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-r border-gray-200">{t('max')}</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('description')}</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{[
{ id: 1, name: 'agent_density', type: 'Float', dist: 'Constant', val: '0.75', min: 'N/A', max: 'N/A', desc: language === 'en' ? 'Initial population density' : '初始种群密度', error: false },
{ id: 2, name: 'interaction_radius', type: 'Integer', dist: 'Uniform', val: '--', min: '1', max: '10', desc: language === 'en' ? 'Missing initial value' : '缺少初始值', error: true },
{ id: 3, name: 'random_seed', type: 'Integer', dist: 'Constant', val: '42', min: 'N/A', max: 'N/A', desc: language === 'en' ? 'Reproducibility seed' : '可重复性种子', error: false },
].map((row) => (
<tr key={row.id} className={`hover:bg-blue-50 transition-colors ${row.error ? 'bg-red-50/30' : ''}`}>
<td className="px-3 py-4 whitespace-nowrap text-xs text-gray-400 border-r border-gray-100 text-center">{row.id}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-blue-700 border-r border-gray-100">{row.name}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 border-r border-gray-100">
<select className="block w-full text-xs border-gray-300 rounded-sm focus:ring-blue-500 focus:border-blue-500 p-1">
<option selected={row.type === 'Float'}>Float</option>
<option selected={row.type === 'Integer'}>Integer</option>
<option selected={row.type === 'Boolean'}>Boolean</option>
</select>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 border-r border-gray-100">
<select className="block w-full text-xs border-gray-300 rounded-sm focus:ring-blue-500 focus:border-blue-500 p-1">
<option selected={row.dist === 'Normal'}>{language === 'en' ? 'Normal' : '正态'}</option>
<option selected={row.dist === 'Uniform'}>{language === 'en' ? 'Uniform' : '均匀'}</option>
<option selected={row.dist === 'Constant'}>{language === 'en' ? 'Constant' : '常量'}</option>
</select>
</td>
<td className={`px-6 py-4 whitespace-nowrap text-sm text-gray-900 border-r border-gray-100 font-medium ${row.error ? 'bg-red-100/50' : ''}`}>{row.val}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 border-r border-gray-100 italic">{row.min}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 border-r border-gray-100 italic">{row.max}</td>
<td className={`px-6 py-4 whitespace-nowrap text-sm ${row.error ? 'text-gray-400 italic' : 'text-gray-400'}`}>{row.desc}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
{/* Right Sidebar (Logic Validation) */}
<aside className="w-80 bg-white border-l border-gray-200 flex flex-col z-20">
<div className="p-4 border-b border-gray-200 bg-gray-50">
<h2 className="text-sm font-bold text-gray-700 flex items-center gap-2">
<CheckCircle className="h-4 w-4 text-blue-600" />
{t('logic_validation')}
</h2>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4">
<div className="p-3 bg-red-50 border border-red-100 rounded-lg">
<div className="flex items-start gap-3">
<div className="bg-red-200 p-1 rounded-full mt-0.5">
<AlertCircle className="h-3 w-3 text-red-700" />
</div>
<div>
<p className="text-xs font-bold text-red-800 uppercase">{t('missing_value')}</p>
<p className="text-sm text-red-700 mt-1">{language === 'en' ? 'Variable' : '变量'} <span className="font-mono">interaction_radius</span> {language === 'en' ? 'requires an initial value or distribution range.' : '需要初始值或分布范围。'}</p>
</div>
</div>
</div>
<div className="p-3 bg-amber-50 border border-amber-100 rounded-lg">
<div className="flex items-start gap-3">
<div className="bg-amber-200 p-1 rounded-full mt-0.5">
<AlertCircle className="h-3 w-3 text-amber-700" />
</div>
<div>
<p className="text-xs font-bold text-amber-800 uppercase">{t('type_mismatch')}</p>
<p className="text-sm text-amber-700 mt-1">{language === 'en' ? 'Potential float-to-int conversion in row 1 description field.' : '第 1 行描述字段中存在潜在的浮点数到整数转换。'}</p>
</div>
</div>
</div>
<div className="p-3 bg-green-50 border border-green-100 rounded-lg">
<p className="text-xs font-bold text-green-800 uppercase flex items-center gap-2">
<span className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse"></span>
{t('realtime_monitoring')}
</p>
<p className="text-sm text-green-700 mt-1">{language === 'en' ? 'Logic validation is active. 3 issues detected.' : '逻辑验证已激活。检测到 3 个问题。'}</p>
</div>
</div>
<div className="p-4 border-t border-gray-200 text-center">
<button className="text-sm font-semibold text-blue-600 hover:text-blue-700" onClick={() => handleAction(t('run_deep_scan'))}>{t('run_deep_scan')}</button>
</div>
</aside>
{/* Bottom Drawer (Code Preview) */}
<div
className={`absolute bottom-0 left-0 right-0 bg-white text-slate-800 z-40 transition-transform duration-300 border-t border-slate-200 ${isDrawerOpen ? 'translate-y-0 h-[240px]' : 'translate-y-[200px] h-[240px]'}`}
>
<div
className="flex items-center justify-between px-6 py-2 bg-slate-50 cursor-pointer border-b border-slate-100"
onClick={() => setIsDrawerOpen(!isDrawerOpen)}
>
<div className="flex items-center gap-3">
<span className="text-xs font-bold text-slate-500 uppercase tracking-widest">{t('agentpy_preview')}</span>
<span className="px-2 py-0.5 rounded text-[10px] bg-green-50 text-green-600 border border-green-200 font-bold uppercase tracking-tighter">{t('auto_generated')}</span>
</div>
<div className="flex items-center gap-4">
<button className="text-xs text-blue-600 font-bold hover:text-blue-700 transition-colors" onClick={(e) => { e.stopPropagation(); handleAction(t('copy_code')); }}>{t('copy_code')}</button>
<ChevronDown className={`h-4 w-4 text-slate-400 transition-transform ${isDrawerOpen ? '' : 'rotate-180'}`} />
</div>
</div>
<div className="p-6 font-mono text-sm overflow-auto h-full pb-12 bg-slate-50 shadow-inner">
<pre className="text-blue-700">
import <span className="text-slate-900 font-bold">agentpy</span> as ap{'\n\n'}
<span className="text-slate-400 italic"># {language === 'en' ? 'Generated Parameters' : '生成的参数'}</span>{'\n'}
exp_parameters = {'{'}{'\n'}
{' '}<span className="text-amber-700">'agent_density'</span>: <span className="text-slate-900 font-bold">0.75</span>,{'\n'}
{' '}<span className="text-amber-700">'interaction_radius'</span>: <span className="text-slate-400 italic"># {language === 'en' ? 'MISSING_VALUE' : '缺失值'}</span>,{'\n'}
{' '}<span className="text-amber-700">'random_seed'</span>: <span className="text-slate-900 font-bold">42</span>{'\n'}
{'}'}{'\n\n'}
<span className="text-slate-400 italic"># {language === 'en' ? 'Model Definition' : '模型定义'}</span>{'\n'}
class <span className="text-slate-900 font-bold">ScientificModel</span>(ap.Model):{'\n'}
{' '}def setup(self):{'\n'}
{' '}<span className="text-slate-400 italic"># {language === 'en' ? 'Setup logic based on table parameters' : '基于表格参数的设置逻辑'}</span>{'\n'}
{' '}pass{'\n\n'}
{' '}def step(self):{'\n'}
{' '}pass
</pre>
</div>
</div>
</main>
</div>
);
};
export default SciDashboard;
+343
View File
@@ -0,0 +1,343 @@
import React, { useState } from 'react';
import { BookOpen, Video, Code, Users, Settings, Search, ArrowRight, PlayCircle, X } from 'lucide-react';
import { Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'motion/react';
import { useLanguage } from '../context/LanguageContext';
import ModuleSwitcher from '../components/ModuleSwitcher';
const SciHub: React.FC = () => {
const [showToast, setShowToast] = useState<string | null>(null);
const { t, language } = useLanguage();
const handleLinkClick = (e: React.MouseEvent, name: string) => {
e.preventDefault();
setShowToast(name);
setTimeout(() => setShowToast(null), 3000);
};
return (
<div className="min-h-screen bg-slate-50 text-slate-900 font-sans antialiased flex flex-col relative">
{/* Toast Notification */}
<AnimatePresence>
{showToast && (
<motion.div
initial={{ opacity: 0, y: 50, x: '-50%' }}
animate={{ opacity: 1, y: 0, x: '-50%' }}
exit={{ opacity: 0, y: 50, x: '-50%' }}
className="fixed bottom-8 left-1/2 z-[100] bg-white text-slate-900 px-6 py-3 rounded-full shadow-2xl border border-slate-200 flex items-center gap-3"
>
<div className="w-2 h-2 bg-indigo-600 rounded-full animate-pulse" />
<span className="text-sm font-medium">"{showToast}" {t('coming_soon')}</span>
<button onClick={() => setShowToast(null)} className="ml-2 hover:text-indigo-600 transition-colors">
<X className="w-4 h-4" />
</button>
</motion.div>
)}
</AnimatePresence>
{/* Main Header */}
<header className="h-16 bg-white border-b border-slate-200 flex items-center justify-between px-6 z-50 sticky top-0 shrink-0">
<div className="flex items-center gap-4">
<ModuleSwitcher />
<div className="h-6 w-px bg-slate-200 mx-2"></div>
<Link to="/" className="flex items-center gap-2">
<div className="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center text-white font-bold">S</div>
<span className="text-xl font-bold tracking-tight text-indigo-900">{t('scihub_title')}</span>
</Link>
</div>
<div className="flex items-center gap-4">
<button
onClick={(e) => handleLinkClick(e, t('settings'))}
className="p-2 text-slate-400 hover:text-indigo-600 transition-colors"
>
<Settings className="w-5 h-5" />
</button>
</div>
</header>
<div className="flex flex-1 overflow-hidden">
{/* Sidebar */}
<aside className="w-64 bg-white border-r border-slate-200 flex flex-col shrink-0">
<nav className="flex-1 overflow-y-auto p-4 space-y-6">
<div>
<h3 className="px-3 text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2">{t('resources')}</h3>
<ul className="space-y-1">
<li>
<a
href="#"
onClick={(e) => handleLinkClick(e, t('documentation'))}
className="flex items-center gap-3 px-3 py-2 text-sm font-medium text-slate-700 rounded-md hover:bg-slate-100 group transition-colors"
>
<BookOpen className="w-5 h-5 text-slate-400 group-hover:text-indigo-600" />
{t('documentation')}
</a>
</li>
<li>
<a
href="#"
onClick={(e) => handleLinkClick(e, t('video_tutorials'))}
className="flex items-center gap-3 px-3 py-2 text-sm font-medium text-slate-700 rounded-md hover:bg-slate-100 group transition-colors"
>
<Video className="w-5 h-5 text-slate-400 group-hover:text-indigo-600" />
{t('video_tutorials')}
</a>
</li>
<li>
<a
href="#"
onClick={(e) => handleLinkClick(e, t('api_specs'))}
className="flex items-center gap-3 px-3 py-2 text-sm font-medium text-slate-700 rounded-md hover:bg-slate-100 group transition-colors"
>
<Code className="w-5 h-5 text-slate-400 group-hover:text-indigo-600" />
{t('api_specs')}
</a>
</li>
</ul>
</div>
<div>
<h3 className="px-3 text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2">{t('community')}</h3>
<ul className="space-y-1">
<li>
<a
href="#"
onClick={(e) => handleLinkClick(e, t('forum'))}
className="flex items-center gap-3 px-3 py-2 text-sm font-medium text-slate-700 rounded-md hover:bg-slate-100 group transition-colors"
>
<Users className="w-5 h-5 text-slate-400 group-hover:text-indigo-600" />
{t('forum')}
</a>
</li>
</ul>
</div>
</nav>
</aside>
{/* Main Content */}
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-slate-50">
{/* Hero Section */}
<section className="bg-white border-b border-slate-100 py-16 px-8 text-slate-900 relative overflow-hidden">
<div className="max-w-4xl mx-auto relative z-10 text-center">
<h1 className="text-4xl md:text-5xl font-extrabold mb-4 tracking-tight text-slate-900">
{t('hero_title')}
</h1>
<p className="text-slate-500 text-lg mb-8 max-w-2xl mx-auto">
{t('hero_subtitle')}
</p>
<div className="relative max-w-3xl mx-auto group">
{/* Decorative Glow */}
<div className="absolute -inset-1 bg-gradient-to-r from-blue-100 to-indigo-100 rounded-2xl blur opacity-20 group-hover:opacity-40 transition duration-1000 group-hover:duration-200"></div>
<div className="relative flex items-center bg-white rounded-xl shadow-xl overflow-hidden p-1 border border-slate-200">
<div className="pl-4 flex items-center pointer-events-none">
<Search className="h-5 w-5 text-indigo-400" />
</div>
<input
className="block w-full pl-3 pr-4 py-2.5 bg-transparent border-none text-slate-900 focus:ring-0 text-lg placeholder-slate-400 font-medium"
placeholder={t('search_placeholder')}
type="text"
/>
<button className="bg-indigo-600 hover:bg-indigo-700 text-white px-8 py-2.5 rounded-lg font-bold transition-all shadow-lg shadow-indigo-500/20 active:scale-95 flex items-center gap-2 shrink-0">
<Search className="w-4 h-4" />
{t('search_button')}
</button>
</div>
</div>
<div className="mt-4 flex justify-center gap-4 text-sm text-slate-400 font-medium">
<span className="text-slate-500 font-bold uppercase tracking-wider text-[10px]">{t('trending')}</span>
<a className="hover:text-indigo-600 underline underline-offset-4 decoration-indigo-200" href="#">#SocialDynamics</a>
<a className="hover:text-indigo-600 underline underline-offset-4 decoration-indigo-200" href="#">#EconModels</a>
<a className="hover:text-indigo-600 underline underline-offset-4 decoration-indigo-200" href="#">#VISA-2024</a>
</div>
</div>
{/* Abstract background pattern */}
<div className="absolute top-0 right-0 -mr-20 -mt-20 opacity-[0.03] text-indigo-900">
<svg fill="currentColor" height="400" viewBox="0 0 100 100" width="400"><circle cx="50" cy="50" r="50"></circle></svg>
</div>
</section>
{/* Content Container */}
<div className="max-w-7xl mx-auto px-8 py-12 space-y-16">
{/* Researcher's Journey */}
<section>
<div className="flex items-end justify-between mb-8">
<div>
<h2 className="text-2xl font-bold text-slate-800">{t('researcher_journey')}</h2>
<p className="text-slate-500 mt-1">{t('onboarding_path')}</p>
</div>
<a className="text-indigo-600 font-semibold text-sm hover:underline" href="#" onClick={(e) => handleLinkClick(e, t('view_roadmap'))}>{t('view_roadmap')} <ArrowRight className="inline w-4 h-4" /></a>
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{[
{ title: t('setup_env'), desc: t('setup_env_desc'), step: '01', progress: 100 },
{ title: t('understand_visa'), desc: t('understand_visa_desc'), step: '02', progress: 0 },
{ title: t('run_first_model'), desc: t('run_first_model_desc'), step: '03', progress: 0 },
{ title: t('contribute'), desc: t('contribute_desc'), step: '04', progress: 0 },
].map((item, i) => (
<div key={i} className="relative bg-white p-6 rounded-xl border-t-4 border-indigo-500 shadow-sm transition-transform hover:-translate-y-1 cursor-pointer" onClick={(e) => handleLinkClick(e, item.title)}>
<span className="absolute top-4 right-4 text-4xl font-black text-slate-100 select-none">{item.step}</span>
<div className="relative z-10">
<h4 className="font-bold mb-2">{item.title}</h4>
<p className="text-xs text-slate-500 leading-relaxed">{item.desc}</p>
<div className="mt-4 h-1.5 w-full bg-slate-100 rounded-full overflow-hidden">
<div className="bg-indigo-500 h-full" style={{ width: `${item.progress}%` }}></div>
</div>
</div>
</div>
))}
</div>
</section>
{/* Classic Case Library */}
<section>
<div className="flex items-end justify-between mb-8">
<div>
<h2 className="text-2xl font-bold text-slate-800">{t('case_library')}</h2>
<p className="text-slate-500 mt-1">{t('case_library_desc')}</p>
</div>
<div className="flex gap-2">
<button className="p-2 rounded-md bg-white border border-slate-200 hover:bg-slate-50 text-slate-600" onClick={(e) => handleLinkClick(e, t('case_library'))}>
<Search className="w-5 h-5" />
</button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{[
{
title: language === 'en' ? "Schelling's Segregation" : "谢林隔离模型",
category: language === 'en' ? 'Social Sciences' : '社会科学',
updated: language === 'en' ? '2d ago' : '2天前',
desc: language === 'en'
? 'A classic agent-based model showing how individual preferences for neighbors can lead to large-scale segregation.'
: '一个经典的基于代理的模型,展示了个人对邻居的偏好如何导致大规模的隔离。',
color: 'bg-indigo-50',
iconColor: 'text-indigo-200'
},
{
title: language === 'en' ? "Cournot Competition" : "古诺竞争",
category: language === 'en' ? 'Economics' : '经济学',
updated: language === 'en' ? '1w ago' : '1周前',
desc: language === 'en'
? 'Economic model describing an industry structure in which rival companies offering an identical product compete on quantity.'
: '描述一种行业结构的经济模型,在这种结构中,提供相同产品的竞争对手在数量上进行竞争。',
color: 'bg-emerald-50',
iconColor: 'text-emerald-200'
},
{
title: language === 'en' ? "Conway's Game of Life" : "康威生命游戏",
category: language === 'en' ? 'Mathematics' : '数学',
updated: language === 'en' ? '5d ago' : '5天前',
desc: language === 'en'
? 'A cellular automaton that demonstrates how complex patterns can emerge from simple deterministic rules.'
: '一种元胞自动机,展示了如何从简单的确定性规则中产生复杂的模式。',
color: 'bg-amber-50',
iconColor: 'text-amber-200'
},
].map((item, i) => (
<article key={i} className="bg-white rounded-2xl overflow-hidden border border-slate-200 transition-all hover:shadow-lg hover:-translate-y-1 flex flex-col h-full">
<div className={`h-40 ${item.color} flex items-center justify-center border-b border-slate-100`}>
<PlayCircle className={`w-16 h-16 ${item.iconColor}`} />
</div>
<div className="p-6 flex-1 flex flex-col">
<div className="flex items-center gap-2 mb-3">
<span className="bg-blue-100 text-blue-700 text-[10px] font-bold px-2 py-0.5 rounded uppercase tracking-wider">{item.category}</span>
<span className="text-slate-400 text-xs">{language === 'en' ? 'Updated' : '更新于'} {item.updated}</span>
</div>
<h3 className="text-xl font-bold mb-2 text-slate-800">{item.title}</h3>
<p className="text-slate-600 text-sm mb-6 flex-1">{item.desc}</p>
<Link to="/editor" className="w-full bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold py-2.5 rounded-lg transition-colors flex items-center justify-center gap-2">
<PlayCircle className="w-4 h-4" />
{t('reproduce_one_click')}
</Link>
</div>
</article>
))}
</div>
</section>
{/* Model Library */}
<section>
<div className="flex items-end justify-between mb-8">
<div>
<h2 className="text-2xl font-bold text-slate-800">{t('model_library')}</h2>
<p className="text-slate-500 mt-1">{t('model_library_desc')}</p>
</div>
<div className="flex bg-slate-100 p-1 rounded-lg">
<button className="px-4 py-1.5 text-sm font-medium rounded-md bg-white shadow-sm text-slate-900">{language === 'en' ? 'Personal' : '个人'}</button>
<button className="px-4 py-1.5 text-sm font-medium rounded-md text-slate-600 hover:text-slate-900" onClick={(e) => handleLinkClick(e, t('community'))}>{language === 'en' ? 'Shared' : '共享'}</button>
</div>
</div>
<div className="bg-white rounded-2xl border border-slate-200 overflow-hidden shadow-sm">
<table className="w-full text-left text-sm">
<thead className="bg-slate-50 text-slate-500 font-semibold border-b border-slate-200">
<tr>
<th className="px-6 py-4">{language === 'en' ? 'Model Name' : '模型名称'}</th>
<th className="px-6 py-4">{language === 'en' ? 'Type' : '类型'}</th>
<th className="px-6 py-4">{language === 'en' ? 'Status' : '状态'}</th>
<th className="px-6 py-4">{language === 'en' ? 'Visibility' : '可见性'}</th>
<th className="px-6 py-4 text-right">{language === 'en' ? 'Actions' : '操作'}</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{[
{ name: 'Prisoner_Dilemma_Iterated', type: language === 'en' ? 'Game Theory' : '博弈论', status: language === 'en' ? 'Verified' : '已验证', visibility: language === 'en' ? 'Private' : '私有', initials: 'PD', color: 'bg-indigo-100', textColor: 'text-indigo-600' },
{ name: 'SIR_Epidemic_Spatial', type: language === 'en' ? 'Epidemiology' : '流行病学', status: language === 'en' ? 'Draft' : '草稿', visibility: language === 'en' ? 'Shared' : '共享', initials: 'SI', color: 'bg-orange-100', textColor: 'text-orange-600' },
{ name: 'Nash_Equilibrium_Solver', type: language === 'en' ? 'Algorithm' : '算法', status: language === 'en' ? 'Verified' : '已验证', visibility: language === 'en' ? 'Public' : '公开', initials: 'NC', color: 'bg-sky-100', textColor: 'text-sky-600' },
].map((row, i) => (
<tr key={i} className="hover:bg-slate-50/50 transition-colors">
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<div className={`w-8 h-8 rounded ${row.color} flex items-center justify-center ${row.textColor} font-bold text-xs`}>{row.initials}</div>
<span className="font-medium text-slate-900">{row.name}</span>
</div>
</td>
<td className="px-6 py-4 text-slate-600">{row.type}</td>
<td className="px-6 py-4">
<span className={`inline-flex items-center gap-1.5 ${row.status === (language === 'en' ? 'Verified' : '已验证') ? 'text-emerald-700 bg-emerald-100' : 'text-amber-700 bg-amber-100'} px-2 py-0.5 rounded-full text-[11px] font-bold`}>
<span className={`w-1.5 h-1.5 rounded-full ${row.status === (language === 'en' ? 'Verified' : '已验证') ? 'bg-emerald-500' : 'bg-amber-500'}`}></span>
{row.status}
</span>
</td>
<td className="px-6 py-4 text-slate-600">{row.visibility}</td>
<td className="px-6 py-4 text-right">
<Link to="/dashboard" className="text-indigo-600 hover:text-indigo-800 font-semibold">{language === 'en' ? 'Edit' : '编辑'}</Link>
</td>
</tr>
))}
</tbody>
</table>
<div className="p-4 bg-slate-50 border-t border-slate-200 text-center">
<button className="text-sm font-semibold text-indigo-600 hover:text-indigo-700" onClick={(e) => handleLinkClick(e, language === 'en' ? 'Load 12 more models...' : '加载更多模型...')}>
{language === 'en' ? 'Load 12 more models...' : '加载更多模型...'}
</button>
</div>
</div>
</section>
</div>
{/* Footer */}
<footer className="border-t border-slate-200 py-12 px-8 bg-white">
<div className="max-w-7xl mx-auto flex flex-col md:flex-row justify-between items-center gap-8">
<div className="flex items-center gap-4">
<div className="w-10 h-10 bg-indigo-600 rounded-lg flex items-center justify-center text-white font-bold">S</div>
<div>
<p className="font-bold text-slate-800 uppercase tracking-widest text-sm">SciHub Platform</p>
<p className="text-xs text-slate-500">Empowering scientific reproduction through data.</p>
</div>
</div>
<div className="flex gap-8 text-sm font-medium text-slate-500">
<a className="hover:text-indigo-600" href="#">Privacy Policy</a>
<a className="hover:text-indigo-600" href="#">Terms of Service</a>
<a className="hover:text-indigo-600" href="#">GitHub</a>
<a className="hover:text-indigo-600" href="#">Contact Support</a>
</div>
<p className="text-xs text-slate-400">© 2026 SciHub Research Hub. All rights reserved.</p>
</div>
</footer>
</main>
</div>
</div>
);
};
export default SciHub;
+342
View File
@@ -0,0 +1,342 @@
import React, { useEffect, useRef, useState } from 'react';
import {
Play,
Pause,
Square,
Activity,
BarChart3,
PieChart,
Terminal,
Download,
FileText,
Zap,
TrendingUp,
X
} from 'lucide-react';
import { Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'motion/react';
import { useLanguage } from '../context/LanguageContext';
import ModuleSwitcher from '../components/ModuleSwitcher';
const SimuAnalysis: React.FC = () => {
const { t, language } = useLanguage();
const canvasRef = useRef<HTMLCanvasElement>(null);
const [showToast, setShowToast] = useState<string | null>(null);
const handleAction = (name: string) => {
setShowToast(name);
setTimeout(() => setShowToast(null), 3000);
};
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const width = canvas.width;
const height = canvas.height;
const agents = Array.from({ length: 120 }, () => ({
x: Math.random() * width,
y: Math.random() * height,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
type: Math.floor(Math.random() * 3)
}));
let animationFrameId: number;
const draw = () => {
// Light background for canvas animation with trails
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
ctx.fillRect(0, 0, width, height);
agents.forEach(agent => {
agent.x += agent.vx;
agent.y += agent.vy;
if (agent.x < 0 || agent.x > width) agent.vx *= -1;
if (agent.y < 0 || agent.y > height) agent.vy *= -1;
ctx.beginPath();
ctx.arc(agent.x, agent.y, 2.5, 0, Math.PI * 2);
if (agent.type === 0) ctx.fillStyle = '#ef4444';
else if (agent.type === 1) ctx.fillStyle = '#2563eb';
else ctx.fillStyle = '#64748b';
ctx.fill();
});
animationFrameId = requestAnimationFrame(draw);
};
draw();
return () => cancelAnimationFrame(animationFrameId);
}, []);
return (
<div className="bg-slate-50 text-slate-800 min-h-screen flex flex-col font-sans overflow-hidden relative">
{/* Toast Notification */}
<AnimatePresence>
{showToast && (
<motion.div
initial={{ opacity: 0, y: 50, x: '-50%' }}
animate={{ opacity: 1, y: 0, x: '-50%' }}
exit={{ opacity: 0, y: 50, x: '-50%' }}
className="fixed bottom-8 left-1/2 z-[100] bg-white text-slate-900 px-6 py-3 rounded-full shadow-2xl border border-slate-200 flex items-center gap-3"
>
<div className="w-2 h-2 bg-blue-600 rounded-full animate-pulse" />
<span className="text-sm font-medium">"{showToast}" {t('coming_soon')}</span>
<button onClick={() => setShowToast(null)} className="ml-2 hover:text-blue-600 transition-colors">
<X className="w-4 h-4" />
</button>
</motion.div>
)}
</AnimatePresence>
{/* Main Header */}
<header className="border-b border-slate-200 bg-white/80 backdrop-blur-md sticky top-0 z-50 px-6 py-4 flex items-center justify-between shrink-0">
<div className="flex items-center gap-4">
<ModuleSwitcher />
<div className="h-6 w-px bg-slate-200 mx-2"></div>
<Link to="/" className="w-10 h-10 bg-blue-600 rounded-xl flex items-center justify-center shadow-lg shadow-blue-500/20">
<Activity className="h-6 w-6 text-white" />
</Link>
<div>
<h1 className="text-xl font-bold tracking-tight text-slate-900">SimuLink V4.2</h1>
<p className="text-[10px] text-slate-400 uppercase tracking-widest font-bold">{t('simulink_subtitle')}</p>
</div>
</div>
{/* Execution Controls */}
<div className="flex items-center gap-3 bg-slate-100 p-1 rounded-2xl border border-slate-200">
<button
onClick={() => handleAction(t('start_sim'))}
className="flex items-center gap-2 px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-xl transition-all active:scale-95 text-sm font-bold shadow-sm"
>
<Play className="h-4 w-4 fill-current" />
{t('start_sim')}
</button>
<button
onClick={() => handleAction(t('pause_sim'))}
className="flex items-center gap-2 px-4 py-2 bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 rounded-xl transition-all active:scale-95 text-sm font-bold shadow-sm"
>
<Pause className="h-4 w-4 fill-current" />
{t('pause_sim')}
</button>
<button
onClick={() => handleAction(t('stop_sim'))}
className="flex items-center gap-2 px-4 py-2 bg-rose-50 hover:bg-rose-100 text-rose-600 border border-rose-100 rounded-xl transition-all active:scale-95 text-sm font-bold"
>
<Square className="h-4 w-4 fill-current" />
{t('stop_sim')}
</button>
</div>
{/* Resource Monitors */}
<div className="hidden lg:flex items-center gap-8 border-l border-slate-200 pl-8">
<div className="flex flex-col gap-1">
<div className="flex justify-between text-[10px] text-slate-500 font-bold uppercase tracking-tighter">
<span>{t('cpu_usage')}</span>
<span className="text-blue-600">42%</span>
</div>
<div className="w-32 h-1.5 bg-slate-200 rounded-full overflow-hidden">
<div className="bg-blue-600 h-full" style={{ width: '42%' }}></div>
</div>
</div>
<div className="flex flex-col gap-1">
<div className="flex justify-between text-[10px] text-slate-500 font-bold uppercase tracking-tighter">
<span>{t('memory')}</span>
<span className="text-indigo-600">2.4GB</span>
</div>
<div className="w-32 h-1.5 bg-slate-200 rounded-full overflow-hidden">
<div className="bg-indigo-600 h-full" style={{ width: '65%' }}></div>
</div>
</div>
</div>
</header>
{/* Main Dashboard Layout */}
<main className="flex-1 grid grid-cols-12 overflow-hidden bg-slate-50">
{/* LEFT & CENTER AREA (9 cols) */}
<div className="col-span-12 lg:col-span-9 flex flex-col h-full border-r border-slate-200 overflow-hidden">
{/* Progress Bar */}
<div className="px-6 py-2 bg-white border-b border-slate-100 flex items-center gap-4 shrink-0">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{t('sim_progress')}</span>
<div className="flex-1 h-2 bg-slate-100 rounded-full overflow-hidden flex">
<div className="bg-gradient-to-r from-blue-600 to-indigo-500 h-full" style={{ width: '74%' }}></div>
</div>
<span className="text-[10px] font-mono text-slate-500 font-bold">74% ({language === 'en' ? 'Iteration' : '迭代'} 7,400 / 10,000)</span>
</div>
{/* Scrollable Grid Content */}
<div className="flex-1 overflow-y-auto p-6 space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Spatial Dynamics */}
<section className="bg-white rounded-2xl border border-slate-200 overflow-hidden flex flex-col shadow-sm">
<div className="px-4 py-3 border-b border-slate-100 flex justify-between items-center bg-slate-50/50">
<h3 className="text-sm font-bold flex items-center gap-2 text-slate-700">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
{t('spatial_dynamics')}
</h3>
<span className="text-[10px] text-slate-400 font-mono font-bold uppercase tracking-wider">GRID: 100x100</span>
</div>
<div className="p-4 flex-1 flex items-center justify-center bg-white">
<canvas ref={canvasRef} width={400} height={225} className="rounded-xl border border-slate-100 w-full aspect-video bg-white shadow-inner"></canvas>
</div>
<div className="px-4 py-2 border-t border-slate-100 flex gap-4 text-[10px] text-slate-500 font-bold uppercase tracking-wider">
<div className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-full bg-red-500"></span> {language === 'en' ? 'Producers' : '生产者'}</div>
<div className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-full bg-blue-600"></span> {language === 'en' ? 'Consumers' : '消费者'}</div>
<div className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-full bg-slate-300"></span> {language === 'en' ? 'Neutral' : '中立者'}</div>
</div>
</section>
{/* Analytical Results */}
<section className="bg-white rounded-2xl border border-slate-200 overflow-hidden flex flex-col shadow-sm">
<div className="px-4 py-3 border-b border-slate-100 bg-slate-50/50">
<h3 className="text-sm font-bold text-slate-700">{t('analytical_results')}</h3>
</div>
<div className="p-6 flex-1 overflow-y-auto space-y-6">
<div className="space-y-2">
<p className="text-[10px] text-blue-600 font-bold uppercase tracking-widest">{t('nash_equilibrium')}</p>
<div className="bg-slate-50 p-4 rounded-xl border border-slate-100 text-center font-serif italic text-lg text-slate-800">
N* = arg max (Σ log(x_i) - λ Σ x_i²)
</div>
</div>
<div className="space-y-2">
<p className="text-[10px] text-indigo-600 font-bold uppercase tracking-widest">{t('stability_matrix')}</p>
<div className="bg-slate-50 p-4 rounded-xl border border-slate-100 text-center">
<div className="text-sm font-serif text-slate-800">
J = [ f_i / x_j ]
</div>
<p className="mt-3 text-[10px] text-slate-400 italic tracking-wide">{language === 'en' ? 'Eigenvalues' : '特征值'}: λ = -0.42, λ = -0.11</p>
<div className="mt-1 text-[9px] bg-emerald-50 text-emerald-600 inline-block px-2 py-0.5 rounded-full font-bold uppercase tracking-widest">
{language === 'en' ? 'Asymptotically Stable' : '渐近稳定'}
</div>
</div>
</div>
</div>
</section>
{/* Macro Trends */}
<section className="md:col-span-2 bg-white rounded-2xl border border-slate-200 overflow-hidden shadow-sm">
<div className="px-4 py-3 border-b border-slate-100 flex justify-between items-center bg-slate-50/50">
<h3 className="text-sm font-bold text-slate-700">{t('macro_trends')}</h3>
<div className="flex gap-2">
<button className="px-3 py-1 bg-slate-100 hover:bg-slate-200 rounded-lg text-[10px] font-bold text-slate-600 transition-colors">1H</button>
<button className="px-3 py-1 bg-blue-50 text-blue-600 border border-blue-100 rounded-lg text-[10px] font-bold">{language === 'en' ? 'Live' : '实时'}</button>
</div>
</div>
<div className="p-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="h-44 bg-slate-50 rounded-xl border border-slate-100 flex flex-col p-3 shadow-inner">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-3">{t('pop_volatility')}</span>
<div className="flex-1 flex items-end gap-1.5">
{[40, 60, 75, 50, 40, 85, 65].map((h, i) => (
<div key={i} className="flex-1 bg-blue-500/30 rounded-t-lg transition-all hover:bg-blue-500" style={{ height: `${h}%` }}></div>
))}
</div>
</div>
<div className="h-44 bg-slate-50 rounded-xl border border-slate-100 flex flex-col p-3 shadow-inner">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-3">{t('entropy_metric')}</span>
<div className="flex-1 relative">
<svg className="w-full h-full" preserveAspectRatio="none" viewBox="0 0 100 100">
<path d="M0 80 Q 25 20, 50 50 T 100 30" fill="none" stroke="#3b82f6" strokeWidth="3" strokeLinecap="round" />
</svg>
</div>
</div>
<div className="h-44 bg-slate-50 rounded-xl border border-slate-100 flex flex-col p-3 shadow-inner">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-3">{t('resource_dist')}</span>
<div className="flex-1 flex items-center justify-center">
<div className="w-20 h-20 rounded-full border-8 border-slate-200 border-t-blue-500 rotate-45 shadow-sm"></div>
</div>
</div>
</div>
</section>
</div>
{/* Execution Logs Terminal */}
<section className="bg-slate-900 rounded-2xl border border-slate-800 overflow-hidden shadow-xl">
<div className="px-4 py-2 border-b border-slate-800 bg-slate-950/50 flex justify-between items-center">
<span className="text-[10px] font-bold text-slate-500 uppercase tracking-widest flex items-center gap-2">
<Terminal className="w-3.5 h-3.5" />
{t('execution_terminal')}
</span>
<span className="text-[10px] text-emerald-500 font-mono font-bold tracking-tighter"> {language === 'en' ? 'ACTIVE' : '活动'}</span>
</div>
<div className="p-4 h-48 overflow-y-auto font-mono text-xs space-y-1.5 scrollbar-hide">
<p className="text-slate-500"><span className="text-[10px] text-slate-600 font-bold opacity-50">[14:22:01]</span> <span className="text-emerald-500 font-bold uppercase text-[9px] bg-emerald-500/10 px-1.5 py-0.5 rounded tracking-tighter mr-2">INFO</span> {language === 'en' ? 'Grid initialized successfully. Agent density: 0.12' : '网格初始化成功。代理密度:0.12'}</p>
<p className="text-slate-500"><span className="text-[10px] text-slate-600 font-bold opacity-50">[14:22:05]</span> <span className="text-blue-400 font-bold uppercase text-[9px] bg-blue-400/10 px-1.5 py-0.5 rounded tracking-tighter mr-2">MATH</span> {language === 'en' ? 'Re-calculating Jacobian matrix for Step 5000...' : '正在重新计算第 5000 步的雅可比矩阵...'}</p>
<p className="text-slate-500"><span className="text-[10px] text-slate-600 font-bold opacity-50">[14:22:10]</span> <span className="text-amber-500 font-bold uppercase text-[9px] bg-amber-500/10 px-1.5 py-0.5 rounded tracking-tighter mr-2">WARN</span> {language === 'en' ? 'Local cluster detected at [42, 88]. Possible phase transition.' : '在 [42, 88] 检测到局部集群。可能存在相变。'}</p>
<p className="text-slate-500"><span className="text-[10px] text-slate-600 font-bold opacity-50">[14:23:45]</span> <span className="text-emerald-500 font-bold uppercase text-[9px] bg-emerald-500/10 px-1.5 py-0.5 rounded tracking-tighter mr-2">INFO</span> {language === 'en' ? 'Checkpoint reached. Saving state to /models/sim_v4_state.bin' : '到达检查点。正在将状态保存到 /models/sim_v4_state.bin'}</p>
<p className="text-slate-300 animate-pulse"><span className="text-[10px] text-slate-600 font-bold opacity-50">[14:24:12]</span> <span className="text-emerald-500 font-bold uppercase text-[9px] bg-emerald-500/10 px-1.5 py-0.5 rounded tracking-tighter mr-2">INFO</span> {language === 'en' ? 'Processing batch 7401/10000...' : '正在处理批次 7401/10000...'}</p>
<p className="text-blue-400">&gt; <span className="w-1.5 h-4 bg-blue-500 inline-block animate-pulse ml-1 align-middle"></span></p>
</div>
</section>
</div>
</div>
{/* RIGHT SIDEBAR (3 cols) */}
<aside className="hidden lg:flex col-span-3 flex-col bg-white border-l border-slate-200 overflow-hidden">
<div className="p-6 h-full flex flex-col space-y-6">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 rounded-2xl bg-blue-50 flex items-center justify-center border border-blue-100">
<Zap className="h-5 w-5 text-blue-600" />
</div>
<div>
<h2 className="font-bold text-lg text-slate-900">{t('ai_report')}</h2>
<div className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full"></span>
<span className="text-[9px] text-slate-400 uppercase font-bold tracking-widest">{t('visa_connected')}</span>
</div>
</div>
</div>
<div className="space-y-5 overflow-y-auto flex-1 pr-1 custom-scrollbar">
<div className="bg-slate-50 p-5 rounded-2xl border border-slate-100 hover:border-blue-200 transition-all shadow-sm">
<span className="text-[10px] text-blue-600 font-bold uppercase tracking-widest">{t('key_findings')}</span>
<p className="text-sm mt-3 text-slate-600 leading-relaxed font-medium">
{language === 'en' ? 'Systemic resilience is currently at' : '系统韧性目前为'} <span className="text-emerald-600 font-bold">88%</span>{language === 'en' ? 'Agent clustering suggests an emerging Pareto distribution in resource allocation within Sector 4.' : '代理集群表明第 4 区资源分配中出现了帕累托分布。'}
</p>
</div>
<div className="bg-slate-50 p-5 rounded-2xl border border-slate-100">
<span className="text-[10px] text-indigo-600 font-bold uppercase tracking-widest">{t('recommended_props')}</span>
<ul className="mt-4 space-y-4">
<li className="flex gap-4 text-xs text-slate-500 leading-relaxed">
<span className="mt-1.5 w-1.5 h-1.5 rounded-full bg-indigo-500 flex-shrink-0 shadow-[0_0_8px_rgba(99,102,241,0.5)]"></span>
{language === 'en' ? 'Introduce stochastic perturbations to break current lock-in state.' : '引入随机扰动以打破当前的锁定状态。'}
</li>
<li className="flex gap-4 text-xs text-slate-500 leading-relaxed">
<span className="mt-1.5 w-1.5 h-1.5 rounded-full bg-indigo-500 flex-shrink-0 shadow-[0_0_8px_rgba(99,102,241,0.5)]"></span>
{language === 'en' ? 'Increase β coefficient to observe sensitivity to migration rates.' : '增加 β 系数以观察对迁移率的敏感性。'}
</li>
</ul>
</div>
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-5 rounded-2xl border border-blue-100 shadow-sm">
<span className="text-[10px] text-blue-700 font-bold uppercase tracking-widest">{t('stability_forecast')}</span>
<div className="mt-3 flex items-center justify-between">
<span className="text-3xl font-mono font-bold text-slate-900">0.92</span>
<span className="text-[10px] text-emerald-600 bg-emerald-100 px-2.5 py-1 rounded-full font-bold uppercase tracking-tighter"> +0.03</span>
</div>
<p className="text-[10px] text-slate-400 mt-3 italic font-medium">{language === 'en' ? 'Probability of convergence: 94.2% within 2k iterations.' : '收敛概率:2k 次迭代内为 94.2%。'}</p>
</div>
</div>
<div className="pt-4 border-t border-slate-100 space-y-3">
<button className="w-full py-2.5 bg-white border border-slate-200 text-slate-700 rounded-xl text-xs font-bold hover:bg-slate-50 transition-all shadow-sm active:scale-95" onClick={() => handleAction(t('export_dataset'))}>
{t('export_dataset')}
</button>
<button className="w-full py-2.5 bg-blue-600 text-white rounded-xl text-xs font-bold hover:bg-blue-700 transition-all shadow-lg shadow-blue-500/20 active:scale-95 flex items-center justify-center gap-2" onClick={() => handleAction(t('download_summary'))}>
<Download className="w-3.5 h-3.5" />
{t('download_summary')}
</button>
</div>
</div>
</aside>
</main>
</div>
);
};
export default SimuAnalysis;
+256
View File
@@ -0,0 +1,256 @@
export type Language = 'en' | 'zh';
export const translations = {
en: {
// SciHub
scihub_title: 'SciHub',
resources: 'Resources',
documentation: 'Documentation',
video_tutorials: 'Video Tutorials',
api_specs: 'API Specs',
community: 'Community',
forum: 'Forum',
settings: 'Settings',
coming_soon: 'is coming soon in the next update!',
trending: 'Trending:',
hero_title: 'Scientific Knowledge Base',
hero_subtitle: 'Explore millions of literature papers in VISA format. Scientific insights made accessible and reproducible.',
search_placeholder: 'Search for VISA format papers...',
search_button: 'Search',
researcher_journey: "Researcher's Journey",
onboarding_path: 'New to SciHub? Follow this immersive onboarding path.',
view_roadmap: 'View Roadmap',
setup_env: 'Setup Environment',
setup_env_desc: 'Configure your workspace and local API keys for data fetching.',
understand_visa: 'Understand VISA',
understand_visa_desc: 'Learn the Virtual Integrated Scientific Architecture format.',
run_first_model: 'Run First Model',
run_first_model_desc: 'Execute a sample simulation from the case library.',
contribute: 'Contribute',
contribute_desc: 'Publish your findings and models to the community.',
case_library: 'Classic Case Library',
case_library_desc: 'Foundation models ready for exploration and replication.',
reproduce_one_click: 'Reproduce with One-click',
model_library: 'Model Library',
model_library_desc: 'Manage your personal workflows and discover shared assets.',
view_details: 'View Details',
start_reading: 'Start Reading',
// OmniEditor
omnieditor_title: 'OmniModel',
project_settings: 'Project Settings',
run_model: 'Run Model',
visa_connected: 'VISA Connected',
new_model_draft: 'New Model Draft',
active_projects: 'Active Projects',
model_drafts: 'Model Drafts',
visa_storage: 'VISA STORAGE',
ai_assistant: 'AI Assistant',
ai_researcher: 'AI RESEARCHER',
user: 'USER',
visual_canvas: 'Visual Canvas',
logic_blocks: 'Logic Blocks',
parameters: 'Parameters',
import_data: 'Import Data',
export_data: 'Export Data',
variable_name: 'Variable Name',
type: 'Type',
distribution: 'Distribution',
initial_value: 'Initial Value',
min: 'Min',
max: 'Max',
description: 'Description',
variable_params: 'Variable Parameters',
logic_validation: 'Logic Validation',
missing_value: 'Missing Value',
type_mismatch: 'Type Mismatch',
real_time_monitoring: 'Real-time Monitoring',
run_deep_scan: 'Run Deep Scan',
agentpy_preview: 'Agentpy Preview',
auto_generated: 'Auto Generated',
copy_code: 'Copy Code',
simulation_settings: 'Simulation Settings',
live_feed: 'Live Feed',
population_distribution: 'Population Distribution',
system_logs: 'System Logs',
// Landing
landing_product: 'Product',
landing_enterprise: 'Enterprise',
landing_industries: 'Industries',
landing_solutions: 'Solutions',
landing_pricing: 'Pricing',
landing_my_account: 'My Account',
landing_contact_sales: 'Contact Sales',
landing_launch_app: 'Launch App',
landing_hero_title: 'Scientific Modeling & Simulation Platform',
landing_hero_subtitle: 'Design better scientific models with better VISA tools.',
landing_cta: 'Start Your Research Now',
// Login
login_title: 'Sign in to SciHub',
login_subtitle: 'Enter your credentials to access your research workspace.',
email: 'Email address',
password: 'Password',
forgot_password: 'Forgot password?',
sign_in: 'Sign in',
or_continue_with: 'Or continue with',
google_sign_in: 'Sign in with Google',
no_account: "Don't have an account?",
sign_up: 'Sign up',
back_to_home: 'Back to home',
// Common
language: 'Language',
switch_to: '切换至中文',
// Environment Modal
env_modal_title: 'Initialize ABM Environment',
env_modal_subtitle: 'Agent-Based Models require a spatial subject. Would you like to add a visualization space?',
env_grid: 'Grid Space',
env_grid_desc: 'Discrete discrete cells (e.g., Schelling model).',
env_network: 'Network Space',
env_network_desc: 'Nodes and edges representing relationships.',
env_continuous: 'Continuous Space',
env_continuous_desc: 'Continuous coordinates for fluid movement.',
env_gis: 'GIS Space',
env_gis_desc: 'Real geography based on map coordinates.',
env_skip: 'Skip for now',
env_confirm: 'Initialize Space',
env_dim: 'Dimensions',
env_width: 'Width',
env_height: 'Height',
env_connections: 'Connections',
env_horizontal_wrap: 'Horizontal Wrap',
env_vertical_wrap: 'Vertical Wrap',
voice_input: 'Voice Input',
add_agent: 'Add Agent',
add_space: 'Add Space Hub',
},
zh: {
// SciHub
scihub_title: '科学中心',
resources: '资源',
documentation: '文档',
video_tutorials: '视频教程',
api_specs: 'API 规范',
community: '社区',
forum: '论坛',
settings: '设置',
coming_soon: '即将在下次更新中推出!',
trending: '热门:',
hero_title: '科学知识库',
hero_subtitle: '探索数百万篇 VISA 格式的文献论文。让科学见解触手可及且可重现。',
search_placeholder: '搜索 VISA 格式论文...',
search_button: '搜索',
researcher_journey: '研究者之旅',
onboarding_path: '初次来到 SciHub?跟随这个沉浸式入门路径。',
view_roadmap: '查看路线图',
setup_env: '环境搭建',
setup_env_desc: '配置您的工作区和本地 API 密钥以进行数据获取。',
understand_visa: '理解 VISA',
understand_visa_desc: '学习虚拟集成科学架构格式。',
run_first_model: '运行首个模型',
run_first_model_desc: '从案例库中执行一个示例仿真。',
contribute: '贡献',
contribute_desc: '向社区发布您的发现和模型。',
case_library: '经典案例库',
case_library_desc: '准备好进行探索和复制的基础模型。',
reproduce_one_click: '一键复现',
model_library: '模型库',
model_library_desc: '管理您的个人工作流并发现共享资产。',
view_details: '查看详情',
start_reading: '开始阅读',
// OmniEditor
omnieditor_title: '全能模型',
project_settings: '项目设置',
run_model: '运行模型',
visa_connected: 'VISA 已连接',
new_model_draft: '新建模型草稿',
active_projects: '进行中的项目',
model_drafts: '模型草稿',
visa_storage: 'VISA 存储',
ai_assistant: 'AI 助手',
ai_researcher: 'AI 研究员',
user: '用户',
visual_canvas: '可视化画布',
logic_blocks: '逻辑区块',
parameters: '参数',
import_data: '导入数据',
export_data: '导出数据',
variable_name: '变量名称',
type: '类型',
distribution: '分布',
initial_value: '初始值',
min: '最小值',
max: '最大值',
description: '描述',
variable_params: '变量参数',
logic_validation: '逻辑验证',
missing_value: '缺失值',
type_mismatch: '类型不匹配',
real_time_monitoring: '实时监测',
run_deep_scan: '运行深度扫描',
agentpy_preview: 'Agentpy 预览',
auto_generated: '自动生成',
copy_code: '复制代码',
simulation_settings: '仿真设置',
live_feed: '实时画面',
population_distribution: '种群分布',
system_logs: '系统日志',
// Landing
landing_product: '产品',
landing_enterprise: '企业',
landing_industries: '行业',
landing_solutions: '解决方案',
landing_pricing: '价格',
landing_my_account: '我的账户',
landing_contact_sales: '联系销售',
landing_launch_app: '启动应用',
landing_hero_title: '科学建模与仿真平台',
landing_hero_subtitle: '使用更好的 VISA 工具设计更好的科学模型。',
landing_cta: '立即开始您的研究',
// Login
login_title: '登录 SciHub',
login_subtitle: '输入您的凭据以访问您的研究工作区。',
email: '电子邮箱',
password: '密码',
forgot_password: '忘记密码?',
sign_in: '登录',
or_continue_with: '或通过以下方式继续',
google_sign_in: '使用 Google 登录',
no_account: '还没有账号?',
sign_up: '注册',
back_to_home: '返回首页',
// Common
language: '语言',
switch_to: 'Switch to English',
// Environment Modal
env_modal_title: '初始化 ABM 环境',
env_modal_subtitle: 'Agent-Based Model 默认需要一个环境主体。您是否要增加一个可视化空间主体?',
env_grid: '网格空间',
env_grid_desc: '离散的网格单元(例如:谢林隔离模型)。',
env_network: '网络空间',
env_network_desc: '代表节点与连线之间的关系网络。',
env_continuous: '连续空间',
env_continuous_desc: '支持流体运动的连续坐标空间。',
env_gis: 'GIS 真实地理空间',
env_gis_desc: '基于真实地图坐标的地理空间。',
env_skip: '稍后添加',
env_confirm: '初始化空间',
env_dim: '尺寸布局',
env_width: '宽度',
env_height: '高度',
env_connections: '连接性',
env_horizontal_wrap: '左右相连 (周期性边界)',
env_vertical_wrap: '上下相连 (周期性边界)',
voice_input: '语音输入',
add_agent: '添加 Agent 代理',
add_space: '添加 Space 空间枢纽',
}
};
+25
View File
@@ -0,0 +1,25 @@
export interface Project {
id: string;
name: string;
type: string;
status: 'Verified' | 'Draft';
visibility: 'Private' | 'Shared' | 'Public';
updatedAt: string;
}
export interface Parameter {
id: string;
name: string;
type: 'Float' | 'Integer' | 'Boolean';
distribution: 'Normal' | 'Uniform' | 'Constant';
initialValue: string;
min: string;
max: string;
description: string;
}
export interface ChatMessage {
role: 'assistant' | 'user';
content: string;
timestamp: number;
}
+26
View File
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": [
"./*"
]
},
"allowImportingTsExtensions": true,
"noEmit": true
}
}
+24
View File
@@ -0,0 +1,24 @@
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import {defineConfig, loadEnv} from 'vite';
export default defineConfig(({mode}) => {
const env = loadEnv(mode, '.', '');
return {
plugins: [react(), tailwindcss()],
define: {
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
},
},
server: {
// HMR is disabled in AI Studio via DISABLE_HMR env var.
// Do not modify—file watching is disabled to prevent flickering during agent edits.
hmr: process.env.DISABLE_HMR !== 'true',
},
};
});