Spaces:
Running
Running
Upload 170 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +71 -0
- .gitattributes +3 -0
- .github/workflows/fly-deploy.yml +18 -0
- .gitignore +101 -0
- .idea/.gitignore +10 -0
- .idea/QuantumFieldKit.iml +22 -0
- .idea/copilot.data.migration.agent.xml +6 -0
- .idea/copilot.data.migration.ask.xml +6 -0
- .idea/copilot.data.migration.edit.xml +6 -0
- .idea/inspectionProfiles/Project_Default.xml +6 -0
- .idea/inspectionProfiles/profiles_settings.xml +6 -0
- .idea/misc.xml +4 -0
- .idea/modules.xml +8 -0
- .idea/vcs.xml +6 -0
- .idea/workspace.xml +74 -0
- Dockerfile +66 -0
- HUGGINGFACE_DEPLOYMENT.md +166 -0
- LICENSE +21 -0
- README.md +246 -11
- README_HF.md +28 -0
- __init__.py +0 -0
- app.py +1453 -0
- docker-compose.yml +46 -0
- fly.toml +44 -0
- frontend/package-lock.json +0 -0
- frontend/package.json +53 -0
- frontend/postcss.config.js +8 -0
- frontend/public/data/glossary_terms.json +218 -0
- frontend/public/index.html +40 -0
- frontend/src/App.js +78 -0
- frontend/src/components/AccessibilityProvider.js +122 -0
- frontend/src/components/ErrorBoundary.js +125 -0
- frontend/src/components/FloatingActionButton.js +72 -0
- frontend/src/components/Footer.js +54 -0
- frontend/src/components/Navbar.js +221 -0
- frontend/src/components/QuantumParticles.js +116 -0
- frontend/src/components/SkeletonLoader.js +52 -0
- frontend/src/components/plugin/EnhancedPluginParameterForm.js +297 -0
- frontend/src/components/plugin/EnhancedPluginResultsPanel.js +333 -0
- frontend/src/components/plugin/FullscreenCircuitViewer.js +205 -0
- frontend/src/components/plugin/PluginExplanation.js +35 -0
- frontend/src/components/plugin/PluginLayout.css +27 -0
- frontend/src/components/plugin/PluginLayout.js +23 -0
- frontend/src/components/plugin/PluginParameterForm.js +87 -0
- frontend/src/components/plugin/PluginResultsPanel.js +82 -0
- frontend/src/components/plugin/QuantumVisualization.js +393 -0
- frontend/src/components/plugin/SimpleTabs.js +67 -0
- frontend/src/design-system/components/Button.js +87 -0
- frontend/src/design-system/components/Card.js +129 -0
- frontend/src/design-system/components/Input.js +162 -0
.dockerignore
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Git files
|
| 2 |
+
.git
|
| 3 |
+
.gitignore
|
| 4 |
+
|
| 5 |
+
# Python cache files
|
| 6 |
+
__pycache__/
|
| 7 |
+
*.py[cod]
|
| 8 |
+
*$py.class
|
| 9 |
+
*.so
|
| 10 |
+
.Python
|
| 11 |
+
env/
|
| 12 |
+
build/
|
| 13 |
+
develop-eggs/
|
| 14 |
+
dist/
|
| 15 |
+
downloads/
|
| 16 |
+
eggs/
|
| 17 |
+
.eggs/
|
| 18 |
+
lib/
|
| 19 |
+
lib64/
|
| 20 |
+
parts/
|
| 21 |
+
sdist/
|
| 22 |
+
var/
|
| 23 |
+
*.egg-info/
|
| 24 |
+
.installed.cfg
|
| 25 |
+
*.egg
|
| 26 |
+
|
| 27 |
+
# Virtual environment
|
| 28 |
+
venv/
|
| 29 |
+
ENV/
|
| 30 |
+
|
| 31 |
+
# Node.js files
|
| 32 |
+
node_modules/
|
| 33 |
+
frontend/node_modules/
|
| 34 |
+
npm-debug.log*
|
| 35 |
+
yarn-debug.log*
|
| 36 |
+
yarn-error.log*
|
| 37 |
+
frontend/build/
|
| 38 |
+
.npm
|
| 39 |
+
.yarn
|
| 40 |
+
|
| 41 |
+
# IDE files
|
| 42 |
+
.idea/
|
| 43 |
+
.vscode/
|
| 44 |
+
*.swp
|
| 45 |
+
*.swo
|
| 46 |
+
|
| 47 |
+
# Logs
|
| 48 |
+
*.log
|
| 49 |
+
logs/
|
| 50 |
+
|
| 51 |
+
# Local configuration
|
| 52 |
+
.env
|
| 53 |
+
.env.local
|
| 54 |
+
.env.development.local
|
| 55 |
+
.env.test.local
|
| 56 |
+
.env.production.local
|
| 57 |
+
|
| 58 |
+
# Temp files
|
| 59 |
+
.DS_Store
|
| 60 |
+
Thumbs.db
|
| 61 |
+
*.tmp
|
| 62 |
+
*.temp
|
| 63 |
+
|
| 64 |
+
# Documentation
|
| 65 |
+
README.md
|
| 66 |
+
DEPLOYMENT.md
|
| 67 |
+
*.md
|
| 68 |
+
|
| 69 |
+
# Docker files
|
| 70 |
+
Dockerfile*
|
| 71 |
+
docker-compose*
|
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
image.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
static/img/favicon.ico filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
static/img/image.png filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/fly-deploy.yml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://fly.io/docs/app-guides/continuous-deployment-with-Facebook-actions/
|
| 2 |
+
|
| 3 |
+
name: Fly Deploy
|
| 4 |
+
on:
|
| 5 |
+
push:
|
| 6 |
+
branches:
|
| 7 |
+
- main
|
| 8 |
+
jobs:
|
| 9 |
+
deploy:
|
| 10 |
+
name: Deploy app
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
concurrency: deploy-group # optional: ensure only one action runs at a time
|
| 13 |
+
steps:
|
| 14 |
+
- uses: actions/checkout@v4
|
| 15 |
+
- uses: superfly/flyctl-actions/setup-flyctl@master
|
| 16 |
+
- run: flyctl deploy --remote-only
|
| 17 |
+
env:
|
| 18 |
+
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
.gitignore
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
build/
|
| 8 |
+
develop-eggs/
|
| 9 |
+
dist/
|
| 10 |
+
downloads/
|
| 11 |
+
eggs/
|
| 12 |
+
.eggs/
|
| 13 |
+
lib/
|
| 14 |
+
lib64/
|
| 15 |
+
parts/
|
| 16 |
+
sdist/
|
| 17 |
+
var/
|
| 18 |
+
wheels/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
MANIFEST
|
| 23 |
+
|
| 24 |
+
# Virtual environments
|
| 25 |
+
.env
|
| 26 |
+
.venv
|
| 27 |
+
env/
|
| 28 |
+
venv/
|
| 29 |
+
ENV/
|
| 30 |
+
env.bak/
|
| 31 |
+
venv.bak/
|
| 32 |
+
|
| 33 |
+
# Environment variables
|
| 34 |
+
.env
|
| 35 |
+
.env.local
|
| 36 |
+
.env.development.local
|
| 37 |
+
.env.test.local
|
| 38 |
+
.env.production.local
|
| 39 |
+
|
| 40 |
+
# Node.js dependencies
|
| 41 |
+
node_modules/
|
| 42 |
+
frontend/node_modules/
|
| 43 |
+
npm-debug.log*
|
| 44 |
+
yarn-debug.log*
|
| 45 |
+
yarn-error.log*
|
| 46 |
+
|
| 47 |
+
# React build output
|
| 48 |
+
frontend/build/
|
| 49 |
+
frontend/dist/
|
| 50 |
+
|
| 51 |
+
# Static build artifacts
|
| 52 |
+
static/media/
|
| 53 |
+
|
| 54 |
+
# Package files
|
| 55 |
+
package-lock.json
|
| 56 |
+
yarn.lock
|
| 57 |
+
|
| 58 |
+
# Logs
|
| 59 |
+
*.log
|
| 60 |
+
logs/
|
| 61 |
+
*.log.*
|
| 62 |
+
|
| 63 |
+
# IDE and editor files
|
| 64 |
+
.vscode/
|
| 65 |
+
.idea/
|
| 66 |
+
*.swp
|
| 67 |
+
*.swo
|
| 68 |
+
*~
|
| 69 |
+
|
| 70 |
+
# OS generated files
|
| 71 |
+
.DS_Store
|
| 72 |
+
.DS_Store?
|
| 73 |
+
._*
|
| 74 |
+
.Spotlight-V100
|
| 75 |
+
.Trashes
|
| 76 |
+
ehthumbs.db
|
| 77 |
+
Thumbs.db
|
| 78 |
+
|
| 79 |
+
# Temporary files
|
| 80 |
+
*.tmp
|
| 81 |
+
*.temp
|
| 82 |
+
.cache/
|
| 83 |
+
|
| 84 |
+
# Coverage reports
|
| 85 |
+
htmlcov/
|
| 86 |
+
.coverage
|
| 87 |
+
.coverage.*
|
| 88 |
+
coverage.xml
|
| 89 |
+
*.cover
|
| 90 |
+
.hypothesis/
|
| 91 |
+
.pytest_cache/
|
| 92 |
+
|
| 93 |
+
# Jupyter Notebook
|
| 94 |
+
.ipynb_checkpoints
|
| 95 |
+
|
| 96 |
+
# Flask
|
| 97 |
+
instance/
|
| 98 |
+
.webassets-cache
|
| 99 |
+
|
| 100 |
+
# Docker
|
| 101 |
+
.dockerignore
|
.idea/.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Default ignored files
|
| 2 |
+
/shelf/
|
| 3 |
+
/workspace.xml
|
| 4 |
+
# Ignored default folder with query files
|
| 5 |
+
/queries/
|
| 6 |
+
# Datasource local storage ignored files
|
| 7 |
+
/dataSources/
|
| 8 |
+
/dataSources.local.xml
|
| 9 |
+
# Editor-based HTTP Client requests
|
| 10 |
+
/httpRequests/
|
.idea/QuantumFieldKit.iml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<module type="PYTHON_MODULE" version="4">
|
| 3 |
+
<component name="NewModuleRootManager">
|
| 4 |
+
<content url="file://$MODULE_DIR$">
|
| 5 |
+
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
| 6 |
+
</content>
|
| 7 |
+
<orderEntry type="jdk" jdkName="Python 3.13 (QuantumFieldKit)" jdkType="Python SDK" />
|
| 8 |
+
<orderEntry type="sourceFolder" forTests="false" />
|
| 9 |
+
</component>
|
| 10 |
+
<component name="PyDocumentationSettings">
|
| 11 |
+
<option name="format" value="PLAIN" />
|
| 12 |
+
<option name="myDocStringFormat" value="Plain" />
|
| 13 |
+
</component>
|
| 14 |
+
<component name="TemplatesService">
|
| 15 |
+
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
|
| 16 |
+
<option name="TEMPLATE_FOLDERS">
|
| 17 |
+
<list>
|
| 18 |
+
<option value="$MODULE_DIR$/templates" />
|
| 19 |
+
</list>
|
| 20 |
+
</option>
|
| 21 |
+
</component>
|
| 22 |
+
</module>
|
.idea/copilot.data.migration.agent.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="AgentMigrationStateService">
|
| 4 |
+
<option name="migrationStatus" value="COMPLETED" />
|
| 5 |
+
</component>
|
| 6 |
+
</project>
|
.idea/copilot.data.migration.ask.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="AskMigrationStateService">
|
| 4 |
+
<option name="migrationStatus" value="COMPLETED" />
|
| 5 |
+
</component>
|
| 6 |
+
</project>
|
.idea/copilot.data.migration.edit.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="EditMigrationStateService">
|
| 4 |
+
<option name="migrationStatus" value="COMPLETED" />
|
| 5 |
+
</component>
|
| 6 |
+
</project>
|
.idea/inspectionProfiles/Project_Default.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<component name="InspectionProjectProfileManager">
|
| 2 |
+
<profile version="1.0">
|
| 3 |
+
<option name="myName" value="Project Default" />
|
| 4 |
+
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
| 5 |
+
</profile>
|
| 6 |
+
</component>
|
.idea/inspectionProfiles/profiles_settings.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<component name="InspectionProjectProfileManager">
|
| 2 |
+
<settings>
|
| 3 |
+
<option name="USE_PROJECT_PROFILE" value="false" />
|
| 4 |
+
<version value="1.0" />
|
| 5 |
+
</settings>
|
| 6 |
+
</component>
|
.idea/misc.xml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (QuantumFieldKit)" project-jdk-type="Python SDK" />
|
| 4 |
+
</project>
|
.idea/modules.xml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="ProjectModuleManager">
|
| 4 |
+
<modules>
|
| 5 |
+
<module fileurl="file://$PROJECT_DIR$/.idea/QuantumFieldKit.iml" filepath="$PROJECT_DIR$/.idea/QuantumFieldKit.iml" />
|
| 6 |
+
</modules>
|
| 7 |
+
</component>
|
| 8 |
+
</project>
|
.idea/vcs.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="VcsDirectoryMappings">
|
| 4 |
+
<mapping directory="" vcs="Git" />
|
| 5 |
+
</component>
|
| 6 |
+
</project>
|
.idea/workspace.xml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="AutoImportSettings">
|
| 4 |
+
<option name="autoReloadType" value="SELECTIVE" />
|
| 5 |
+
</component>
|
| 6 |
+
<component name="ChangeListManager">
|
| 7 |
+
<list default="true" id="13f18b08-acbd-4bdc-87f0-1f84f46c3585" name="Changes" comment="" />
|
| 8 |
+
<option name="SHOW_DIALOG" value="false" />
|
| 9 |
+
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
| 10 |
+
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
| 11 |
+
<option name="LAST_RESOLUTION" value="IGNORE" />
|
| 12 |
+
</component>
|
| 13 |
+
<component name="FlaskConsoleOptions" custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform)); sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) from flask.cli import ScriptInfo, NoAppException for module in ["main.py", "wsgi.py", "app.py"]: try: locals().update(ScriptInfo(app_import_path=module, create_app=None).load_app().make_shell_context()); print("\nFlask App: %s" % app.import_name); break except NoAppException: pass">
|
| 14 |
+
<envs>
|
| 15 |
+
<env key="FLASK_APP" value="app" />
|
| 16 |
+
</envs>
|
| 17 |
+
<option name="myCustomStartScript" value="import sys; print('Python %s on %s' % (sys.version, sys.platform)); sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) from flask.cli import ScriptInfo, NoAppException for module in ["main.py", "wsgi.py", "app.py"]: try: locals().update(ScriptInfo(app_import_path=module, create_app=None).load_app().make_shell_context()); print("\nFlask App: %s" % app.import_name); break except NoAppException: pass" />
|
| 18 |
+
<option name="myEnvs">
|
| 19 |
+
<map>
|
| 20 |
+
<entry key="FLASK_APP" value="app" />
|
| 21 |
+
</map>
|
| 22 |
+
</option>
|
| 23 |
+
</component>
|
| 24 |
+
<component name="Git.Settings">
|
| 25 |
+
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
| 26 |
+
</component>
|
| 27 |
+
<component name="ProjectColorInfo">{
|
| 28 |
+
"associatedIndex": 8
|
| 29 |
+
}</component>
|
| 30 |
+
<component name="ProjectId" id="33dH1si7wAg3HCmgZrxnQXxBaQj" />
|
| 31 |
+
<component name="ProjectViewState">
|
| 32 |
+
<option name="hideEmptyMiddlePackages" value="true" />
|
| 33 |
+
<option name="showLibraryContents" value="true" />
|
| 34 |
+
</component>
|
| 35 |
+
<component name="PropertiesComponent">{
|
| 36 |
+
"keyToString": {
|
| 37 |
+
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
| 38 |
+
"RunOnceActivity.ShowReadmeOnStart": "true",
|
| 39 |
+
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
| 40 |
+
"RunOnceActivity.git.unshallow": "true",
|
| 41 |
+
"git-widget-placeholder": "main",
|
| 42 |
+
"node.js.detected.package.eslint": "true",
|
| 43 |
+
"node.js.detected.package.tslint": "true",
|
| 44 |
+
"node.js.selected.package.eslint": "(autodetect)",
|
| 45 |
+
"node.js.selected.package.tslint": "(autodetect)",
|
| 46 |
+
"nodejs_package_manager_path": "npm",
|
| 47 |
+
"settings.editor.selected.configurable": "preferences.pluginManager",
|
| 48 |
+
"vue.rearranger.settings.migration": "true"
|
| 49 |
+
}
|
| 50 |
+
}</component>
|
| 51 |
+
<component name="SharedIndexes">
|
| 52 |
+
<attachedChunks>
|
| 53 |
+
<set>
|
| 54 |
+
<option value="bundled-js-predefined-d6986cc7102b-256f9dae541f-JavaScript-PY-253.20558.58" />
|
| 55 |
+
<option value="bundled-python-sdk-7e710ed067e7-8e468156c80b-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.20558.58" />
|
| 56 |
+
</set>
|
| 57 |
+
</attachedChunks>
|
| 58 |
+
</component>
|
| 59 |
+
<component name="TaskManager">
|
| 60 |
+
<task active="true" id="Default" summary="Default task">
|
| 61 |
+
<changelist id="13f18b08-acbd-4bdc-87f0-1f84f46c3585" name="Changes" comment="" />
|
| 62 |
+
<created>1759639829082</created>
|
| 63 |
+
<option name="number" value="Default" />
|
| 64 |
+
<option name="presentableId" value="Default" />
|
| 65 |
+
<updated>1759639829082</updated>
|
| 66 |
+
<workItem from="1759639837125" duration="2723000" />
|
| 67 |
+
<workItem from="1759642793213" duration="40000" />
|
| 68 |
+
</task>
|
| 69 |
+
<servers />
|
| 70 |
+
</component>
|
| 71 |
+
<component name="TypeScriptGeneratedFilesManager">
|
| 72 |
+
<option name="version" value="3" />
|
| 73 |
+
</component>
|
| 74 |
+
</project>
|
Dockerfile
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multi-stage build for better optimization
|
| 2 |
+
FROM node:18-alpine AS frontend-builder
|
| 3 |
+
|
| 4 |
+
WORKDIR /app/frontend
|
| 5 |
+
|
| 6 |
+
# Copy package files
|
| 7 |
+
COPY frontend/package*.json ./
|
| 8 |
+
|
| 9 |
+
# Clear npm cache and install dependencies
|
| 10 |
+
RUN npm cache clean --force && \
|
| 11 |
+
(npm ci --no-audit --no-fund || npm install --no-audit --no-fund)
|
| 12 |
+
|
| 13 |
+
# Copy frontend source code
|
| 14 |
+
COPY frontend/ ./
|
| 15 |
+
|
| 16 |
+
# Build the frontend
|
| 17 |
+
RUN npm run build
|
| 18 |
+
|
| 19 |
+
# Python backend stage
|
| 20 |
+
FROM python:3.11-slim AS backend
|
| 21 |
+
|
| 22 |
+
# Install system dependencies
|
| 23 |
+
RUN apt-get update && apt-get install -y \
|
| 24 |
+
gcc \
|
| 25 |
+
g++ \
|
| 26 |
+
curl \
|
| 27 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 28 |
+
|
| 29 |
+
# Create non-root user for security
|
| 30 |
+
RUN groupadd -r appuser && useradd -r -g appuser appuser
|
| 31 |
+
|
| 32 |
+
WORKDIR /app
|
| 33 |
+
|
| 34 |
+
# Copy and install Python dependencies
|
| 35 |
+
COPY requirements.txt .
|
| 36 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 37 |
+
|
| 38 |
+
# Copy application code
|
| 39 |
+
COPY . .
|
| 40 |
+
|
| 41 |
+
# Copy built frontend from previous stage
|
| 42 |
+
COPY --from=frontend-builder /app/frontend/build ./frontend/build
|
| 43 |
+
|
| 44 |
+
# Create directories with proper permissions for containerized environments
|
| 45 |
+
RUN mkdir -p /tmp/logs /tmp/matplotlib && \
|
| 46 |
+
chown -R appuser:appuser /app /tmp/logs /tmp/matplotlib
|
| 47 |
+
|
| 48 |
+
# Switch to non-root user
|
| 49 |
+
USER appuser
|
| 50 |
+
|
| 51 |
+
# Set environment variables for containerized environments
|
| 52 |
+
ENV PORT=8080
|
| 53 |
+
ENV HOST=0.0.0.0
|
| 54 |
+
ENV FLASK_ENV=production
|
| 55 |
+
ENV PYTHONPATH=/app
|
| 56 |
+
ENV LOG_DIR=/tmp/logs
|
| 57 |
+
ENV MPLCONFIGDIR=/tmp/matplotlib
|
| 58 |
+
|
| 59 |
+
EXPOSE 8080
|
| 60 |
+
|
| 61 |
+
# Health check
|
| 62 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 63 |
+
CMD curl -f http://localhost:8080/api/plugins || exit 1
|
| 64 |
+
|
| 65 |
+
# Use our startup script which ensures directories are properly configured
|
| 66 |
+
CMD ["python", "startup_hf.py"]
|
HUGGINGFACE_DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Deploying QuantumFieldKit on Hugging Face Spaces
|
| 2 |
+
|
| 3 |
+
This guide explains how to deploy QuantumFieldKit on Hugging Face Spaces.
|
| 4 |
+
|
| 5 |
+
## Prerequisites
|
| 6 |
+
|
| 7 |
+
1. A Hugging Face account
|
| 8 |
+
2. A Space repository on Hugging Face
|
| 9 |
+
|
| 10 |
+
## Deployment Steps
|
| 11 |
+
|
| 12 |
+
### 1. Create a New Space
|
| 13 |
+
|
| 14 |
+
1. Go to [Hugging Face Spaces](https://huggingface.co/spaces)
|
| 15 |
+
2. Click "New Space"
|
| 16 |
+
3. Choose a name for your space
|
| 17 |
+
4. Select "Docker" as the Space SDK
|
| 18 |
+
5. Choose your visibility settings (Public/Private)
|
| 19 |
+
|
| 20 |
+
### 2. Configure Your Space
|
| 21 |
+
|
| 22 |
+
In your Space repository, create or update the following files:
|
| 23 |
+
|
| 24 |
+
#### `requirements.txt`
|
| 25 |
+
|
| 26 |
+
Use the optimized requirements file:
|
| 27 |
+
|
| 28 |
+
```
|
| 29 |
+
# Requirements specifically optimized for Hugging Face Spaces deployment
|
| 30 |
+
flask>=2.3.0,<3.0.0
|
| 31 |
+
flask-cors>=4.0.0
|
| 32 |
+
flask-socketio>=5.3.2
|
| 33 |
+
Werkzeug>=2.3.0,<3.0.0
|
| 34 |
+
cirq-core>=1.1.0
|
| 35 |
+
numpy>=1.24.0,<2.0.0
|
| 36 |
+
sympy>=1.11.1
|
| 37 |
+
eventlet>=0.33.3
|
| 38 |
+
gunicorn>=20.1.0,<22.0.0
|
| 39 |
+
psutil>=5.9.0
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
#### `Dockerfile`
|
| 43 |
+
|
| 44 |
+
Create a Dockerfile that's optimized for Hugging Face Spaces:
|
| 45 |
+
|
| 46 |
+
```dockerfile
|
| 47 |
+
FROM python:3.11-slim
|
| 48 |
+
|
| 49 |
+
# Install system dependencies
|
| 50 |
+
RUN apt-get update && apt-get install -y \
|
| 51 |
+
gcc \
|
| 52 |
+
g++ \
|
| 53 |
+
curl \
|
| 54 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 55 |
+
|
| 56 |
+
WORKDIR /app
|
| 57 |
+
|
| 58 |
+
# Copy and install Python dependencies
|
| 59 |
+
COPY requirements.txt .
|
| 60 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 61 |
+
|
| 62 |
+
# Copy application code
|
| 63 |
+
COPY . .
|
| 64 |
+
|
| 65 |
+
# Create directories with proper permissions for containerized environments
|
| 66 |
+
RUN mkdir -p /tmp/logs /tmp/matplotlib
|
| 67 |
+
|
| 68 |
+
# Set environment variables for containerized environments
|
| 69 |
+
ENV PORT=7860
|
| 70 |
+
ENV HOST=0.0.0.0
|
| 71 |
+
ENV FLASK_ENV=production
|
| 72 |
+
ENV LOG_DIR=/tmp/logs
|
| 73 |
+
ENV MPLCONFIGDIR=/tmp/matplotlib
|
| 74 |
+
|
| 75 |
+
EXPOSE 7860
|
| 76 |
+
|
| 77 |
+
# Use our startup script which ensures directories are properly configured
|
| 78 |
+
CMD ["python", "startup_hf.py"]
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
### 3. Push Your Code
|
| 82 |
+
|
| 83 |
+
Push your QuantumFieldKit code to the Space repository:
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
git clone https://huggingface.co/spaces/username/your-space-name
|
| 87 |
+
cd your-space-name
|
| 88 |
+
# Add your QuantumFieldKit files here
|
| 89 |
+
git add .
|
| 90 |
+
git commit -m "Add QuantumFieldKit"
|
| 91 |
+
git push
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
### 4. Environment Variables
|
| 95 |
+
|
| 96 |
+
The following environment variables are automatically set for optimal performance on Hugging Face Spaces:
|
| 97 |
+
|
| 98 |
+
- `PORT`: The port the application should listen on (default: 7860)
|
| 99 |
+
- `HOST`: The host to bind to (default: 0.0.0.0)
|
| 100 |
+
- `FLASK_ENV`: Set to "production"
|
| 101 |
+
- `LOG_DIR`: Set to "/tmp/logs" (writable directory)
|
| 102 |
+
- `MPLCONFIGDIR`: Set to "/tmp/matplotlib" (writable directory)
|
| 103 |
+
|
| 104 |
+
### 5. Build and Deploy
|
| 105 |
+
|
| 106 |
+
After pushing your code, Hugging Face Spaces will automatically:
|
| 107 |
+
|
| 108 |
+
1. Build the Docker image using your Dockerfile
|
| 109 |
+
2. Install the dependencies from requirements.txt
|
| 110 |
+
3. Start the application using the [startup_hf.py](file:///d:/Projects/QuantumFieldKit/startup_hf.py) script
|
| 111 |
+
|
| 112 |
+
## Troubleshooting
|
| 113 |
+
|
| 114 |
+
### Permission Errors
|
| 115 |
+
|
| 116 |
+
If you encounter permission errors like:
|
| 117 |
+
|
| 118 |
+
```
|
| 119 |
+
PermissionError: [Errno 13] Permission denied: '/app/logs/quantum_field_kit_2025-10-05.log'
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
This is because the application is trying to write to a non-writable directory. The solution is to:
|
| 123 |
+
|
| 124 |
+
1. Use `/tmp` directory for logs (already configured in the Dockerfile)
|
| 125 |
+
2. Set the `LOG_DIR` environment variable to `/tmp/logs`
|
| 126 |
+
|
| 127 |
+
### Matplotlib Configuration Errors
|
| 128 |
+
|
| 129 |
+
If you see errors like:
|
| 130 |
+
|
| 131 |
+
```
|
| 132 |
+
mkdir -p failed for path /.config/matplotlib: [Errno 13] Permission denied: '/.config'
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
This is resolved by setting the `MPLCONFIGDIR` environment variable to a writable directory like `/tmp/matplotlib`.
|
| 136 |
+
|
| 137 |
+
The [startup_hf.py](file:///d:/Projects/QuantumFieldKit/startup_hf.py) script handles this automatically by:
|
| 138 |
+
|
| 139 |
+
1. Creating the required directories at startup
|
| 140 |
+
2. Testing write access to these directories
|
| 141 |
+
3. Falling back to alternative directories if needed
|
| 142 |
+
4. Setting the appropriate environment variables
|
| 143 |
+
|
| 144 |
+
## Performance Considerations
|
| 145 |
+
|
| 146 |
+
1. **Resource Limits**: Hugging Face Spaces have resource limits. Complex quantum simulations might take longer to execute.
|
| 147 |
+
|
| 148 |
+
2. **Timeout Settings**: The gunicorn timeout is set to 120 seconds to accommodate longer simulations.
|
| 149 |
+
|
| 150 |
+
3. **Memory Usage**: Monitor memory usage as quantum simulations can be memory-intensive.
|
| 151 |
+
|
| 152 |
+
## Customization
|
| 153 |
+
|
| 154 |
+
You can customize the deployment by:
|
| 155 |
+
|
| 156 |
+
1. Modifying the Dockerfile to include additional dependencies
|
| 157 |
+
2. Adjusting the gunicorn settings in the [startup_hf.py](file:///d:/Projects/QuantumFieldKit/startup_hf.py) script
|
| 158 |
+
3. Adding custom environment variables in the Hugging Face Space settings
|
| 159 |
+
|
| 160 |
+
## Support
|
| 161 |
+
|
| 162 |
+
For issues with deployment, please check:
|
| 163 |
+
|
| 164 |
+
1. The Hugging Face Space logs
|
| 165 |
+
2. Ensure all dependencies are correctly specified in requirements.txt
|
| 166 |
+
3. Verify the Dockerfile is correctly configured
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Mason Parle
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,11 +1,246 @@
|
|
| 1 |
-
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
--
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GXS - QuantumNexus
|
| 2 |
+
[](https://quantumfieldkit.com) [](LICENSE) [](https://www.python.org/) [](https://flask.palletsprojects.com/) [](https://quantumai.google/cirq) [](https://www.docker.com/)
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
##### Visit the live app: [quantumfieldkit.com](https://quantumfieldkit.com/)
|
| 6 |
+
|
| 7 |
+
## Project Overview
|
| 8 |
+
|
| 9 |
+
GXS - QuantumNexus is a web-based quantum computing simulation platform designed to bridge the gap between theoretical quantum mechanics and practical quantum computing education. The project aims to provide accessible visualizations and interactive demonstrations of quantum phenomena that are typically difficult to comprehend through traditional learning methods.
|
| 10 |
+
|
| 11 |
+
## Purpose & Motivation
|
| 12 |
+
|
| 13 |
+
### Why This Project Exists
|
| 14 |
+
|
| 15 |
+
The field of quantum computing is growing rapidly, but learning resources often fall into two categories:
|
| 16 |
+
1. Highly theoretical texts with complex mathematics
|
| 17 |
+
2. Oversimplified explanations that miss critical quantum concepts
|
| 18 |
+
|
| 19 |
+
GXS - QuantumNexus addresses this gap by providing:
|
| 20 |
+
- Visual, interactive representations of quantum states and operations
|
| 21 |
+
- Accurate simulations of quantum algorithms and protocols
|
| 22 |
+
- Educational content that connects theory to visual behavior
|
| 23 |
+
- A no-installation learning environment accessible via web browser
|
| 24 |
+
|
| 25 |
+
The project was created to democratize quantum computing education and provide both students and professionals with an intuitive understanding of quantum behavior through direct interaction and experimentation.
|
| 26 |
+
|
| 27 |
+
## Architecture
|
| 28 |
+
|
| 29 |
+
GXS - QuantumNexus follows a modern web application architecture with a focus on real-time interactivity and computational integrity.
|
| 30 |
+
|
| 31 |
+
### System Architecture Overview
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+

|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
### Component Breakdown
|
| 38 |
+
|
| 39 |
+
#### 1. Backend Architecture
|
| 40 |
+
|
| 41 |
+
- **Flask Application Layer**
|
| 42 |
+
- Routes for simulation plugins
|
| 43 |
+
- Parameter validation and security controls
|
| 44 |
+
- Template rendering with Jinja2
|
| 45 |
+
- Error handling and graceful degradation
|
| 46 |
+
|
| 47 |
+
- **Real-time Communication Layer**
|
| 48 |
+
- Socket.IO for bidirectional communication
|
| 49 |
+
- Progress updates during simulation execution
|
| 50 |
+
- Streaming results for long-running simulations
|
| 51 |
+
|
| 52 |
+
- **Quantum Simulation Layer**
|
| 53 |
+
- Google Cirq integration for quantum circuit simulation
|
| 54 |
+
- Modular plugin system for different quantum algorithms
|
| 55 |
+
- Advanced visualization data preparation
|
| 56 |
+
- Optimized for web-appropriate computation limits
|
| 57 |
+
|
| 58 |
+
#### 2. Frontend Architecture
|
| 59 |
+
|
| 60 |
+
- **User Interface Layer**
|
| 61 |
+
- Bootstrap 5 framework for responsive design
|
| 62 |
+
- Form-based interaction model for simulation parameters
|
| 63 |
+
- Tab-based result visualization
|
| 64 |
+
- Educational content modals
|
| 65 |
+
|
| 66 |
+
- **Visualization Layer**
|
| 67 |
+
- THREE.js for 3D quantum state visualization (Bloch sphere)
|
| 68 |
+
- Chart.js for statistical results and distributions
|
| 69 |
+
- SVG-based circuit diagram visualization
|
| 70 |
+
- Custom visualization adapters for different quantum protocols
|
| 71 |
+
|
| 72 |
+
- **Client-side Processing**
|
| 73 |
+
- JavaScript modules for post-processing simulation results
|
| 74 |
+
- Dynamic content generation from simulation data
|
| 75 |
+
- Browser-appropriate computation delegation
|
| 76 |
+
|
| 77 |
+
#### 3. Deployment Architecture
|
| 78 |
+
|
| 79 |
+
- **Containerization**
|
| 80 |
+
- Docker for consistent runtime environment
|
| 81 |
+
- Multi-stage build process for optimization
|
| 82 |
+
- Environment-based configuration
|
| 83 |
+
|
| 84 |
+
- **Hosting**
|
| 85 |
+
- Fly.io for global edge deployment
|
| 86 |
+
- Automated deployment pipeline
|
| 87 |
+
- Resource-constrained execution environment
|
| 88 |
+
|
| 89 |
+
### Data Flow
|
| 90 |
+
|
| 91 |
+
1. User configures simulation parameters through web interface
|
| 92 |
+
2. Parameters are validated and passed to the appropriate simulation plugin
|
| 93 |
+
3. Simulation runs on server using Google Cirq
|
| 94 |
+
4. Results streamed back to client via Socket.IO
|
| 95 |
+
5. Client-side processing transforms data into visualizations
|
| 96 |
+
6. Interactive visualization elements respond to user interaction
|
| 97 |
+
|
| 98 |
+
## Development Process & Technical Decisions
|
| 99 |
+
|
| 100 |
+
### Technology Selection Rationale
|
| 101 |
+
|
| 102 |
+
- **Flask**: Chosen for its lightweight nature and flexibility in designing API endpoints and serving web content
|
| 103 |
+
- **Google Cirq**: Selected as the quantum simulation library for its comprehensive gate implementations and support for noise models
|
| 104 |
+
- **Socket.IO**: Implemented to provide real-time feedback during computationally intensive simulations
|
| 105 |
+
- **THREE.js**: Used for 3D visualization of quantum states on the Bloch sphere, providing intuitive representation of qubit states
|
| 106 |
+
- **Bootstrap 5**: Selected for rapid UI development with responsive design principles
|
| 107 |
+
- **Docker + Fly.io**: Chosen for simplified deployment and scaling capabilities
|
| 108 |
+
|
| 109 |
+
### Development Workflow
|
| 110 |
+
|
| 111 |
+
The project was developed through several phases:
|
| 112 |
+
|
| 113 |
+
1. **Research & Planning**
|
| 114 |
+
- Study of quantum algorithms and their visual representation needs
|
| 115 |
+
- Identification of key educational quantum protocols
|
| 116 |
+
- User experience mapping for quantum concept exploration
|
| 117 |
+
|
| 118 |
+
2. **Core Architecture Development**
|
| 119 |
+
- Flask application setup with plugin architecture
|
| 120 |
+
- Integration of Cirq simulation capabilities
|
| 121 |
+
- Real-time communication layer implementation
|
| 122 |
+
|
| 123 |
+
3. **Quantum Simulation Implementation**
|
| 124 |
+
- Protocol-by-protocol implementation of quantum simulations
|
| 125 |
+
- Enhanced with realistic physics effects (noise, error, etc.)
|
| 126 |
+
- Development of detailed logging for educational insights
|
| 127 |
+
|
| 128 |
+
4. **Visualization Layer Development**
|
| 129 |
+
- Custom visualization components for quantum states
|
| 130 |
+
- Circuit diagram rendering with interactive elements
|
| 131 |
+
- Adaptive visualizations for different quantum phenomena
|
| 132 |
+
|
| 133 |
+
5. **Educational Content Creation**
|
| 134 |
+
- Writing accessible explanations of quantum concepts
|
| 135 |
+
- Creating analogies and simplified models for complex ideas
|
| 136 |
+
- Structuring content to complement interactive elements
|
| 137 |
+
|
| 138 |
+
6. **Deployment & Optimization**
|
| 139 |
+
- Containerization with Docker
|
| 140 |
+
- Performance optimization for web environment
|
| 141 |
+
- Security hardening and input validation
|
| 142 |
+
|
| 143 |
+
### Technical Challenges Overcome
|
| 144 |
+
|
| 145 |
+
- **Computation Balance**: Finding the right balance between simulation accuracy and web performance
|
| 146 |
+
- **Visualization Complexity**: Creating intuitive visualizations for non-intuitive quantum behavior
|
| 147 |
+
- **Cross-browser Compatibility**: Ensuring consistent 3D visualization across different browsers
|
| 148 |
+
- **Resource Constraints**: Optimizing simulations to work within memory and computation limits of web environments
|
| 149 |
+
|
| 150 |
+
## Key Features & Implementation Details
|
| 151 |
+
|
| 152 |
+
### Quantum Protocols Implementation
|
| 153 |
+
|
| 154 |
+
#### BB84 Protocol (Quantum Key Distribution)
|
| 155 |
+
- Implementation of realistic channel effects (noise, loss)
|
| 156 |
+
- Visual pipeline of the BB84 steps from qubit generation to secure key
|
| 157 |
+
- Eavesdropper simulation with detection probability
|
| 158 |
+
|
| 159 |
+
#### Quantum Teleportation
|
| 160 |
+
- Full circuit implementation with Bell state preparation
|
| 161 |
+
- Step-by-step visualization of the teleportation process
|
| 162 |
+
- Detailed mathematical explanation of quantum operations
|
| 163 |
+
|
| 164 |
+
#### Quantum Network (Entanglement Swapping)
|
| 165 |
+
- Multi-node quantum network simulation
|
| 166 |
+
- Visualization of entanglement distribution
|
| 167 |
+
- Correlation measurements across network nodes
|
| 168 |
+
|
| 169 |
+
### Quantum Algorithms Implementation
|
| 170 |
+
|
| 171 |
+
#### Grover's Algorithm
|
| 172 |
+
- Variable qubit implementation with adjustable search targets
|
| 173 |
+
- Amplitude visualization throughout the algorithm steps
|
| 174 |
+
- Performance comparison with classical search
|
| 175 |
+
|
| 176 |
+
#### Quantum Fourier Transform
|
| 177 |
+
- Interactive representation of quantum basis transformation
|
| 178 |
+
- Phase visualization before and after transformation
|
| 179 |
+
- Applications in other quantum algorithms explained
|
| 180 |
+
|
| 181 |
+
#### QAOA (Quantum Approximate Optimization Algorithm)
|
| 182 |
+
- Graph problem visualization
|
| 183 |
+
- Solution quality analysis
|
| 184 |
+
- Classical/quantum hybrid approach demonstration
|
| 185 |
+
|
| 186 |
+
### Core Visualization Components
|
| 187 |
+
|
| 188 |
+
#### Bloch Sphere Visualization
|
| 189 |
+
- Interactive 3D representation of qubit states
|
| 190 |
+
- Real-time state updates based on quantum operations
|
| 191 |
+
- Custom implementation using THREE.js with optimizations
|
| 192 |
+
|
| 193 |
+
#### Quantum Circuit Diagrams
|
| 194 |
+
- SVG-based circuit rendering
|
| 195 |
+
- Gate-by-gate visualization with educational tooltips
|
| 196 |
+
- Downloadable circuit diagrams for educational use
|
| 197 |
+
|
| 198 |
+
## Resources & Dependencies
|
| 199 |
+
|
| 200 |
+
### Core Libraries & Frameworks
|
| 201 |
+
|
| 202 |
+
#### Backend
|
| 203 |
+
- **Flask**: Web application framework
|
| 204 |
+
- **Werkzeug**: WSGI web application toolkit
|
| 205 |
+
- **Flask-SocketIO**: WebSocket communication
|
| 206 |
+
- **Eventlet**: Concurrent networking library
|
| 207 |
+
- **Gunicorn**: WSGI HTTP Server
|
| 208 |
+
|
| 209 |
+
#### Quantum Computing
|
| 210 |
+
- **Cirq**: Google's framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits
|
| 211 |
+
- **NumPy**: Scientific computing package
|
| 212 |
+
- **SymPy**: Symbolic mathematics
|
| 213 |
+
- **NetworkX**: Network creation and analysis
|
| 214 |
+
|
| 215 |
+
#### Frontend
|
| 216 |
+
- **Bootstrap 5**: Front-end component library
|
| 217 |
+
- **Chart.js**: JavaScript charting library
|
| 218 |
+
- **THREE.js**: JavaScript 3D library
|
| 219 |
+
- **Socket.IO-client**: Real-time bidirectional communication
|
| 220 |
+
|
| 221 |
+
### Learning Resources
|
| 222 |
+
|
| 223 |
+
For those interested in quantum computing fundamentals that informed this project:
|
| 224 |
+
|
| 225 |
+
- **Quantum Computation and Quantum Information**: [Michael Nielsen & Isaac Chuang](https://archive.org/details/QuantumComputationAndQuantumInformation10thAnniversaryEdition/page/n461/mode/2up)
|
| 226 |
+
- **Google Cirq Documentation**: [https://quantumai.google/cirq](https://quantumai.google/cirq)
|
| 227 |
+
- **Qiskit Textbook**: [https://qiskit.org/textbook](https://qiskit.org/textbook)
|
| 228 |
+
- **Quantum Algorithm Zoo**: [https://quantumalgorithmzoo.org/](https://quantumalgorithmzoo.org/)
|
| 229 |
+
|
| 230 |
+
## Future Development
|
| 231 |
+
|
| 232 |
+
The project is continually evolving with planned enhancements:
|
| 233 |
+
|
| 234 |
+
- Additional quantum algorithms and protocols
|
| 235 |
+
- Enhanced visualization components for educational clarity
|
| 236 |
+
- Improved performance for larger quantum systems
|
| 237 |
+
- Advanced noise models for realistic quantum behavior
|
| 238 |
+
- API endpoints for external application integration
|
| 239 |
+
|
| 240 |
+
## License
|
| 241 |
+
|
| 242 |
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
| 243 |
+
|
| 244 |
+
---
|
| 245 |
+
|
| 246 |
+
For questions, feedback, or support, please visit [issues](https://Facebook.com/ChuyenDoiXanh/issues)
|
README_HF.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# QuantumFieldKit on Hugging Face Spaces
|
| 2 |
+
|
| 3 |
+
This is a deployment of QuantumFieldKit specifically configured for Hugging Face Spaces.
|
| 4 |
+
|
| 5 |
+
## Quick Start
|
| 6 |
+
|
| 7 |
+
1. Create a new Docker Space on Hugging Face
|
| 8 |
+
2. Use the files in this repository
|
| 9 |
+
3. The application will automatically start on the port specified by Hugging Face Spaces
|
| 10 |
+
|
| 11 |
+
## Configuration
|
| 12 |
+
|
| 13 |
+
The application is pre-configured to work with Hugging Face Spaces:
|
| 14 |
+
|
| 15 |
+
- Uses `/tmp` directory for logs and temporary files
|
| 16 |
+
- Sets appropriate environment variables for containerized deployment
|
| 17 |
+
- Handles permission restrictions in containerized environments
|
| 18 |
+
|
| 19 |
+
## Files
|
| 20 |
+
|
| 21 |
+
- `Dockerfile`: Optimized Docker configuration for Hugging Face Spaces
|
| 22 |
+
- `huggingface_requirements.txt`: Dependencies optimized for Hugging Face Spaces
|
| 23 |
+
- `app.py`: Main application with containerized environment fixes
|
| 24 |
+
- `HUGGINGFACE_DEPLOYMENT.md`: Full deployment guide
|
| 25 |
+
|
| 26 |
+
## Support
|
| 27 |
+
|
| 28 |
+
For issues with the deployment, please refer to `HUGGINGFACE_DEPLOYMENT.md` or check the Hugging Face Space logs.
|
__init__.py
ADDED
|
File without changes
|
app.py
ADDED
|
@@ -0,0 +1,1453 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import traceback
|
| 3 |
+
import json
|
| 4 |
+
import logging
|
| 5 |
+
from flask import Flask, request, render_template, jsonify, session, abort, send_from_directory
|
| 6 |
+
from flask_socketio import SocketIO, emit
|
| 7 |
+
from werkzeug.middleware.proxy_fix import ProxyFix
|
| 8 |
+
import signal
|
| 9 |
+
from functools import wraps
|
| 10 |
+
#import threading
|
| 11 |
+
#import multiprocessing
|
| 12 |
+
import concurrent.futures
|
| 13 |
+
from datetime import datetime
|
| 14 |
+
import psutil
|
| 15 |
+
import re
|
| 16 |
+
from flask_cors import CORS
|
| 17 |
+
|
| 18 |
+
# Import simulation functions from plugins
|
| 19 |
+
from plugins.authentication.auth import generate_quantum_fingerprint_cirq
|
| 20 |
+
from plugins.encryption_bb84.bb84 import bb84_protocol_cirq
|
| 21 |
+
from plugins.error_correction.shor_code import run_shor_code
|
| 22 |
+
from plugins.grover.grover import run_grover
|
| 23 |
+
from plugins.handshake.handshake import handshake_cirq
|
| 24 |
+
from plugins.network.network import entanglement_swapping_cirq
|
| 25 |
+
from plugins.qrng.qrng import generate_random_number_cirq
|
| 26 |
+
from plugins.quantum_decryption.quantum_decryption import grover_key_search, shor_factorization
|
| 27 |
+
from plugins.teleportation.teleport import teleportation_circuit
|
| 28 |
+
from plugins.variational.vqe import run_vqe
|
| 29 |
+
from plugins.deutsch_jozsa.deutsch_jozsa import deutsch_jozsa_cirq
|
| 30 |
+
from plugins.quantum_fourier.qft import run_qft
|
| 31 |
+
from plugins.phase_estimation.phase_estimation import run_phase_estimation
|
| 32 |
+
from plugins.optimization.qaoa import run_qaoa
|
| 33 |
+
|
| 34 |
+
# Configure Errors
|
| 35 |
+
class SimulationError(Exception):
|
| 36 |
+
"""Base class for simulation errors"""
|
| 37 |
+
def __init__(self, message, param_info=None, suggestion=None):
|
| 38 |
+
self.message = message
|
| 39 |
+
self.param_info = param_info
|
| 40 |
+
self.suggestion = suggestion
|
| 41 |
+
super().__init__(self.message)
|
| 42 |
+
|
| 43 |
+
class ParameterError(SimulationError):
|
| 44 |
+
"""Error for invalid simulation parameters"""
|
| 45 |
+
pass
|
| 46 |
+
|
| 47 |
+
class QuantumCircuitError(SimulationError):
|
| 48 |
+
"""Error in quantum circuit construction or execution"""
|
| 49 |
+
pass
|
| 50 |
+
|
| 51 |
+
class ResourceExceededError(SimulationError):
|
| 52 |
+
"""Error when simulation exceeds available resources"""
|
| 53 |
+
pass
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def configure_logging():
|
| 58 |
+
# Use environment variable to determine log directory, fallback to tmp directory for containerized environments
|
| 59 |
+
log_dir = os.environ.get('LOG_DIR', '/tmp' if os.path.exists('/tmp') else '.')
|
| 60 |
+
|
| 61 |
+
# Create logs directory if it doesn't exist and we have permission
|
| 62 |
+
try:
|
| 63 |
+
if not os.path.exists(log_dir):
|
| 64 |
+
os.makedirs(log_dir, exist_ok=True)
|
| 65 |
+
# Test if we can write to the directory
|
| 66 |
+
test_file = os.path.join(log_dir, '.test_write_access')
|
| 67 |
+
with open(test_file, 'w') as f:
|
| 68 |
+
f.write('test')
|
| 69 |
+
os.remove(test_file)
|
| 70 |
+
except Exception as e:
|
| 71 |
+
# Fallback to current directory if we can't write to the log directory
|
| 72 |
+
log_dir = '.'
|
| 73 |
+
try:
|
| 74 |
+
if not os.path.exists(log_dir):
|
| 75 |
+
os.makedirs(log_dir, exist_ok=True)
|
| 76 |
+
except Exception:
|
| 77 |
+
# If we still can't create directory, use current directory
|
| 78 |
+
log_dir = '.'
|
| 79 |
+
|
| 80 |
+
# Create log file with date-based naming
|
| 81 |
+
log_filename = f"{log_dir}/quantum_field_kit_{datetime.now().strftime('%Y-%m-%d')}.log"
|
| 82 |
+
|
| 83 |
+
# Configure root logger
|
| 84 |
+
logging.basicConfig(
|
| 85 |
+
level=logging.INFO,
|
| 86 |
+
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
|
| 87 |
+
handlers=[
|
| 88 |
+
# Console handler for immediate feedback (always works)
|
| 89 |
+
logging.StreamHandler()
|
| 90 |
+
]
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
# Try to add file handler, but don't fail if we can't write to file
|
| 94 |
+
try:
|
| 95 |
+
file_handler = logging.FileHandler(log_filename)
|
| 96 |
+
logging.getLogger().addHandler(file_handler)
|
| 97 |
+
logger.info(f"File logging configured successfully. Log file: {log_filename}")
|
| 98 |
+
except Exception as e:
|
| 99 |
+
# In containerized environments, we might not be able to write files
|
| 100 |
+
# Just continue with console logging only
|
| 101 |
+
logger.warning(f"File logging not available: {e}. Using console logging only.")
|
| 102 |
+
|
| 103 |
+
# Set specific logger levels
|
| 104 |
+
logging.getLogger('werkzeug').setLevel(logging.WARNING)
|
| 105 |
+
logging.getLogger('socketio').setLevel(logging.WARNING)
|
| 106 |
+
|
| 107 |
+
return logging.getLogger('quantum_field_kit')
|
| 108 |
+
|
| 109 |
+
# Create application logger
|
| 110 |
+
logger = configure_logging()
|
| 111 |
+
|
| 112 |
+
# Configure matplotlib for containerized environments
|
| 113 |
+
# Set MPLCONFIGDIR to a writable directory to avoid permission errors
|
| 114 |
+
def setup_matplotlib_config():
|
| 115 |
+
"""Setup matplotlib configuration directory with proper permissions"""
|
| 116 |
+
mpl_config_dir = os.environ.get('MPLCONFIGDIR', '/tmp/matplotlib' if os.path.exists('/tmp') else './.matplotlib')
|
| 117 |
+
|
| 118 |
+
try:
|
| 119 |
+
# Create the directory with exist_ok to avoid errors if it already exists
|
| 120 |
+
os.makedirs(mpl_config_dir, exist_ok=True)
|
| 121 |
+
|
| 122 |
+
# Test if we can write to the directory
|
| 123 |
+
test_file = os.path.join(mpl_config_dir, '.test_write_access')
|
| 124 |
+
with open(test_file, 'w') as f:
|
| 125 |
+
f.write('test')
|
| 126 |
+
os.remove(test_file)
|
| 127 |
+
|
| 128 |
+
# Set the environment variable
|
| 129 |
+
os.environ['MPLCONFIGDIR'] = mpl_config_dir
|
| 130 |
+
logger.info(f"Matplotlib config directory set to: {mpl_config_dir}")
|
| 131 |
+
return True
|
| 132 |
+
except Exception as e:
|
| 133 |
+
logger.warning(f"Could not configure matplotlib directory {mpl_config_dir}: {e}")
|
| 134 |
+
|
| 135 |
+
# Try fallback directories
|
| 136 |
+
fallback_dirs = ['./.matplotlib', '.', '/tmp']
|
| 137 |
+
for fallback_dir in fallback_dirs:
|
| 138 |
+
try:
|
| 139 |
+
os.makedirs(fallback_dir, exist_ok=True)
|
| 140 |
+
test_file = os.path.join(fallback_dir, '.test_write_access')
|
| 141 |
+
with open(test_file, 'w') as f:
|
| 142 |
+
f.write('test')
|
| 143 |
+
os.remove(test_file)
|
| 144 |
+
os.environ['MPLCONFIGDIR'] = fallback_dir
|
| 145 |
+
logger.info(f"Matplotlib config directory set to fallback: {fallback_dir}")
|
| 146 |
+
return True
|
| 147 |
+
except Exception as fallback_e:
|
| 148 |
+
logger.warning(f"Fallback directory {fallback_dir} also failed: {fallback_e}")
|
| 149 |
+
continue
|
| 150 |
+
|
| 151 |
+
# If all else fails, let matplotlib handle it
|
| 152 |
+
logger.warning("All attempts to set matplotlib config directory failed. Letting matplotlib use its default.")
|
| 153 |
+
return False
|
| 154 |
+
|
| 155 |
+
# Setup matplotlib configuration
|
| 156 |
+
setup_matplotlib_config()
|
| 157 |
+
|
| 158 |
+
# Initialize Flask application (serve React build in production)
|
| 159 |
+
app = Flask(__name__, static_folder='frontend/build', static_url_path='/')
|
| 160 |
+
|
| 161 |
+
# Configure CORS origins from environment variable
|
| 162 |
+
cors_origins = os.environ.get('CORS_ORIGINS',
|
| 163 |
+
'http://localhost:3000,http://127.0.0.1:3000,http://localhost:5000,http://127.0.0.1:5000,https://quantumfieldkit.com,https://www.quantumfieldkit.com,https://quantumfieldkit.fly.dev'
|
| 164 |
+
).split(',')
|
| 165 |
+
|
| 166 |
+
CORS(app, resources={
|
| 167 |
+
r"/api/*": {
|
| 168 |
+
"origins": [origin.strip() for origin in cors_origins],
|
| 169 |
+
"methods": ["GET", "POST", "OPTIONS"],
|
| 170 |
+
"allow_headers": ["Content-Type"]
|
| 171 |
+
}
|
| 172 |
+
})
|
| 173 |
+
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', os.urandom(24))
|
| 174 |
+
app.config['TEMPLATES_AUTO_RELOAD'] = True
|
| 175 |
+
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max upload
|
| 176 |
+
|
| 177 |
+
# Add proxy fix for proper IP handling behind reverse proxies
|
| 178 |
+
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
|
| 179 |
+
|
| 180 |
+
# Initialize Socket.IO for real-time communication
|
| 181 |
+
socketio = SocketIO(app, async_mode='eventlet', cors_allowed_origins="*")
|
| 182 |
+
|
| 183 |
+
def timeout(seconds):
|
| 184 |
+
"""
|
| 185 |
+
Decorator that adds a timeout to a function.
|
| 186 |
+
If the function takes longer than 'seconds' to execute, it will be terminated.
|
| 187 |
+
|
| 188 |
+
Args:
|
| 189 |
+
seconds: Maximum execution time in seconds
|
| 190 |
+
|
| 191 |
+
Returns:
|
| 192 |
+
Decorated function with timeout capability
|
| 193 |
+
"""
|
| 194 |
+
def decorator(func):
|
| 195 |
+
@wraps(func)
|
| 196 |
+
def wrapper(*args, **kwargs):
|
| 197 |
+
plugin_name = kwargs.get('_plugin_name', 'Unknown plugin')
|
| 198 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
| 199 |
+
future = executor.submit(func, *args, **kwargs)
|
| 200 |
+
try:
|
| 201 |
+
return future.result(timeout=seconds)
|
| 202 |
+
except concurrent.futures.TimeoutError:
|
| 203 |
+
# More detailed timeout message
|
| 204 |
+
return {
|
| 205 |
+
"output": None,
|
| 206 |
+
"log": f"Simulation for {plugin_name} started but could not complete within {seconds} seconds.\n"
|
| 207 |
+
f"This may be due to complex parameters or high precision settings.",
|
| 208 |
+
"error": f"Execution timed out after {seconds} seconds. Try reducing complexity of the simulation."
|
| 209 |
+
}
|
| 210 |
+
return wrapper
|
| 211 |
+
return decorator
|
| 212 |
+
|
| 213 |
+
def json_safe(obj):
|
| 214 |
+
"""
|
| 215 |
+
Recursively convert non-JSON-serializable objects to strings.
|
| 216 |
+
Preserve raw SVG (under the key "circuit_svg") so it is not altered.
|
| 217 |
+
"""
|
| 218 |
+
try:
|
| 219 |
+
json.dumps(obj)
|
| 220 |
+
return obj
|
| 221 |
+
except (TypeError, OverflowError):
|
| 222 |
+
if isinstance(obj, dict):
|
| 223 |
+
new_obj = {}
|
| 224 |
+
for k, v in obj.items():
|
| 225 |
+
new_obj[k] = v if k == "circuit_svg" else json_safe(v)
|
| 226 |
+
return new_obj
|
| 227 |
+
elif isinstance(obj, (list, tuple)):
|
| 228 |
+
return [json_safe(item) for item in obj]
|
| 229 |
+
elif hasattr(obj, 'tolist'): # For numpy arrays
|
| 230 |
+
return obj.tolist()
|
| 231 |
+
else:
|
| 232 |
+
return str(obj)
|
| 233 |
+
|
| 234 |
+
def check_memory_usage():
|
| 235 |
+
"""Check if memory usage is within acceptable limits"""
|
| 236 |
+
try:
|
| 237 |
+
process = psutil.Process(os.getpid())
|
| 238 |
+
memory_usage = process.memory_info().rss / (1024 * 1024) # MB
|
| 239 |
+
memory_percent = process.memory_percent()
|
| 240 |
+
|
| 241 |
+
if memory_percent > 85:
|
| 242 |
+
logger.warning(f"High memory usage detected: {memory_usage:.2f} MB ({memory_percent:.1f}%)")
|
| 243 |
+
return False
|
| 244 |
+
|
| 245 |
+
return True
|
| 246 |
+
except Exception as e:
|
| 247 |
+
logger.error(f"Error checking memory usage: {e}")
|
| 248 |
+
return True # Assume it's safe if we can't check
|
| 249 |
+
|
| 250 |
+
def wrap_result(sim_result):
|
| 251 |
+
"""
|
| 252 |
+
Standardize the simulation result into a dictionary with keys:
|
| 253 |
+
- "output": All keys except "log"
|
| 254 |
+
- "log": Detailed process log
|
| 255 |
+
- "error": None if successful, or the error message.
|
| 256 |
+
"""
|
| 257 |
+
if isinstance(sim_result, dict):
|
| 258 |
+
if 'log' in sim_result:
|
| 259 |
+
output = {k: v for k, v in sim_result.items() if k != 'log'}
|
| 260 |
+
output = json_safe(output)
|
| 261 |
+
return {"output": output, "log": sim_result['log'], "error": None}
|
| 262 |
+
else:
|
| 263 |
+
return {"output": json_safe(sim_result), "log": "", "error": None}
|
| 264 |
+
elif isinstance(sim_result, tuple):
|
| 265 |
+
if len(sim_result) > 1:
|
| 266 |
+
*outputs, log = sim_result
|
| 267 |
+
output = outputs[0] if len(outputs) == 1 else outputs
|
| 268 |
+
output = json_safe(output)
|
| 269 |
+
return {"output": output, "log": log, "error": None}
|
| 270 |
+
else:
|
| 271 |
+
return {"output": json_safe(sim_result[0]), "log": "", "error": None}
|
| 272 |
+
else:
|
| 273 |
+
return {"output": json_safe(sim_result), "log": "", "error": None}
|
| 274 |
+
|
| 275 |
+
def run_plugin(sim_func, **params):
|
| 276 |
+
"""
|
| 277 |
+
Calls the simulation function with the given parameters.
|
| 278 |
+
Returns a standardized result dictionary with improved error handling.
|
| 279 |
+
"""
|
| 280 |
+
# Check memory before running simulation
|
| 281 |
+
if not check_memory_usage():
|
| 282 |
+
return {
|
| 283 |
+
"output": None,
|
| 284 |
+
"log": "Server is currently experiencing high memory usage. Please try again later.",
|
| 285 |
+
"error": "Insufficient memory available for simulation. Try reducing simulation complexity."
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
try:
|
| 289 |
+
# Extract plugin key for error reporting but don't pass it to the simulation function
|
| 290 |
+
plugin_key = params.pop('_plugin_key', 'unknown_plugin')
|
| 291 |
+
|
| 292 |
+
# Also remove _plugin_name if it exists (backward compatibility)
|
| 293 |
+
params.pop('_plugin_name', None)
|
| 294 |
+
|
| 295 |
+
logger.info(f"Running {plugin_key} simulation with parameters: {params}")
|
| 296 |
+
|
| 297 |
+
# Apply timeout to simulation function
|
| 298 |
+
@timeout(15) # Increased timeout for complex simulations
|
| 299 |
+
def run_with_timeout():
|
| 300 |
+
# Don't pass plugin identifiers to the actual simulation function
|
| 301 |
+
return sim_func(**params)
|
| 302 |
+
|
| 303 |
+
sim_result = run_with_timeout()
|
| 304 |
+
if isinstance(sim_result, dict) and "error" in sim_result and sim_result["error"] is not None:
|
| 305 |
+
# This is a timeout error from our decorator
|
| 306 |
+
return sim_result
|
| 307 |
+
|
| 308 |
+
return wrap_result(sim_result)
|
| 309 |
+
except ParameterError as e:
|
| 310 |
+
# Handle parameter errors with helpful suggestions
|
| 311 |
+
error_msg = f"Parameter error: {e.message}"
|
| 312 |
+
if e.param_info:
|
| 313 |
+
error_msg += f"\nParameter: {e.param_info}"
|
| 314 |
+
if e.suggestion:
|
| 315 |
+
error_msg += f"\nSuggestion: {e.suggestion}"
|
| 316 |
+
|
| 317 |
+
logger.error(f"Parameter error in {plugin_key}: {error_msg}")
|
| 318 |
+
return {"output": None, "log": None, "error": error_msg}
|
| 319 |
+
except QuantumCircuitError as e:
|
| 320 |
+
# Handle quantum circuit specific errors
|
| 321 |
+
error_msg = f"Quantum circuit error: {e.message}"
|
| 322 |
+
if e.suggestion:
|
| 323 |
+
error_msg += f"\nSuggestion: {e.suggestion}"
|
| 324 |
+
|
| 325 |
+
logger.error(f"Circuit error in {plugin_key}: {error_msg}")
|
| 326 |
+
return {"output": None, "log": None, "error": error_msg}
|
| 327 |
+
except Exception as e:
|
| 328 |
+
# Generic exception handling with improved context
|
| 329 |
+
error_type = type(e).__name__
|
| 330 |
+
error_msg = str(e)
|
| 331 |
+
stack_trace = traceback.format_exc()
|
| 332 |
+
|
| 333 |
+
# Create a user-friendly error message
|
| 334 |
+
user_error = f"Error ({error_type}): {error_msg}"
|
| 335 |
+
|
| 336 |
+
# Log the full technical details
|
| 337 |
+
logger.error(f"Simulation error in {plugin_key}: {error_type} - {error_msg}\n{stack_trace}")
|
| 338 |
+
|
| 339 |
+
return {
|
| 340 |
+
"output": None,
|
| 341 |
+
"log": f"Simulation failed. See error tab for details.",
|
| 342 |
+
"error": user_error
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
def validate_parameters(plugin, params):
|
| 346 |
+
"""Enhanced parameter validation with detailed error messages"""
|
| 347 |
+
validated_params = {}
|
| 348 |
+
|
| 349 |
+
for param in plugin["parameters"]:
|
| 350 |
+
param_name = param["name"]
|
| 351 |
+
|
| 352 |
+
# Check if required parameter is missing
|
| 353 |
+
if param_name not in params and "default" not in param:
|
| 354 |
+
raise ParameterError(
|
| 355 |
+
f"Missing required parameter: {param_name}",
|
| 356 |
+
param_info=f"{param_name} ({param['type']})",
|
| 357 |
+
suggestion="Please provide a value for this required parameter."
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
# Use default if parameter is missing
|
| 361 |
+
if param_name not in params and "default" in param:
|
| 362 |
+
validated_params[param_name] = param["default"]
|
| 363 |
+
continue
|
| 364 |
+
|
| 365 |
+
# Validate parameter based on type
|
| 366 |
+
raw_val = params[param_name]
|
| 367 |
+
|
| 368 |
+
if param["type"] == "int":
|
| 369 |
+
try:
|
| 370 |
+
val = int(raw_val)
|
| 371 |
+
# Check min/max bounds
|
| 372 |
+
if "min" in param and val < param["min"]:
|
| 373 |
+
raise ParameterError(
|
| 374 |
+
f"Value for {param_name} is too small",
|
| 375 |
+
param_info=f"{param_name} = {val}",
|
| 376 |
+
suggestion=f"Minimum allowed value is {param['min']}."
|
| 377 |
+
)
|
| 378 |
+
if "max" in param and val > param["max"]:
|
| 379 |
+
raise ParameterError(
|
| 380 |
+
f"Value for {param_name} is too large",
|
| 381 |
+
param_info=f"{param_name} = {val}",
|
| 382 |
+
suggestion=f"Maximum allowed value is {param['max']}."
|
| 383 |
+
)
|
| 384 |
+
validated_params[param_name] = val
|
| 385 |
+
except ValueError:
|
| 386 |
+
raise ParameterError(
|
| 387 |
+
f"Invalid integer value for {param_name}",
|
| 388 |
+
param_info=f"Received: {raw_val}",
|
| 389 |
+
suggestion="Please provide a valid integer value."
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
elif param["type"] == "float":
|
| 393 |
+
try:
|
| 394 |
+
val = float(raw_val)
|
| 395 |
+
# Check min/max bounds
|
| 396 |
+
if "min" in param and val < param["min"]:
|
| 397 |
+
raise ParameterError(
|
| 398 |
+
f"Value for {param_name} is too small",
|
| 399 |
+
param_info=f"{param_name} = {val}",
|
| 400 |
+
suggestion=f"Minimum allowed value is {param['min']}."
|
| 401 |
+
)
|
| 402 |
+
if "max" in param and val > param["max"]:
|
| 403 |
+
raise ParameterError(
|
| 404 |
+
f"Value for {param_name} is too large",
|
| 405 |
+
param_info=f"{param_name} = {val}",
|
| 406 |
+
suggestion=f"Maximum allowed value is {param['max']}."
|
| 407 |
+
)
|
| 408 |
+
validated_params[param_name] = val
|
| 409 |
+
except ValueError:
|
| 410 |
+
raise ParameterError(
|
| 411 |
+
f"Invalid float value for {param_name}",
|
| 412 |
+
param_info=f"Received: {raw_val}",
|
| 413 |
+
suggestion="Please provide a valid decimal number."
|
| 414 |
+
)
|
| 415 |
+
|
| 416 |
+
elif param["type"] == "bool":
|
| 417 |
+
if isinstance(raw_val, bool):
|
| 418 |
+
validated_params[param_name] = raw_val
|
| 419 |
+
elif isinstance(raw_val, str):
|
| 420 |
+
validated_params[param_name] = raw_val.lower() == "true"
|
| 421 |
+
else:
|
| 422 |
+
raise ParameterError(
|
| 423 |
+
f"Invalid boolean value for {param_name}",
|
| 424 |
+
param_info=f"Received: {raw_val}",
|
| 425 |
+
suggestion="Please provide 'true' or 'false'."
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
elif param["type"] == "str":
|
| 429 |
+
if not isinstance(raw_val, str):
|
| 430 |
+
raw_val = str(raw_val)
|
| 431 |
+
|
| 432 |
+
# Check max length for strings
|
| 433 |
+
if "max_length" in param and len(raw_val) > param["max_length"]:
|
| 434 |
+
raise ParameterError(
|
| 435 |
+
f"Value for {param_name} is too long",
|
| 436 |
+
param_info=f"Length: {len(raw_val)} characters",
|
| 437 |
+
suggestion=f"Maximum allowed length is {param['max_length']} characters."
|
| 438 |
+
)
|
| 439 |
+
|
| 440 |
+
# Check if value is in allowed options
|
| 441 |
+
if "options" in param and raw_val not in param["options"]:
|
| 442 |
+
raise ParameterError(
|
| 443 |
+
f"Invalid option for {param_name}",
|
| 444 |
+
param_info=f"Received: {raw_val}",
|
| 445 |
+
suggestion=f"Allowed options are: {', '.join(param['options'])}"
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
validated_params[param_name] = raw_val
|
| 449 |
+
|
| 450 |
+
elif param["type"] == "select":
|
| 451 |
+
if "options" in param and raw_val not in param["options"]:
|
| 452 |
+
raise ParameterError(
|
| 453 |
+
f"Invalid selection for {param_name}",
|
| 454 |
+
param_info=f"Received: {raw_val}",
|
| 455 |
+
suggestion=f"Please select one of: {', '.join(param['options'])}"
|
| 456 |
+
)
|
| 457 |
+
validated_params[param_name] = raw_val
|
| 458 |
+
|
| 459 |
+
else:
|
| 460 |
+
# For unrecognized types, just pass through the value
|
| 461 |
+
validated_params[param_name] = raw_val
|
| 462 |
+
|
| 463 |
+
return validated_params
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
# --- Plugin Registry ---
|
| 467 |
+
# Define all available quantum simulation plugins
|
| 468 |
+
PLUGINS = {
|
| 469 |
+
'auth': {
|
| 470 |
+
'name': 'Post-Quantum Authentication',
|
| 471 |
+
'description': 'Simulate a lattice-based authentication system that remains secure against quantum computer attacks, based on the Ring-LWE problem.',
|
| 472 |
+
'icon': 'fa-lock',
|
| 473 |
+
'category': 'security',
|
| 474 |
+
'parameters': [
|
| 475 |
+
{'name': 'username', 'type': 'str', 'default': 'Bob', 'description': 'Username for authentication'},
|
| 476 |
+
{'name': 'noise', 'type': 'float', 'default': 0.0, 'description': 'Noise level (0.0 - 0.2)',"min": 0, "max": 0.2},
|
| 477 |
+
{'name': 'dimension', 'type': 'int', 'default': 4, 'description': 'Lattice dimension parameter',"min": 1, "max": 32}
|
| 478 |
+
],
|
| 479 |
+
'function': generate_quantum_fingerprint_cirq,
|
| 480 |
+
# Standardized runner to match other plugins and Socket.IO flow
|
| 481 |
+
'run': lambda p: run_plugin(
|
| 482 |
+
generate_quantum_fingerprint_cirq,
|
| 483 |
+
_plugin_key="auth",
|
| 484 |
+
data=p.get("username", "Bob"),
|
| 485 |
+
num_qubits=p.get("dimension", 4)
|
| 486 |
+
)
|
| 487 |
+
},
|
| 488 |
+
|
| 489 |
+
"bb84": {
|
| 490 |
+
"name": "BB84 Protocol Simulation",
|
| 491 |
+
"description": "Simulate the BB84 quantum key distribution protocol with realistic physical effects.",
|
| 492 |
+
"icon": "fa-key",
|
| 493 |
+
"category": "cryptography",
|
| 494 |
+
"parameters": [
|
| 495 |
+
{"name": "num_bits", "type": "int", "default": 10, "description": "Number of bits to simulate",
|
| 496 |
+
"min": 1, "max": 16},
|
| 497 |
+
|
| 498 |
+
{"name": "distance_km", "type": "float", "default": 0.0, "description": "Distance between Alice and Bob (km)",
|
| 499 |
+
"min": 0.0, "max": 1000.0},
|
| 500 |
+
|
| 501 |
+
{"name": "hardware_type", "type": "select", "default": "fiber",
|
| 502 |
+
"description": "Hardware type (fiber, satellite, trapped_ion)",
|
| 503 |
+
"options": ["fiber", "satellite", "trapped_ion"]},
|
| 504 |
+
|
| 505 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Additional noise probability",
|
| 506 |
+
"min": 0.0, "max": 0.3},
|
| 507 |
+
|
| 508 |
+
{"name": "eve_present", "type": "bool", "default": False,
|
| 509 |
+
"description": "Eavesdropper present"},
|
| 510 |
+
|
| 511 |
+
{"name": "eve_strategy", "type": "select", "default": "intercept_resend",
|
| 512 |
+
"description": "Eavesdropper strategy (intercept_resend, beam_splitting, trojan)",
|
| 513 |
+
"options": ["intercept_resend", "beam_splitting", "trojan_horse"]},
|
| 514 |
+
|
| 515 |
+
{"name": "detailed_simulation", "type": "bool", "default": True,
|
| 516 |
+
"description": "Run detailed quantum simulation"}
|
| 517 |
+
],
|
| 518 |
+
"run": lambda p: run_plugin(bb84_protocol_cirq,
|
| 519 |
+
_plugin_key="bb84",
|
| 520 |
+
num_bits=p["num_bits"],
|
| 521 |
+
distance_km=p["distance_km"],
|
| 522 |
+
hardware_type=p["hardware_type"],
|
| 523 |
+
eve_present=p["eve_present"] if isinstance(p["eve_present"], bool) else p["eve_present"].lower() == "true",
|
| 524 |
+
eve_strategy=p["eve_strategy"],
|
| 525 |
+
detailed_simulation=p["detailed_simulation"] if isinstance(p["detailed_simulation"], bool) else p["detailed_simulation"].lower() == "true",
|
| 526 |
+
noise_prob=p["noise"])
|
| 527 |
+
},
|
| 528 |
+
|
| 529 |
+
"shor": {
|
| 530 |
+
"name": "Shor's Code Simulation",
|
| 531 |
+
"description": "Simulate quantum error correction using Shor's code.",
|
| 532 |
+
"icon": "fa-shield-alt",
|
| 533 |
+
"category": "error-correction",
|
| 534 |
+
"parameters": [
|
| 535 |
+
{"name": "noise", "type": "float", "default": 0.01, "description": "Noise probability",
|
| 536 |
+
"min": 0.0, "max": 0.3}
|
| 537 |
+
],
|
| 538 |
+
"run": lambda p: run_plugin(run_shor_code, _plugin_key="shor", noise_prob=p["noise"])
|
| 539 |
+
},
|
| 540 |
+
|
| 541 |
+
"grover": {
|
| 542 |
+
"name": "Grover's Algorithm Simulation",
|
| 543 |
+
"description": "Simulate Grover's search algorithm.",
|
| 544 |
+
"icon": "fa-search",
|
| 545 |
+
"category": "algorithms",
|
| 546 |
+
"parameters": [
|
| 547 |
+
{"name": "n", "type": "int", "default": 3, "description": "Number of qubits",
|
| 548 |
+
"min": 1, "max": 8},
|
| 549 |
+
{"name": "target_state", "type": "str", "default": "101", "description": "Target state (binary)",
|
| 550 |
+
"max_length": 8},
|
| 551 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Noise probability",
|
| 552 |
+
"min": 0.0, "max": 0.3}
|
| 553 |
+
],
|
| 554 |
+
"run": lambda p: run_plugin(run_grover, _plugin_key="grover", n=p["n"], target_state=p["target_state"], noise_prob=p["noise"])
|
| 555 |
+
},
|
| 556 |
+
|
| 557 |
+
"handshake": {
|
| 558 |
+
"name": "Quantum Handshake Simulation",
|
| 559 |
+
"description": "Simulate a quantum handshake using entangled Bell pairs.",
|
| 560 |
+
"icon": "fa-handshake",
|
| 561 |
+
"category": "protocols",
|
| 562 |
+
"parameters": [
|
| 563 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Noise probability",
|
| 564 |
+
"min": 0.0, "max": 0.3}
|
| 565 |
+
],
|
| 566 |
+
"run": lambda p: run_plugin(handshake_cirq, _plugin_key="handshake", noise_prob=p["noise"])
|
| 567 |
+
},
|
| 568 |
+
|
| 569 |
+
"network": {
|
| 570 |
+
"name": "Entanglement Swapping Simulation",
|
| 571 |
+
"description": "Simulate a quantum network using entanglement swapping.",
|
| 572 |
+
"icon": "fa-network-wired",
|
| 573 |
+
"category": "protocols",
|
| 574 |
+
"parameters": [
|
| 575 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Noise probability",
|
| 576 |
+
"min": 0.0, "max": 0.3}
|
| 577 |
+
],
|
| 578 |
+
"run": lambda p: run_plugin(entanglement_swapping_cirq, _plugin_key="network", noise_prob=p["noise"])
|
| 579 |
+
},
|
| 580 |
+
|
| 581 |
+
"qrng": {
|
| 582 |
+
"name": "Enhanced Quantum Random Number Generator",
|
| 583 |
+
"description": "Generate truly random numbers using multiple quantum sources with advanced statistical analysis.",
|
| 584 |
+
"icon": "fa-dice",
|
| 585 |
+
"category": "utilities",
|
| 586 |
+
"parameters": [
|
| 587 |
+
{"name": "num_bits", "type": "int", "default": 8, "description": "Number of quantum bits to generate",
|
| 588 |
+
"min": 1, "max": 32},
|
| 589 |
+
{"name": "source_type", "type": "select", "default": "superposition",
|
| 590 |
+
"description": "Quantum randomness source",
|
| 591 |
+
"options": ["superposition", "vacuum_fluctuation", "entanglement"]},
|
| 592 |
+
{"name": "noise_level", "type": "float", "default": 0.0, "description": "Hardware noise level",
|
| 593 |
+
"min": 0.0, "max": 0.3},
|
| 594 |
+
{"name": "enable_post_processing", "type": "bool", "default": False,
|
| 595 |
+
"description": "Apply bias correction"},
|
| 596 |
+
{"name": "hardware_simulation", "type": "bool", "default": False,
|
| 597 |
+
"description": "Add timing delays"}
|
| 598 |
+
],
|
| 599 |
+
"run": lambda p: run_plugin(generate_random_number_cirq,
|
| 600 |
+
_plugin_key="qrng",
|
| 601 |
+
num_bits=p["num_bits"],
|
| 602 |
+
source_type=p["source_type"],
|
| 603 |
+
noise_level=p["noise_level"],
|
| 604 |
+
enable_post_processing=p["enable_post_processing"] if isinstance(p["enable_post_processing"], bool) else p["enable_post_processing"].lower() == "true",
|
| 605 |
+
hardware_simulation=p["hardware_simulation"] if isinstance(p["hardware_simulation"], bool) else p["hardware_simulation"].lower() == "true")
|
| 606 |
+
},
|
| 607 |
+
|
| 608 |
+
"teleport": {
|
| 609 |
+
"name": "Quantum Teleportation Simulation",
|
| 610 |
+
"description": "Simulate quantum teleportation protocol.",
|
| 611 |
+
"icon": "fa-atom",
|
| 612 |
+
"category": "protocols",
|
| 613 |
+
"parameters": [
|
| 614 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Noise probability",
|
| 615 |
+
"min": 0.0, "max": 0.3}
|
| 616 |
+
],
|
| 617 |
+
"run": lambda p: run_plugin(teleportation_circuit, _plugin_key="teleport", noise_prob=p["noise"])
|
| 618 |
+
},
|
| 619 |
+
|
| 620 |
+
"vqe": {
|
| 621 |
+
"name": "Variational Quantum Eigensolver (VQE)",
|
| 622 |
+
"description": "Simulate VQE to find the ground state energy of a hydrogen molecule with quantum chemical accuracy.",
|
| 623 |
+
"icon": "fa-wave-square",
|
| 624 |
+
"category": "algorithms",
|
| 625 |
+
"parameters": [
|
| 626 |
+
{"name": "num_qubits", "type": "int", "default": 2, "description": "Number of qubits",
|
| 627 |
+
"min": 2, "max": 4},
|
| 628 |
+
{"name": "noise_prob", "type": "float", "default": 0.01, "description": "Noise probability",
|
| 629 |
+
"min": 0.0, "max": 0.3},
|
| 630 |
+
{"name": "max_iter", "type": "int", "default": 3, "description": "Maximum iterations",
|
| 631 |
+
"min": 1, "max": 5},
|
| 632 |
+
{"name": "bond_distance", "type": "float", "default": 0.7414, "description": "H-H bond distance (Å)",
|
| 633 |
+
"min": 0.0, "max": 2.5}
|
| 634 |
+
],
|
| 635 |
+
"run": lambda p: run_plugin(run_vqe,
|
| 636 |
+
_plugin_key="vqe",
|
| 637 |
+
num_qubits=p["num_qubits"],
|
| 638 |
+
noise_prob=p["noise_prob"],
|
| 639 |
+
max_iter=p["max_iter"],
|
| 640 |
+
bond_distance=p["bond_distance"])
|
| 641 |
+
},
|
| 642 |
+
|
| 643 |
+
"quantum_decryption_grover": {
|
| 644 |
+
"name": "Quantum Decryption via Grover Key Search",
|
| 645 |
+
"description": "Use Grover's algorithm to search for a secret key.",
|
| 646 |
+
"icon": "fa-unlock",
|
| 647 |
+
"category": "cryptography",
|
| 648 |
+
"parameters": [
|
| 649 |
+
{"name": "key", "type": "int", "default": 5, "description": "Secret key (integer)",
|
| 650 |
+
"min": 0, "max": 255},
|
| 651 |
+
{"name": "num_bits", "type": "int", "default": 4, "description": "Number of bits (search space)",
|
| 652 |
+
"min": 1, "max": 8},
|
| 653 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Noise probability",
|
| 654 |
+
"min": 0.0, "max": 0.3}
|
| 655 |
+
],
|
| 656 |
+
"run": lambda p: run_plugin(grover_key_search, _plugin_key="quantum_decryption_grover", key=p["key"], num_bits=p["num_bits"], noise_prob=p["noise"])
|
| 657 |
+
},
|
| 658 |
+
|
| 659 |
+
"quantum_decryption_shor": {
|
| 660 |
+
"name": "Quantum Decryption via Shor Factorization",
|
| 661 |
+
"description": "Simulate quantum decryption using Shor's code simulation.",
|
| 662 |
+
"icon": "fa-key",
|
| 663 |
+
"category": "cryptography",
|
| 664 |
+
"parameters": [
|
| 665 |
+
{"name": "N", "type": "int", "default": 15, "description": "Composite number",
|
| 666 |
+
"min": 4, "max": 100}
|
| 667 |
+
],
|
| 668 |
+
"run": lambda p: run_plugin(shor_factorization, _plugin_key="quantum_decryption_shor", N=p["N"])
|
| 669 |
+
},
|
| 670 |
+
|
| 671 |
+
"deutsch_jozsa": {
|
| 672 |
+
"name": "Deutsch-Jozsa Algorithm",
|
| 673 |
+
"description": "Determine if a function is constant or balanced with a single quantum query.",
|
| 674 |
+
"icon": "fa-balance-scale",
|
| 675 |
+
"category": "algorithms",
|
| 676 |
+
"parameters": [
|
| 677 |
+
{"name": "n_qubits", "type": "int", "default": 3, "description": "Number of input qubits",
|
| 678 |
+
"min": 1, "max": 8},
|
| 679 |
+
{"name": "oracle_type", "type": "str", "default": "random", "description": "Oracle type: constant_0, constant_1, balanced, or random",
|
| 680 |
+
"options": ["constant_0", "constant_1", "balanced", "random"], "max_length": 10},
|
| 681 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Noise probability",
|
| 682 |
+
"min": 0.0, "max": 0.3}
|
| 683 |
+
],
|
| 684 |
+
"run": lambda p: run_plugin(deutsch_jozsa_cirq, _plugin_key="deutsch_jozsa", n_qubits=p["n_qubits"], oracle_type=p["oracle_type"], noise_prob=p["noise"])
|
| 685 |
+
},
|
| 686 |
+
|
| 687 |
+
"qft": {
|
| 688 |
+
"name": "Quantum Fourier Transform",
|
| 689 |
+
"description": "Implement the quantum analogue of the discrete Fourier transform.",
|
| 690 |
+
"icon": "fa-wave-square",
|
| 691 |
+
"category": "algorithms",
|
| 692 |
+
"parameters": [
|
| 693 |
+
{"name": "n_qubits", "type": "int", "default": 3, "description": "Number of qubits",
|
| 694 |
+
"min": 1, "max": 8},
|
| 695 |
+
{"name": "input_state", "type": "str", "default": "010", "description": "Input state (binary)",
|
| 696 |
+
"max_length": 8},
|
| 697 |
+
{"name": "include_inverse", "type": "str", "default": "False", "description": "Include inverse QFT",
|
| 698 |
+
"options": ["True", "False"], "max_length": 5},
|
| 699 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Noise probability",
|
| 700 |
+
"min": 0.0, "max": 0.3}
|
| 701 |
+
],
|
| 702 |
+
"run": lambda p: run_plugin(run_qft, _plugin_key="qft", n_qubits=p["n_qubits"], input_state=p["input_state"],
|
| 703 |
+
include_inverse=p["include_inverse"].lower() == "true", noise_prob=p["noise"])
|
| 704 |
+
},
|
| 705 |
+
|
| 706 |
+
"phase_estimation": {
|
| 707 |
+
"name": "Quantum Phase Estimation",
|
| 708 |
+
"description": "Estimate eigenvalues of unitary operators with applications in quantum computing.",
|
| 709 |
+
"icon": "fa-ruler-combined",
|
| 710 |
+
"category": "algorithms",
|
| 711 |
+
"parameters": [
|
| 712 |
+
{"name": "precision_bits", "type": "int", "default": 3, "description": "Number of bits of precision",
|
| 713 |
+
"min": 1, "max": 6},
|
| 714 |
+
{"name": "target_phase", "type": "float", "default": 0.125, "description": "Target phase to estimate (0-1)",
|
| 715 |
+
"min": 0.0, "max": 1.0},
|
| 716 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Noise probability",
|
| 717 |
+
"min": 0.0, "max": 0.3}
|
| 718 |
+
],
|
| 719 |
+
"run": lambda p: run_plugin(run_phase_estimation, _plugin_key="phase_estimation", precision_bits=p["precision_bits"],
|
| 720 |
+
target_phase=p["target_phase"], noise_prob=p["noise"])
|
| 721 |
+
},
|
| 722 |
+
|
| 723 |
+
"qaoa": {
|
| 724 |
+
"name": "Quantum Approximate Optimization Algorithm",
|
| 725 |
+
"description": "Solve combinatorial optimization problems like MaxCut using a hybrid quantum-classical approach.",
|
| 726 |
+
"icon": "fa-project-diagram",
|
| 727 |
+
"category": "optimization",
|
| 728 |
+
"parameters": [
|
| 729 |
+
{"name": "n_nodes", "type": "int", "default": 4, "description": "Number of nodes in the graph",
|
| 730 |
+
"min": 2, "max": 8},
|
| 731 |
+
{"name": "edge_probability", "type": "float", "default": 0.5, "description": "Probability of edge creation",
|
| 732 |
+
"min": 0.1, "max": 1.0},
|
| 733 |
+
{"name": "p_layers", "type": "int", "default": 1, "description": "Number of QAOA layers",
|
| 734 |
+
"min": 1, "max": 3},
|
| 735 |
+
{"name": "noise", "type": "float", "default": 0.0, "description": "Noise probability",
|
| 736 |
+
"min": 0.0, "max": 0.3},
|
| 737 |
+
{"name": "num_samples", "type": "int", "default": 100, "description": "Number of samples",
|
| 738 |
+
"min": 10, "max": 500}
|
| 739 |
+
],
|
| 740 |
+
"run": lambda p: run_plugin(run_qaoa, _plugin_key="qaoa", n_nodes=p["n_nodes"], edge_probability=p["edge_probability"],
|
| 741 |
+
p_layers=p["p_layers"], noise_prob=p["noise"], num_samples=p["num_samples"])
|
| 742 |
+
}
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
def get_template_path(plugin_key):
|
| 746 |
+
"""
|
| 747 |
+
Returns the appropriate template file path for the plugin's educational content.
|
| 748 |
+
This function only returns the path, it doesn't extract content.
|
| 749 |
+
"""
|
| 750 |
+
templates = {
|
| 751 |
+
'bb84': 'educational/bb84.html',
|
| 752 |
+
'teleport': 'educational/teleport.html',
|
| 753 |
+
'grover': 'educational/grover.html',
|
| 754 |
+
'handshake': 'educational/handshake.html',
|
| 755 |
+
'auth': 'educational/auth.html',
|
| 756 |
+
'network': 'educational/network.html',
|
| 757 |
+
'qrng': 'educational/qrng.html',
|
| 758 |
+
'shor': 'educational/shor.html',
|
| 759 |
+
'vqe': 'educational/vqe.html',
|
| 760 |
+
'quantum_decryption_grover': 'educational/grover.html',
|
| 761 |
+
'quantum_decryption_shor': 'educational/shor.html',
|
| 762 |
+
'deutsch_jozsa': 'educational/deutsch_jozsa.html',
|
| 763 |
+
'qft': 'educational/qft.html',
|
| 764 |
+
'phase_estimation': 'educational/phase_estimation.html',
|
| 765 |
+
'qaoa': 'educational/qaoa.html',
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
# Return the template path or a default
|
| 769 |
+
template_name = templates.get(plugin_key, 'educational/default.html')
|
| 770 |
+
return os.path.join('templates', template_name)
|
| 771 |
+
|
| 772 |
+
def extract_educational_content(template_path):
|
| 773 |
+
"""
|
| 774 |
+
Extracts the full educational content from a template file.
|
| 775 |
+
Looks for content between <!-- EDUCATIONAL-CONTENT BEGIN --> and <!-- EDUCATIONAL-CONTENT END -->
|
| 776 |
+
"""
|
| 777 |
+
try:
|
| 778 |
+
if not os.path.exists(template_path):
|
| 779 |
+
logger.warning(f"Template file not found: {template_path}")
|
| 780 |
+
return None
|
| 781 |
+
|
| 782 |
+
with open(template_path, 'r') as f:
|
| 783 |
+
content = f.read()
|
| 784 |
+
|
| 785 |
+
# Use regex to extract content between markers
|
| 786 |
+
content_pattern = re.compile(r'<!-- EDUCATIONAL-CONTENT BEGIN -->(.*?)<!-- EDUCATIONAL-CONTENT END -->',
|
| 787 |
+
re.DOTALL)
|
| 788 |
+
match = content_pattern.search(content)
|
| 789 |
+
|
| 790 |
+
if match:
|
| 791 |
+
return match.group(1).strip()
|
| 792 |
+
else:
|
| 793 |
+
logger.warning(f"Educational content markers not found in {template_path}")
|
| 794 |
+
return None
|
| 795 |
+
|
| 796 |
+
except Exception as e:
|
| 797 |
+
logger.error(f"Error extracting educational content: {e}")
|
| 798 |
+
return None
|
| 799 |
+
|
| 800 |
+
def extract_mini_explanation(template_path):
|
| 801 |
+
"""
|
| 802 |
+
Extracts mini explanation from a template file.
|
| 803 |
+
"""
|
| 804 |
+
try:
|
| 805 |
+
if not os.path.exists(template_path):
|
| 806 |
+
logger.warning(f"Template file not found: {template_path}")
|
| 807 |
+
return None
|
| 808 |
+
|
| 809 |
+
with open(template_path, 'r') as f:
|
| 810 |
+
content = f.read()
|
| 811 |
+
|
| 812 |
+
# Try all possible marker formats
|
| 813 |
+
marker_patterns = [
|
| 814 |
+
r'<!-- MINI_EXPLANATION_START -->(.*?)<!-- MINI_EXPLANATION_END -->'
|
| 815 |
+
]
|
| 816 |
+
|
| 817 |
+
for pattern in marker_patterns:
|
| 818 |
+
mini_pattern = re.compile(pattern, re.DOTALL)
|
| 819 |
+
match = mini_pattern.search(content)
|
| 820 |
+
|
| 821 |
+
if match:
|
| 822 |
+
logger.info(f"Found mini explanation using pattern: {pattern}")
|
| 823 |
+
return match.group(1).strip()
|
| 824 |
+
|
| 825 |
+
logger.warning(f"No mini explanation markers found in {template_path}")
|
| 826 |
+
return None
|
| 827 |
+
except Exception as e:
|
| 828 |
+
logger.error(f"Error extracting mini explanation: {e}")
|
| 829 |
+
return None
|
| 830 |
+
|
| 831 |
+
def get_mini_explanation(plugin_key):
|
| 832 |
+
"""
|
| 833 |
+
Gets the mini explanation for a plugin, either from its template file
|
| 834 |
+
or returns a default explanation based on the plugin info.
|
| 835 |
+
"""
|
| 836 |
+
# Get the template path
|
| 837 |
+
template_path = get_template_path(plugin_key)
|
| 838 |
+
|
| 839 |
+
# Try to extract from template file
|
| 840 |
+
mini_content = extract_mini_explanation(template_path)
|
| 841 |
+
|
| 842 |
+
if mini_content:
|
| 843 |
+
return mini_content
|
| 844 |
+
|
| 845 |
+
# If no template or no mini section, generate default mini explanation
|
| 846 |
+
plugin = PLUGINS.get(plugin_key, {})
|
| 847 |
+
plugin_name = plugin.get('name', plugin_key.replace('_', ' ').title())
|
| 848 |
+
|
| 849 |
+
return f"""
|
| 850 |
+
<h5 class="fw-bold">{plugin_name}</h5>
|
| 851 |
+
<p>This simulation demonstrates key quantum computing concepts including superposition, entanglement, and measurement.</p>
|
| 852 |
+
<div class="alert alert-primary">
|
| 853 |
+
<strong>Key Quantum Concept:</strong> Quantum simulations provide insight into quantum behavior without requiring actual quantum hardware.
|
| 854 |
+
</div>
|
| 855 |
+
"""
|
| 856 |
+
|
| 857 |
+
def get_educational_content(plugin_key):
|
| 858 |
+
"""
|
| 859 |
+
Gets the educational content for a plugin's modal.
|
| 860 |
+
"""
|
| 861 |
+
template_path = get_template_path(plugin_key)
|
| 862 |
+
print(f"Template path: {template_path}")
|
| 863 |
+
return extract_educational_content(template_path)
|
| 864 |
+
|
| 865 |
+
# --- Route handlers ---
|
| 866 |
+
@app.route("/")
|
| 867 |
+
def index():
|
| 868 |
+
# Serve React index.html
|
| 869 |
+
return send_from_directory(app.static_folder, 'index.html')
|
| 870 |
+
|
| 871 |
+
@app.route('/sitemap.xml')
|
| 872 |
+
def sitemap():
|
| 873 |
+
"""Generate the sitemap.xml file dynamically."""
|
| 874 |
+
# Get base URL from request or use production URL
|
| 875 |
+
if app.config.get('ENV') == 'production':
|
| 876 |
+
base_url = "https://quantumfieldkit.com"
|
| 877 |
+
else:
|
| 878 |
+
base_url = request.url_root.rstrip('/')
|
| 879 |
+
|
| 880 |
+
# Current date for lastmod
|
| 881 |
+
import datetime
|
| 882 |
+
today = datetime.date.today().isoformat()
|
| 883 |
+
|
| 884 |
+
# Build the sitemap XML string
|
| 885 |
+
xml = ['<?xml version="1.0" encoding="UTF-8"?>',
|
| 886 |
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
| 887 |
+
' <url>',
|
| 888 |
+
f' <loc>{base_url}/</loc>',
|
| 889 |
+
f' <lastmod>{today}</lastmod>',
|
| 890 |
+
' <changefreq>weekly</changefreq>',
|
| 891 |
+
' <priority>1.0</priority>',
|
| 892 |
+
' </url>']
|
| 893 |
+
|
| 894 |
+
# Add glossary page
|
| 895 |
+
xml.append(' <url>')
|
| 896 |
+
xml.append(f' <loc>{base_url}/glossary</loc>')
|
| 897 |
+
xml.append(f' <lastmod>{today}</lastmod>')
|
| 898 |
+
xml.append(' <changefreq>monthly</changefreq>')
|
| 899 |
+
xml.append(' <priority>0.8</priority>')
|
| 900 |
+
xml.append(' </url>')
|
| 901 |
+
|
| 902 |
+
|
| 903 |
+
# Add all plugin pages with categorization
|
| 904 |
+
for plugin_key, plugin in PLUGINS.items():
|
| 905 |
+
# Group plugins by category for better SEO
|
| 906 |
+
category = plugin.get("category", "other")
|
| 907 |
+
xml.append(' <url>')
|
| 908 |
+
xml.append(f' <loc>{base_url}/plugin/{plugin_key}</loc>')
|
| 909 |
+
xml.append(f' <lastmod>{today}</lastmod>')
|
| 910 |
+
xml.append(' <changefreq>monthly</changefreq>')
|
| 911 |
+
xml.append(' <priority>0.8</priority>')
|
| 912 |
+
xml.append(' </url>')
|
| 913 |
+
|
| 914 |
+
# Add category pages if you implement them
|
| 915 |
+
categories = set(plugin.get("category", "other") for plugin in PLUGINS.values())
|
| 916 |
+
for category in categories:
|
| 917 |
+
xml.append(' <url>')
|
| 918 |
+
xml.append(f' <loc>{base_url}/category/{category}</loc>')
|
| 919 |
+
xml.append(f' <lastmod>{today}</lastmod>')
|
| 920 |
+
xml.append(' <changefreq>monthly</changefreq>')
|
| 921 |
+
xml.append(' <priority>0.7</priority>')
|
| 922 |
+
xml.append(' </url>')
|
| 923 |
+
|
| 924 |
+
xml.append('</urlset>')
|
| 925 |
+
|
| 926 |
+
return app.response_class(
|
| 927 |
+
response='\n'.join(xml),
|
| 928 |
+
status=200,
|
| 929 |
+
mimetype='application/xml'
|
| 930 |
+
)
|
| 931 |
+
|
| 932 |
+
@app.route("/sitemap")
|
| 933 |
+
def html_sitemap():
|
| 934 |
+
# Serve SPA index for HTML sitemap requests
|
| 935 |
+
return send_from_directory(app.static_folder, 'index.html')
|
| 936 |
+
|
| 937 |
+
@app.route('/robots.txt')
|
| 938 |
+
def robots():
|
| 939 |
+
"""Serve robots.txt dynamically."""
|
| 940 |
+
if app.config.get('ENV') == 'production':
|
| 941 |
+
base_url = "https://quantumfieldkit.com"
|
| 942 |
+
else:
|
| 943 |
+
base_url = request.url_root.rstrip('/')
|
| 944 |
+
|
| 945 |
+
robots_txt = f"""User-agent: *
|
| 946 |
+
Allow: /
|
| 947 |
+
|
| 948 |
+
Sitemap: {base_url}/sitemap.xml
|
| 949 |
+
"""
|
| 950 |
+
return app.response_class(
|
| 951 |
+
response=robots_txt,
|
| 952 |
+
status=200,
|
| 953 |
+
mimetype='text/plain'
|
| 954 |
+
)
|
| 955 |
+
|
| 956 |
+
@app.route("/category/<category>")
|
| 957 |
+
def category_view(category):
|
| 958 |
+
return send_from_directory(app.static_folder, 'index.html')
|
| 959 |
+
|
| 960 |
+
@app.route("/glossary")
|
| 961 |
+
def glossary():
|
| 962 |
+
return send_from_directory(app.static_folder, 'index.html')
|
| 963 |
+
|
| 964 |
+
@app.after_request
|
| 965 |
+
def add_cache_headers(response):
|
| 966 |
+
"""Add cache headers to responses to improve performance."""
|
| 967 |
+
import datetime
|
| 968 |
+
# Don't cache dynamic content
|
| 969 |
+
if request.path.startswith('/static/'):
|
| 970 |
+
# Cache static files for 1 week
|
| 971 |
+
expiry = datetime.datetime.now() + datetime.timedelta(days=7)
|
| 972 |
+
response.headers['Cache-Control'] = 'public, max-age=604800'
|
| 973 |
+
response.headers['Expires'] = expiry.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
| 974 |
+
else:
|
| 975 |
+
# Don't cache dynamic content
|
| 976 |
+
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
| 977 |
+
|
| 978 |
+
return response
|
| 979 |
+
|
| 980 |
+
@app.route("/plugin/<plugin_key>", methods=["GET", "POST"])
|
| 981 |
+
def plugin_view(plugin_key):
|
| 982 |
+
if request.method == 'POST':
|
| 983 |
+
# Support legacy AJAX POSTs from static JS (if any)
|
| 984 |
+
if plugin_key not in PLUGINS:
|
| 985 |
+
return jsonify({"error": "Plugin not found"}), 404
|
| 986 |
+
plugin = PLUGINS[plugin_key]
|
| 987 |
+
try:
|
| 988 |
+
raw_params = {}
|
| 989 |
+
for param in plugin.get('parameters', []):
|
| 990 |
+
param_name = param['name']
|
| 991 |
+
if param_name in request.form:
|
| 992 |
+
raw_params[param_name] = request.form.get(param_name)
|
| 993 |
+
params = validate_parameters(plugin, raw_params)
|
| 994 |
+
if 'run' not in plugin:
|
| 995 |
+
return jsonify({"error": "Plugin runner not defined"}), 500
|
| 996 |
+
result = plugin['run'](params)
|
| 997 |
+
return jsonify(result)
|
| 998 |
+
except Exception as e:
|
| 999 |
+
return jsonify({"error": str(e)}), 400
|
| 1000 |
+
# GET falls back to React
|
| 1001 |
+
return send_from_directory(app.static_folder, 'index.html')
|
| 1002 |
+
|
| 1003 |
+
@app.route("/api/plugins", methods=["GET"])
|
| 1004 |
+
def api_plugins():
|
| 1005 |
+
"""Return a list of available plugins."""
|
| 1006 |
+
categories = {}
|
| 1007 |
+
for key, plugin in PLUGINS.items():
|
| 1008 |
+
category = plugin.get("category", "other")
|
| 1009 |
+
if category not in categories:
|
| 1010 |
+
categories[category] = []
|
| 1011 |
+
# Only include serializable data
|
| 1012 |
+
serializable_plugin = {
|
| 1013 |
+
"key": key,
|
| 1014 |
+
"name": plugin.get("name"),
|
| 1015 |
+
"description": plugin.get("description"),
|
| 1016 |
+
"icon": plugin.get("icon"),
|
| 1017 |
+
"category": plugin.get("category"),
|
| 1018 |
+
"parameters": plugin.get("parameters", [])
|
| 1019 |
+
}
|
| 1020 |
+
categories[category].append(serializable_plugin)
|
| 1021 |
+
return jsonify(categories)
|
| 1022 |
+
|
| 1023 |
+
@app.route("/api/plugin/<plugin_key>", methods=["GET"])
|
| 1024 |
+
def api_plugin(plugin_key):
|
| 1025 |
+
"""Return details for a specific plugin."""
|
| 1026 |
+
if plugin_key not in PLUGINS:
|
| 1027 |
+
return jsonify({"error": "Plugin not found"}), 404
|
| 1028 |
+
|
| 1029 |
+
plugin = PLUGINS[plugin_key]
|
| 1030 |
+
# Only include serializable data
|
| 1031 |
+
serializable_plugin = {
|
| 1032 |
+
"key": plugin_key,
|
| 1033 |
+
"name": plugin.get("name"),
|
| 1034 |
+
"description": plugin.get("description"),
|
| 1035 |
+
"icon": plugin.get("icon"),
|
| 1036 |
+
"category": plugin.get("category"),
|
| 1037 |
+
"parameters": plugin.get("parameters", [])
|
| 1038 |
+
}
|
| 1039 |
+
return jsonify(serializable_plugin)
|
| 1040 |
+
|
| 1041 |
+
@app.route("/api/run/<plugin_key>", methods=["POST"])
|
| 1042 |
+
def api_run_plugin(plugin_key):
|
| 1043 |
+
"""Run a plugin simulation."""
|
| 1044 |
+
if plugin_key not in PLUGINS:
|
| 1045 |
+
return jsonify({"error": "Plugin not found"}), 404
|
| 1046 |
+
try:
|
| 1047 |
+
params = request.get_json()
|
| 1048 |
+
plugin = PLUGINS[plugin_key]
|
| 1049 |
+
if 'run' in plugin and callable(plugin['run']):
|
| 1050 |
+
# Use standardized runner which already wraps results
|
| 1051 |
+
result = plugin['run'](params or {})
|
| 1052 |
+
elif 'function' in plugin and callable(plugin['function']):
|
| 1053 |
+
# Fallback for legacy plugins
|
| 1054 |
+
result = run_plugin(plugin['function'], _plugin_key=plugin_key, **(params or {}))
|
| 1055 |
+
else:
|
| 1056 |
+
return jsonify({"error": "Plugin is misconfigured"}), 500
|
| 1057 |
+
return jsonify(result)
|
| 1058 |
+
except Exception as e:
|
| 1059 |
+
return jsonify({"error": str(e)}), 400
|
| 1060 |
+
|
| 1061 |
+
@app.route("/api/glossary", methods=["GET"])
|
| 1062 |
+
def api_glossary():
|
| 1063 |
+
"""Return glossary terms."""
|
| 1064 |
+
# Prefer glossary shipped with the built SPA, then public, then repo static fallback
|
| 1065 |
+
candidates = [
|
| 1066 |
+
os.path.join(os.path.dirname(__file__), 'frontend', 'build', 'data', 'glossary_terms.json'),
|
| 1067 |
+
os.path.join(os.path.dirname(__file__), 'frontend', 'public', 'data', 'glossary_terms.json'),
|
| 1068 |
+
os.path.join(os.path.dirname(__file__), 'static', 'data', 'glossary_terms.json'),
|
| 1069 |
+
]
|
| 1070 |
+
terms_file = next((p for p in candidates if os.path.exists(p)), None)
|
| 1071 |
+
try:
|
| 1072 |
+
with open(terms_file, 'r') as f:
|
| 1073 |
+
terms = json.load(f)
|
| 1074 |
+
return jsonify(terms)
|
| 1075 |
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
| 1076 |
+
app.logger.error(f"Error loading glossary terms: {e}")
|
| 1077 |
+
return jsonify([{"term": "Qubit", "definition": "The fundamental unit of quantum information."},
|
| 1078 |
+
{"term": "Superposition", "definition": "A quantum property allowing particles to exist in multiple states."}])
|
| 1079 |
+
|
| 1080 |
+
@app.route("/api/category/<category>", methods=["GET"])
|
| 1081 |
+
def api_category(category):
|
| 1082 |
+
"""Return plugins in a specific category."""
|
| 1083 |
+
plugins_in_category = {k: v for k, v in PLUGINS.items() if v.get("category", "other") == category}
|
| 1084 |
+
if not plugins_in_category:
|
| 1085 |
+
return jsonify({"error": "Category not found"}), 404
|
| 1086 |
+
return jsonify(plugins_in_category)
|
| 1087 |
+
|
| 1088 |
+
@app.route("/api/educational/<plugin_key>", methods=["GET"])
|
| 1089 |
+
def api_educational(plugin_key):
|
| 1090 |
+
"""Return educational content for a specific plugin."""
|
| 1091 |
+
if plugin_key not in PLUGINS:
|
| 1092 |
+
return jsonify({"error": "Plugin not found"}), 404
|
| 1093 |
+
|
| 1094 |
+
try:
|
| 1095 |
+
# Get the template path for the plugin
|
| 1096 |
+
template_path = get_template_path(plugin_key)
|
| 1097 |
+
|
| 1098 |
+
# Extract educational content
|
| 1099 |
+
content = extract_educational_content(template_path)
|
| 1100 |
+
|
| 1101 |
+
if content:
|
| 1102 |
+
return jsonify({"content": content})
|
| 1103 |
+
else:
|
| 1104 |
+
return jsonify({"error": "Educational content not found"}), 404
|
| 1105 |
+
|
| 1106 |
+
except Exception as e:
|
| 1107 |
+
logger.error(f"Error loading educational content: {e}")
|
| 1108 |
+
return jsonify({"error": "Failed to load educational content"}), 500
|
| 1109 |
+
|
| 1110 |
+
@app.route("/api/validate/<plugin_key>", methods=["POST", "OPTIONS"])
|
| 1111 |
+
def api_validate(plugin_key):
|
| 1112 |
+
"""Validate parameters for a specific plugin."""
|
| 1113 |
+
if request.method == "OPTIONS":
|
| 1114 |
+
return jsonify({"status": "ok"}), 200
|
| 1115 |
+
|
| 1116 |
+
if plugin_key not in PLUGINS:
|
| 1117 |
+
return jsonify({"error": "Plugin not found"}), 404
|
| 1118 |
+
|
| 1119 |
+
try:
|
| 1120 |
+
params = request.get_json()
|
| 1121 |
+
plugin = PLUGINS[plugin_key]
|
| 1122 |
+
validated_params = validate_parameters(plugin, params)
|
| 1123 |
+
return jsonify({"status": "valid", "params": validated_params})
|
| 1124 |
+
except ParameterError as e:
|
| 1125 |
+
return jsonify({
|
| 1126 |
+
"status": "invalid",
|
| 1127 |
+
"error": e.message,
|
| 1128 |
+
"param_info": e.param_info,
|
| 1129 |
+
"suggestion": e.suggestion
|
| 1130 |
+
}), 400
|
| 1131 |
+
except Exception as e:
|
| 1132 |
+
return jsonify({"status": "error", "error": str(e)}), 500
|
| 1133 |
+
|
| 1134 |
+
@app.route("/circuit")
|
| 1135 |
+
def circuit_designer():
|
| 1136 |
+
# SPA route
|
| 1137 |
+
return send_from_directory(app.static_folder, 'index.html')
|
| 1138 |
+
|
| 1139 |
+
# API routes for Circuit Designer
|
| 1140 |
+
|
| 1141 |
+
@app.route("/api/circuit/gates", methods=["GET"])
|
| 1142 |
+
def api_circuit_gates():
|
| 1143 |
+
"""Get available quantum gates for the circuit designer"""
|
| 1144 |
+
try:
|
| 1145 |
+
# Basic set of gates organized by categories
|
| 1146 |
+
gates = {
|
| 1147 |
+
"single_qubit": [
|
| 1148 |
+
{"id": "x", "name": "X", "description": "Pauli-X gate (NOT gate)", "symbol": "X"},
|
| 1149 |
+
{"id": "y", "name": "Y", "description": "Pauli-Y gate", "symbol": "Y"},
|
| 1150 |
+
{"id": "z", "name": "Z", "description": "Pauli-Z gate", "symbol": "Z"},
|
| 1151 |
+
{"id": "h", "name": "H", "description": "Hadamard gate", "symbol": "H"},
|
| 1152 |
+
{"id": "s", "name": "S", "description": "Phase gate (S)", "symbol": "S"},
|
| 1153 |
+
{"id": "t", "name": "T", "description": "π/8 gate (T)", "symbol": "T"},
|
| 1154 |
+
{"id": "rx", "name": "RX", "description": "Rotation around X-axis", "symbol": "RX", "params": [{"name": "theta", "default": "π/2"}]},
|
| 1155 |
+
{"id": "ry", "name": "RY", "description": "Rotation around Y-axis", "symbol": "RY", "params": [{"name": "theta", "default": "π/2"}]},
|
| 1156 |
+
{"id": "rz", "name": "RZ", "description": "Rotation around Z-axis", "symbol": "RZ", "params": [{"name": "theta", "default": "π/2"}]}
|
| 1157 |
+
],
|
| 1158 |
+
"multi_qubit": [
|
| 1159 |
+
{"id": "cnot", "name": "CNOT", "description": "Controlled-NOT gate", "symbol": "CNOT", "qubits": 2},
|
| 1160 |
+
{"id": "cz", "name": "CZ", "description": "Controlled-Z gate", "symbol": "CZ", "qubits": 2},
|
| 1161 |
+
{"id": "swap", "name": "SWAP", "description": "SWAP gate", "symbol": "SWAP", "qubits": 2},
|
| 1162 |
+
{"id": "ccx", "name": "Toffoli", "description": "Toffoli gate (CCX)", "symbol": "CCX", "qubits": 3},
|
| 1163 |
+
{"id": "cswap", "name": "Fredkin", "description": "Fredkin gate (CSWAP)", "symbol": "CSWAP", "qubits": 3}
|
| 1164 |
+
],
|
| 1165 |
+
"special": [
|
| 1166 |
+
{"id": "measure", "name": "Measure", "description": "Measurement operation", "symbol": "M"},
|
| 1167 |
+
{"id": "reset", "name": "Reset", "description": "Reset qubit to |0⟩", "symbol": "R"}
|
| 1168 |
+
]
|
| 1169 |
+
}
|
| 1170 |
+
return jsonify(gates)
|
| 1171 |
+
except Exception as e:
|
| 1172 |
+
logger.error(f"Error getting available gates: {str(e)}")
|
| 1173 |
+
return jsonify({"error": "Failed to get available gates"}), 500
|
| 1174 |
+
|
| 1175 |
+
@app.route("/api/circuit/simulate", methods=["POST"])
|
| 1176 |
+
def api_circuit_simulate():
|
| 1177 |
+
"""Simulate a quantum circuit"""
|
| 1178 |
+
try:
|
| 1179 |
+
data = request.get_json()
|
| 1180 |
+
if not data:
|
| 1181 |
+
return jsonify({"error": "No circuit data provided"}), 400
|
| 1182 |
+
|
| 1183 |
+
circuit = data.get("circuit")
|
| 1184 |
+
shots = data.get("shots", 1024)
|
| 1185 |
+
|
| 1186 |
+
if not circuit:
|
| 1187 |
+
return jsonify({"error": "No circuit definition provided"}), 400
|
| 1188 |
+
|
| 1189 |
+
# Ensure shots is an integer and within reasonable limits
|
| 1190 |
+
try:
|
| 1191 |
+
shots = int(shots)
|
| 1192 |
+
if shots < 1 or shots > 10000:
|
| 1193 |
+
return jsonify({"error": "Shots must be between 1 and 10000"}), 400
|
| 1194 |
+
except ValueError:
|
| 1195 |
+
return jsonify({"error": "Shots must be an integer"}), 400
|
| 1196 |
+
|
| 1197 |
+
# This is where we'd build and simulate the circuit using Cirq
|
| 1198 |
+
# For now, we'll use mock data
|
| 1199 |
+
|
| 1200 |
+
import numpy as np
|
| 1201 |
+
import cirq
|
| 1202 |
+
|
| 1203 |
+
# Convert JSON circuit representation to a Cirq circuit
|
| 1204 |
+
cirq_circuit = cirq.Circuit()
|
| 1205 |
+
qubits = [cirq.LineQubit(i) for i in range(circuit.get("num_qubits", 3))]
|
| 1206 |
+
|
| 1207 |
+
# Add gates to circuit based on the circuit data
|
| 1208 |
+
# This would need to be implemented based on your circuit JSON schema
|
| 1209 |
+
|
| 1210 |
+
# For now, return mock simulation results
|
| 1211 |
+
result = {
|
| 1212 |
+
"state_vector": {
|
| 1213 |
+
"amplitudes": [
|
| 1214 |
+
{"state": "000", "amplitude": 1.0, "probability": 1.0}
|
| 1215 |
+
# Additional states would be added here in a real implementation
|
| 1216 |
+
],
|
| 1217 |
+
"visualization_data": {} # Visualization data would go here
|
| 1218 |
+
},
|
| 1219 |
+
"measurements": {
|
| 1220 |
+
"counts": {"000": shots},
|
| 1221 |
+
"probabilities": {"000": 1.0}
|
| 1222 |
+
},
|
| 1223 |
+
"circuit_representation": {
|
| 1224 |
+
"qubits": circuit.get("num_qubits", 3),
|
| 1225 |
+
"depth": len(circuit.get("gates", [])),
|
| 1226 |
+
"gates": circuit.get("gates", [])
|
| 1227 |
+
}
|
| 1228 |
+
}
|
| 1229 |
+
|
| 1230 |
+
return jsonify(result)
|
| 1231 |
+
except Exception as e:
|
| 1232 |
+
logger.error(f"Error simulating circuit: {str(e)}\n{traceback.format_exc()}")
|
| 1233 |
+
return jsonify({"error": f"Failed to simulate circuit: {str(e)}"}), 500
|
| 1234 |
+
|
| 1235 |
+
@app.route("/api/circuit/save", methods=["POST"])
|
| 1236 |
+
def api_circuit_save():
|
| 1237 |
+
"""Save a quantum circuit"""
|
| 1238 |
+
try:
|
| 1239 |
+
data = request.get_json()
|
| 1240 |
+
if not data:
|
| 1241 |
+
return jsonify({"error": "No circuit data provided"}), 400
|
| 1242 |
+
|
| 1243 |
+
# Here you would save the circuit to a database or file
|
| 1244 |
+
# For this example, we'll pretend to save it and return a mock ID
|
| 1245 |
+
|
| 1246 |
+
circuit_id = datetime.now().strftime("%Y%m%d%H%M%S")
|
| 1247 |
+
|
| 1248 |
+
return jsonify({
|
| 1249 |
+
"id": circuit_id,
|
| 1250 |
+
"name": data.get("name", "Unnamed Circuit"),
|
| 1251 |
+
"saved": True,
|
| 1252 |
+
"timestamp": datetime.now().isoformat()
|
| 1253 |
+
})
|
| 1254 |
+
except Exception as e:
|
| 1255 |
+
logger.error(f"Error saving circuit: {str(e)}")
|
| 1256 |
+
return jsonify({"error": f"Failed to save circuit: {str(e)}"}), 500
|
| 1257 |
+
|
| 1258 |
+
@app.route("/api/circuit/load/<circuit_id>", methods=["GET"])
|
| 1259 |
+
def api_circuit_load(circuit_id):
|
| 1260 |
+
"""Load a saved quantum circuit"""
|
| 1261 |
+
try:
|
| 1262 |
+
# Here you would load the circuit from a database or file
|
| 1263 |
+
# For this example, we'll return a mock circuit
|
| 1264 |
+
|
| 1265 |
+
mock_circuit = {
|
| 1266 |
+
"id": circuit_id,
|
| 1267 |
+
"name": f"Circuit {circuit_id}",
|
| 1268 |
+
"num_qubits": 3,
|
| 1269 |
+
"gates": [
|
| 1270 |
+
{"type": "h", "targets": [0]},
|
| 1271 |
+
{"type": "cnot", "controls": [0], "targets": [1]},
|
| 1272 |
+
{"type": "measure", "targets": [0, 1]}
|
| 1273 |
+
],
|
| 1274 |
+
"created_at": datetime.now().isoformat()
|
| 1275 |
+
}
|
| 1276 |
+
|
| 1277 |
+
return jsonify(mock_circuit)
|
| 1278 |
+
except Exception as e:
|
| 1279 |
+
logger.error(f"Error loading circuit {circuit_id}: {str(e)}")
|
| 1280 |
+
return jsonify({"error": f"Failed to load circuit: {str(e)}"}), 500
|
| 1281 |
+
|
| 1282 |
+
@app.route("/api/circuit/saved", methods=["GET"])
|
| 1283 |
+
def api_circuit_saved():
|
| 1284 |
+
"""Get list of saved circuits"""
|
| 1285 |
+
try:
|
| 1286 |
+
# Here you would query a database for saved circuits
|
| 1287 |
+
# For this example, we'll return mock data
|
| 1288 |
+
|
| 1289 |
+
mock_saved_circuits = [
|
| 1290 |
+
{"id": "20230512120000", "name": "Bell State", "num_qubits": 2, "created_at": "2023-05-12T12:00:00"},
|
| 1291 |
+
{"id": "20230513130000", "name": "GHZ State", "num_qubits": 3, "created_at": "2023-05-13T13:00:00"},
|
| 1292 |
+
{"id": "20230514140000", "name": "Quantum Teleportation", "num_qubits": 3, "created_at": "2023-05-14T14:00:00"}
|
| 1293 |
+
]
|
| 1294 |
+
|
| 1295 |
+
return jsonify(mock_saved_circuits)
|
| 1296 |
+
except Exception as e:
|
| 1297 |
+
logger.error(f"Error getting saved circuits: {str(e)}")
|
| 1298 |
+
return jsonify({"error": f"Failed to get saved circuits: {str(e)}"}), 500
|
| 1299 |
+
|
| 1300 |
+
@app.route("/api/circuit/export", methods=["POST"])
|
| 1301 |
+
def api_circuit_export():
|
| 1302 |
+
"""Export circuit to code (Cirq, Qiskit, etc.)"""
|
| 1303 |
+
try:
|
| 1304 |
+
data = request.get_json()
|
| 1305 |
+
if not data:
|
| 1306 |
+
return jsonify({"error": "No circuit data provided"}), 400
|
| 1307 |
+
|
| 1308 |
+
circuit = data.get("circuit")
|
| 1309 |
+
format = data.get("format", "cirq").lower()
|
| 1310 |
+
|
| 1311 |
+
if not circuit:
|
| 1312 |
+
return jsonify({"error": "No circuit definition provided"}), 400
|
| 1313 |
+
|
| 1314 |
+
if format not in ["cirq", "qiskit"]:
|
| 1315 |
+
return jsonify({"error": f"Unsupported export format: {format}"}), 400
|
| 1316 |
+
|
| 1317 |
+
# Generate code based on the circuit definition
|
| 1318 |
+
num_qubits = circuit.get("num_qubits", 3)
|
| 1319 |
+
|
| 1320 |
+
if format == "cirq":
|
| 1321 |
+
code = f"""
|
| 1322 |
+
import cirq
|
| 1323 |
+
import numpy as np
|
| 1324 |
+
|
| 1325 |
+
# Create a circuit with {num_qubits} qubits
|
| 1326 |
+
circuit = cirq.Circuit()
|
| 1327 |
+
|
| 1328 |
+
# Define qubits
|
| 1329 |
+
qubits = [cirq.LineQubit(i) for i in range({num_qubits})]
|
| 1330 |
+
|
| 1331 |
+
# Add gates
|
| 1332 |
+
"""
|
| 1333 |
+
# Here you would iterate through the gates and add them to the code
|
| 1334 |
+
|
| 1335 |
+
code += """
|
| 1336 |
+
# Simulate
|
| 1337 |
+
simulator = cirq.Simulator()
|
| 1338 |
+
result = simulator.simulate(circuit)
|
| 1339 |
+
|
| 1340 |
+
print("Final state vector:")
|
| 1341 |
+
print(result.final_state_vector)
|
| 1342 |
+
"""
|
| 1343 |
+
elif format == "qiskit":
|
| 1344 |
+
code = f"""
|
| 1345 |
+
from qiskit import QuantumCircuit, Aer, execute
|
| 1346 |
+
import numpy as np
|
| 1347 |
+
|
| 1348 |
+
# Create a circuit with {num_qubits} qubits and {num_qubits} classical bits
|
| 1349 |
+
qc = QuantumCircuit({num_qubits}, {num_qubits})
|
| 1350 |
+
|
| 1351 |
+
# Add gates
|
| 1352 |
+
"""
|
| 1353 |
+
# Here you would iterate through the gates and add them to the code
|
| 1354 |
+
|
| 1355 |
+
code += """
|
| 1356 |
+
# Simulate
|
| 1357 |
+
simulator = Aer.get_backend('statevector_simulator')
|
| 1358 |
+
result = execute(qc, simulator).result()
|
| 1359 |
+
statevector = result.get_statevector()
|
| 1360 |
+
|
| 1361 |
+
print("Final state vector:")
|
| 1362 |
+
print(statevector)
|
| 1363 |
+
"""
|
| 1364 |
+
|
| 1365 |
+
return jsonify({
|
| 1366 |
+
"code": code,
|
| 1367 |
+
"format": format
|
| 1368 |
+
})
|
| 1369 |
+
except Exception as e:
|
| 1370 |
+
logger.error(f"Error exporting circuit: {str(e)}")
|
| 1371 |
+
return jsonify({"error": f"Failed to export circuit: {str(e)}"}), 500
|
| 1372 |
+
|
| 1373 |
+
# --- Socket.IO event handlers for real-time updates ---
|
| 1374 |
+
@socketio.on('connect')
|
| 1375 |
+
def handle_connect():
|
| 1376 |
+
"""Handle client connection."""
|
| 1377 |
+
logger.info(f"Client connected: {request.sid}")
|
| 1378 |
+
|
| 1379 |
+
@socketio.on('disconnect')
|
| 1380 |
+
def handle_disconnect():
|
| 1381 |
+
"""Handle client disconnection."""
|
| 1382 |
+
logger.info(f"Client disconnected: {request.sid}")
|
| 1383 |
+
|
| 1384 |
+
@socketio.on('run_plugin')
|
| 1385 |
+
def handle_run_plugin(data):
|
| 1386 |
+
"""Run a plugin and emit progress updates."""
|
| 1387 |
+
plugin_key = data.get('plugin_key')
|
| 1388 |
+
raw_params = data.get('params', {})
|
| 1389 |
+
|
| 1390 |
+
if plugin_key not in PLUGINS:
|
| 1391 |
+
emit('plugin_error', {'error': f"Plugin '{plugin_key}' not found"})
|
| 1392 |
+
return
|
| 1393 |
+
|
| 1394 |
+
plugin = PLUGINS[plugin_key]
|
| 1395 |
+
|
| 1396 |
+
try:
|
| 1397 |
+
# Validate parameters
|
| 1398 |
+
params = validate_parameters(plugin, raw_params)
|
| 1399 |
+
|
| 1400 |
+
# Add plugin key for better error reporting
|
| 1401 |
+
params['_plugin_key'] = plugin_key
|
| 1402 |
+
|
| 1403 |
+
# Set up progress tracking
|
| 1404 |
+
session_id = request.sid
|
| 1405 |
+
|
| 1406 |
+
def progress_callback(step, total, message):
|
| 1407 |
+
progress = int(100 * step / total) if total > 0 else 0
|
| 1408 |
+
emit('plugin_progress', {
|
| 1409 |
+
'plugin_key': plugin_key,
|
| 1410 |
+
'progress': progress,
|
| 1411 |
+
'message': message
|
| 1412 |
+
})
|
| 1413 |
+
|
| 1414 |
+
# Add progress callback to parameters if supported
|
| 1415 |
+
params['progress_callback'] = progress_callback
|
| 1416 |
+
|
| 1417 |
+
# Run the plugin and emit the result
|
| 1418 |
+
emit('plugin_start', {'plugin_key': plugin_key})
|
| 1419 |
+
result = plugin["run"](params)
|
| 1420 |
+
emit('plugin_result', {'plugin_key': plugin_key, 'result': result})
|
| 1421 |
+
|
| 1422 |
+
except SimulationError as e:
|
| 1423 |
+
# Handle validation errors
|
| 1424 |
+
error_msg = str(e)
|
| 1425 |
+
if hasattr(e, 'suggestion') and e.suggestion:
|
| 1426 |
+
error_msg += f"\n\nSuggestion: {e.suggestion}"
|
| 1427 |
+
|
| 1428 |
+
emit('plugin_error', {'plugin_key': plugin_key, 'error': error_msg})
|
| 1429 |
+
except Exception as e:
|
| 1430 |
+
emit('plugin_error', {'plugin_key': plugin_key, 'error': str(e)})
|
| 1431 |
+
|
| 1432 |
+
# --- Error handling ---
|
| 1433 |
+
@app.errorhandler(404)
|
| 1434 |
+
def page_not_found(e):
|
| 1435 |
+
# SPA fallback
|
| 1436 |
+
try:
|
| 1437 |
+
return send_from_directory(app.static_folder, 'index.html')
|
| 1438 |
+
except Exception:
|
| 1439 |
+
return jsonify({"error": "Not found"}), 404
|
| 1440 |
+
|
| 1441 |
+
@app.errorhandler(500)
|
| 1442 |
+
def server_error(e):
|
| 1443 |
+
return jsonify({"error": "Server error"}), 500
|
| 1444 |
+
|
| 1445 |
+
# --- Main entry point ---
|
| 1446 |
+
if __name__ == "__main__":
|
| 1447 |
+
# SSL Configuration for production
|
| 1448 |
+
if os.environ.get('FLASK_ENV') == 'production' and os.path.exists('cert.pem') and os.path.exists('key.pem'):
|
| 1449 |
+
socketio.run(app, debug=False, host='0.0.0.0', port=int(os.environ.get('PORT', 5000)),
|
| 1450 |
+
certfile='cert.pem', keyfile='key.pem')
|
| 1451 |
+
else:
|
| 1452 |
+
# Development environment
|
| 1453 |
+
socketio.run(app, debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
quantum-app:
|
| 5 |
+
build:
|
| 6 |
+
context: .
|
| 7 |
+
dockerfile: Dockerfile
|
| 8 |
+
ports:
|
| 9 |
+
- "5000:8080"
|
| 10 |
+
environment:
|
| 11 |
+
- FLASK_ENV=development
|
| 12 |
+
- PORT=8080
|
| 13 |
+
- HOST=0.0.0.0
|
| 14 |
+
volumes:
|
| 15 |
+
- ./logs:/app/logs
|
| 16 |
+
- ./plugins:/app/plugins
|
| 17 |
+
restart: unless-stopped
|
| 18 |
+
healthcheck:
|
| 19 |
+
test: ["CMD", "curl", "-f", "http://localhost:8080/api/plugins"]
|
| 20 |
+
interval: 30s
|
| 21 |
+
timeout: 10s
|
| 22 |
+
retries: 3
|
| 23 |
+
start_period: 40s
|
| 24 |
+
|
| 25 |
+
# Optional: Add Redis for caching
|
| 26 |
+
# redis:
|
| 27 |
+
# image: redis:7-alpine
|
| 28 |
+
# ports:
|
| 29 |
+
# - "6379:6379"
|
| 30 |
+
# restart: unless-stopped
|
| 31 |
+
|
| 32 |
+
# Optional: Add PostgreSQL for persistence
|
| 33 |
+
# postgres:
|
| 34 |
+
# image: postgres:15-alpine
|
| 35 |
+
# environment:
|
| 36 |
+
# POSTGRES_DB: quantumfieldkit
|
| 37 |
+
# POSTGRES_USER: quantum
|
| 38 |
+
# POSTGRES_PASSWORD: quantum_password
|
| 39 |
+
# ports:
|
| 40 |
+
# - "5432:5432"
|
| 41 |
+
# volumes:
|
| 42 |
+
# - postgres_data:/var/lib/postgresql/data
|
| 43 |
+
# restart: unless-stopped
|
| 44 |
+
|
| 45 |
+
# volumes:
|
| 46 |
+
# postgres_data:
|
fly.toml
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
app = "quantumfieldkit"
|
| 2 |
+
primary_region = "arn"
|
| 3 |
+
kill_signal = "SIGINT"
|
| 4 |
+
kill_timeout = "10s"
|
| 5 |
+
|
| 6 |
+
[build]
|
| 7 |
+
dockerfile = "Dockerfile"
|
| 8 |
+
|
| 9 |
+
[env]
|
| 10 |
+
PORT = "8080"
|
| 11 |
+
FLASK_ENV = "production"
|
| 12 |
+
PYTHONPATH = "/app"
|
| 13 |
+
|
| 14 |
+
[http_service]
|
| 15 |
+
internal_port = 8080
|
| 16 |
+
force_https = true
|
| 17 |
+
auto_stop_machines = true
|
| 18 |
+
auto_start_machines = true
|
| 19 |
+
min_machines_running = 1
|
| 20 |
+
max_machines_running = 2
|
| 21 |
+
processes = ["app"]
|
| 22 |
+
|
| 23 |
+
[http_service.concurrency]
|
| 24 |
+
type = "connections"
|
| 25 |
+
hard_limit = 1000
|
| 26 |
+
soft_limit = 500
|
| 27 |
+
|
| 28 |
+
# Health check configuration
|
| 29 |
+
[[http_service.checks]]
|
| 30 |
+
grace_period = "10s"
|
| 31 |
+
interval = "30s"
|
| 32 |
+
method = "GET"
|
| 33 |
+
path = "/api/plugins"
|
| 34 |
+
timeout = "5s"
|
| 35 |
+
headers = {}
|
| 36 |
+
|
| 37 |
+
[[vm]]
|
| 38 |
+
cpu_kind = "shared"
|
| 39 |
+
cpus = 1
|
| 40 |
+
memory_mb = 1024
|
| 41 |
+
|
| 42 |
+
# Optional: Add secrets for sensitive configuration
|
| 43 |
+
# [secrets]
|
| 44 |
+
# SECRET_KEY = "your-secret-key"
|
frontend/package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "quantum-field-kit-frontend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"dependencies": {
|
| 6 |
+
"@fontsource/inter": "^5.2.6",
|
| 7 |
+
"@fortawesome/fontawesome-free": "^6.7.2",
|
| 8 |
+
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
| 9 |
+
"@fortawesome/react-fontawesome": "^0.2.2",
|
| 10 |
+
"bootstrap": "^5.3.5",
|
| 11 |
+
"daisyui": "^5.0.54",
|
| 12 |
+
"framer-motion": "^12.23.12",
|
| 13 |
+
"lucide-react": "^0.542.0",
|
| 14 |
+
"react": "^18.2.0",
|
| 15 |
+
"react-bootstrap": "^2.10.9",
|
| 16 |
+
"react-dom": "^18.2.0",
|
| 17 |
+
"react-ga4": "^2.1.0",
|
| 18 |
+
"react-intersection-observer": "^9.16.0",
|
| 19 |
+
"react-router-dom": "^6.14.2",
|
| 20 |
+
"react-scripts": "5.0.1",
|
| 21 |
+
"socket.io-client": "^4.7.2"
|
| 22 |
+
},
|
| 23 |
+
"scripts": {
|
| 24 |
+
"start": "react-scripts start",
|
| 25 |
+
"build": "react-scripts build",
|
| 26 |
+
"test": "react-scripts test",
|
| 27 |
+
"eject": "react-scripts eject"
|
| 28 |
+
},
|
| 29 |
+
"proxy": "http://localhost:5000",
|
| 30 |
+
"eslintConfig": {
|
| 31 |
+
"extends": [
|
| 32 |
+
"react-app",
|
| 33 |
+
"react-app/jest"
|
| 34 |
+
]
|
| 35 |
+
},
|
| 36 |
+
"browserslist": {
|
| 37 |
+
"production": [
|
| 38 |
+
">0.2%",
|
| 39 |
+
"not dead",
|
| 40 |
+
"not op_mini all"
|
| 41 |
+
],
|
| 42 |
+
"development": [
|
| 43 |
+
"last 1 chrome version",
|
| 44 |
+
"last 1 firefox version",
|
| 45 |
+
"last 1 safari version"
|
| 46 |
+
]
|
| 47 |
+
},
|
| 48 |
+
"devDependencies": {
|
| 49 |
+
"autoprefixer": "^10.4.21",
|
| 50 |
+
"postcss": "^8.4.38",
|
| 51 |
+
"tailwindcss": "^3.4.14"
|
| 52 |
+
}
|
| 53 |
+
}
|
frontend/postcss.config.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = {
|
| 2 |
+
plugins: {
|
| 3 |
+
tailwindcss: {},
|
| 4 |
+
autoprefixer: {},
|
| 5 |
+
},
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
|
frontend/public/data/glossary_terms.json
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"term": "Qubit",
|
| 4 |
+
"definition": "The fundamental unit of quantum information, analogous to a classical bit but with the ability to exist in a superposition of states. Unlike classical bits that are either 0 or 1, qubits can be in a probabilistic combination of both states simultaneously.",
|
| 5 |
+
"category": "Fundamentals",
|
| 6 |
+
"tags": ["basic", "information", "state"]
|
| 7 |
+
},
|
| 8 |
+
{
|
| 9 |
+
"term": "Superposition",
|
| 10 |
+
"definition": "A quantum mechanical principle where a quantum system can exist in multiple states simultaneously until measured. This allows quantum computers to process multiple possibilities in parallel, providing exponential computational advantages for certain problems.",
|
| 11 |
+
"category": "Fundamentals",
|
| 12 |
+
"tags": ["basic", "state", "parallel"]
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"term": "Entanglement",
|
| 16 |
+
"definition": "A quantum mechanical phenomenon where particles become correlated to predictably interact with each other regardless of distance. When qubits are entangled, measuring one instantly affects the state of its partner, enabling quantum communication and computation protocols.",
|
| 17 |
+
"category": "Fundamentals",
|
| 18 |
+
"tags": ["correlation", "communication", "non-locality"]
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"term": "Quantum Gate",
|
| 22 |
+
"definition": "A basic quantum circuit operating on a small number of qubits, analogous to classical logic gates. Common gates include Pauli-X (bit flip), Pauli-Z (phase flip), Hadamard (superposition creator), and CNOT (controlled operations).",
|
| 23 |
+
"category": "Gates & Circuits",
|
| 24 |
+
"tags": ["circuit", "operation", "unitary"]
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"term": "Measurement",
|
| 28 |
+
"definition": "The process of observing a quantum system, which causes it to collapse from a superposition of states into a definite state. This is probabilistic and irreversible, fundamentally changing the quantum system being observed.",
|
| 29 |
+
"category": "Fundamentals",
|
| 30 |
+
"tags": ["observation", "collapse", "probabilistic"]
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"term": "Quantum Circuit",
|
| 34 |
+
"definition": "A sequence of quantum gates applied to a set of qubits to perform a quantum computation. Circuits are read from left to right and represent the time evolution of the quantum system through unitary operations.",
|
| 35 |
+
"category": "Gates & Circuits",
|
| 36 |
+
"tags": ["computation", "sequence", "unitary"]
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"term": "Quantum Algorithm",
|
| 40 |
+
"definition": "An algorithm that runs on a quantum computer, taking advantage of quantum mechanical phenomena like superposition and entanglement. Famous examples include Shor's algorithm for factoring and Grover's algorithm for search.",
|
| 41 |
+
"category": "Algorithms",
|
| 42 |
+
"tags": ["computation", "advantage", "protocol"]
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"term": "Quantum Error Correction",
|
| 46 |
+
"definition": "Techniques used to protect quantum information from errors due to decoherence and other quantum noise. These methods use redundancy and syndrome detection to identify and correct errors without directly measuring the quantum state.",
|
| 47 |
+
"category": "Error Correction",
|
| 48 |
+
"tags": ["protection", "noise", "redundancy"]
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"term": "Decoherence",
|
| 52 |
+
"definition": "The loss of quantum coherence due to interaction with the environment, leading to the collapse of quantum states. This is the primary challenge in building practical quantum computers and limits the time available for quantum computations.",
|
| 53 |
+
"category": "Noise & Errors",
|
| 54 |
+
"tags": ["environment", "noise", "decay"]
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"term": "Quantum Teleportation",
|
| 58 |
+
"definition": "A process by which quantum information can be transmitted from one location to another, using a classical communication channel and quantum entanglement. The original quantum state is destroyed in the process, making it true teleportation rather than copying.",
|
| 59 |
+
"category": "Communication",
|
| 60 |
+
"tags": ["transmission", "entanglement", "protocol"]
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"term": "Hadamard Gate",
|
| 64 |
+
"definition": "A fundamental quantum gate that creates superposition by rotating a qubit from the computational basis to the diagonal basis. It maps |0> to (|0> + |1>)/sqrt(2) and |1> to (|0> - |1>)/sqrt(2).",
|
| 65 |
+
"category": "Gates & Circuits",
|
| 66 |
+
"tags": ["superposition", "rotation", "basis"]
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"term": "CNOT Gate",
|
| 70 |
+
"definition": "A two-qubit controlled gate that flips the target qubit if and only if the control qubit is in state |1>. It's essential for creating entanglement and implementing quantum algorithms.",
|
| 71 |
+
"category": "Gates & Circuits",
|
| 72 |
+
"tags": ["controlled", "entanglement", "two-qubit"]
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"term": "Bloch Sphere",
|
| 76 |
+
"definition": "A geometric representation of qubit states as points on a unit sphere. The north and south poles represent |0> and |1> states, while points on the equator represent equal superposition states with different phases.",
|
| 77 |
+
"category": "Visualization",
|
| 78 |
+
"tags": ["geometry", "representation", "visualization"]
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"term": "Quantum Fourier Transform",
|
| 82 |
+
"definition": "The quantum analog of the discrete Fourier transform, efficiently implementable on quantum computers. It's a key component of Shor's algorithm and other quantum algorithms for period finding.",
|
| 83 |
+
"category": "Algorithms",
|
| 84 |
+
"tags": ["fourier", "period-finding", "efficient"]
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"term": "Grover's Algorithm",
|
| 88 |
+
"definition": "A quantum search algorithm that finds a marked item in an unsorted database with quadratic speedup over classical algorithms. It requires O(sqrt(N)) operations compared to O(N) classically.",
|
| 89 |
+
"category": "Algorithms",
|
| 90 |
+
"tags": ["search", "speedup", "amplitude"]
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"term": "Shor's Algorithm",
|
| 94 |
+
"definition": "A quantum algorithm for efficiently factoring large integers, which threatens current RSA cryptography. It combines period finding using the quantum Fourier transform with classical post-processing.",
|
| 95 |
+
"category": "Algorithms",
|
| 96 |
+
"tags": ["factoring", "cryptography", "period-finding"]
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"term": "Variational Quantum Eigensolver",
|
| 100 |
+
"definition": "A hybrid quantum-classical algorithm for finding ground states of quantum systems. It uses a parameterized quantum circuit optimized by classical methods, making it suitable for near-term quantum devices.",
|
| 101 |
+
"category": "Algorithms",
|
| 102 |
+
"tags": ["hybrid", "optimization", "ground-state"]
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"term": "Quantum Approximate Optimization Algorithm",
|
| 106 |
+
"definition": "A variational quantum algorithm designed to solve combinatorial optimization problems. QAOA alternates between problem-specific and mixing Hamiltonians to explore the solution space.",
|
| 107 |
+
"category": "Algorithms",
|
| 108 |
+
"tags": ["optimization", "variational", "combinatorial"]
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"term": "Bell State",
|
| 112 |
+
"definition": "One of four maximally entangled two-qubit states that form the Bell basis. These states exhibit perfect correlation and are fundamental to quantum communication protocols like quantum teleportation.",
|
| 113 |
+
"category": "Fundamentals",
|
| 114 |
+
"tags": ["entanglement", "correlation", "basis"]
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"term": "Quantum Supremacy",
|
| 118 |
+
"definition": "The point at which a quantum computer can perform a calculation that is practically impossible for classical computers. Also called quantum advantage, it represents a computational milestone rather than practical utility.",
|
| 119 |
+
"category": "Milestones",
|
| 120 |
+
"tags": ["advantage", "milestone", "computational"]
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"term": "NISQ",
|
| 124 |
+
"definition": "Noisy Intermediate-Scale Quantum devices - current quantum computers with 50-1000 qubits that have significant noise but may still provide quantum advantages for specific problems.",
|
| 125 |
+
"category": "Hardware",
|
| 126 |
+
"tags": ["current", "noisy", "intermediate"]
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"term": "Quantum Volume",
|
| 130 |
+
"definition": "A metric that measures the capability of a quantum computer, considering both the number of qubits and the quality of operations. It provides a more comprehensive assessment than qubit count alone.",
|
| 131 |
+
"category": "Metrics",
|
| 132 |
+
"tags": ["capability", "quality", "assessment"]
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"term": "Adiabatic Quantum Computing",
|
| 136 |
+
"definition": "A model of quantum computation based on the adiabatic theorem, where the system evolves slowly from an easy initial Hamiltonian to a problem-encoding final Hamiltonian.",
|
| 137 |
+
"category": "Models",
|
| 138 |
+
"tags": ["adiabatic", "evolution", "hamiltonian"]
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"term": "Quantum Annealing",
|
| 142 |
+
"definition": "A metaheuristic for finding the global minimum of a given objective function using quantum fluctuations. It's implemented in systems like D-Wave quantum computers for optimization problems.",
|
| 143 |
+
"category": "Models",
|
| 144 |
+
"tags": ["optimization", "annealing", "metaheuristic"]
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"term": "Topological Quantum Computing",
|
| 148 |
+
"definition": "A theoretical approach to quantum computing using anyons and topological properties to create naturally error-resistant qubits. Microsoft's approach with Majorana fermions is an example.",
|
| 149 |
+
"category": "Models",
|
| 150 |
+
"tags": ["topological", "anyons", "error-resistant"]
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"term": "Quantum Key Distribution",
|
| 154 |
+
"definition": "A secure communication method that uses quantum mechanics to detect eavesdropping. The BB84 protocol is the most famous implementation, providing information-theoretic security.",
|
| 155 |
+
"category": "Communication",
|
| 156 |
+
"tags": ["security", "cryptography", "eavesdropping"]
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"term": "No-Cloning Theorem",
|
| 160 |
+
"definition": "A fundamental principle stating that arbitrary quantum states cannot be perfectly copied. This theorem is crucial for quantum cryptography security and explains why quantum teleportation destroys the original state.",
|
| 161 |
+
"category": "Principles",
|
| 162 |
+
"tags": ["fundamental", "copying", "security"]
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"term": "Quantum Phase Estimation",
|
| 166 |
+
"definition": "An algorithm for estimating the eigenvalues (phases) of a unitary operator. It's a subroutine in many quantum algorithms including Shor's algorithm and is essential for quantum simulation.",
|
| 167 |
+
"category": "Algorithms",
|
| 168 |
+
"tags": ["eigenvalue", "estimation", "subroutine"]
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"term": "Quantum Error Syndrome",
|
| 172 |
+
"definition": "Information extracted from a quantum error correction code that indicates what type of error occurred without revealing the actual quantum state. Syndromes enable error correction without measurement.",
|
| 173 |
+
"category": "Error Correction",
|
| 174 |
+
"tags": ["syndrome", "detection", "correction"]
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
"term": "Surface Code",
|
| 178 |
+
"definition": "A topological quantum error correction code that arranges qubits on a 2D lattice. It has a high threshold for error correction and is considered one of the most promising codes for fault-tolerant quantum computing.",
|
| 179 |
+
"category": "Error Correction",
|
| 180 |
+
"tags": ["topological", "threshold", "fault-tolerant"]
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"term": "Quantum Coherence Time",
|
| 184 |
+
"definition": "The time scale over which a quantum system maintains its quantum properties before decoherence destroys the quantum information. Longer coherence times allow for more complex quantum computations.",
|
| 185 |
+
"category": "Noise & Errors",
|
| 186 |
+
"tags": ["time", "coherence", "decoherence"]
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"term": "Fidelity",
|
| 190 |
+
"definition": "A measure of how close two quantum states are to each other, ranging from 0 (orthogonal states) to 1 (identical states). High fidelity operations are crucial for reliable quantum computing.",
|
| 191 |
+
"category": "Metrics",
|
| 192 |
+
"tags": ["similarity", "quality", "measure"]
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"term": "Quantum Simulator",
|
| 196 |
+
"definition": "A controllable quantum system used to study another quantum system that is difficult to investigate directly. Both analog and digital quantum simulators are used to understand complex quantum phenomena.",
|
| 197 |
+
"category": "Applications",
|
| 198 |
+
"tags": ["simulation", "modeling", "investigation"]
|
| 199 |
+
},
|
| 200 |
+
{
|
| 201 |
+
"term": "Variational Quantum Algorithm",
|
| 202 |
+
"definition": "A class of hybrid quantum-classical algorithms that use parameterized quantum circuits optimized by classical computers. These algorithms are designed for near-term quantum devices with limited coherence.",
|
| 203 |
+
"category": "Algorithms",
|
| 204 |
+
"tags": ["hybrid", "parameterized", "near-term"]
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"term": "Quantum Machine Learning",
|
| 208 |
+
"definition": "The integration of quantum computing with machine learning, potentially offering advantages in training speed, feature mapping, or handling quantum data. Still largely theoretical with ongoing research.",
|
| 209 |
+
"category": "Applications",
|
| 210 |
+
"tags": ["machine-learning", "integration", "research"]
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"term": "Quantum Sensing",
|
| 214 |
+
"definition": "The use of quantum systems to measure physical quantities with enhanced precision beyond classical limits. Applications include atomic clocks, magnetometry, and gravitational wave detection.",
|
| 215 |
+
"category": "Applications",
|
| 216 |
+
"tags": ["sensing", "precision", "measurement"]
|
| 217 |
+
}
|
| 218 |
+
]
|
frontend/public/index.html
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
|
| 6 |
+
<meta name="theme-color" content="#000000" />
|
| 7 |
+
<meta name="description" content="GXS QuantumNexus - A high-fidelity quantum computing simulation platform" />
|
| 8 |
+
<title>GXS QuantumNexus</title>
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<meta name="color-scheme" content="light dark" />
|
| 11 |
+
<script>
|
| 12 |
+
(function() {
|
| 13 |
+
try {
|
| 14 |
+
var stored = localStorage.getItem('theme');
|
| 15 |
+
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
| 16 |
+
var theme = stored || (prefersDark ? 'dark' : 'quantumlight');
|
| 17 |
+
var html = document.documentElement;
|
| 18 |
+
var head = document.querySelector('head');
|
| 19 |
+
html.setAttribute('data-theme', theme);
|
| 20 |
+
if (head) head.setAttribute('data-theme', theme);
|
| 21 |
+
if (theme === 'dark') html.classList.add('dark');
|
| 22 |
+
else html.classList.remove('dark');
|
| 23 |
+
} catch (e) { /* no-op */ }
|
| 24 |
+
})();
|
| 25 |
+
</script>
|
| 26 |
+
|
| 27 |
+
<!-- Google Analytics -->
|
| 28 |
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-SZH9MSH6K5"></script>
|
| 29 |
+
<script>
|
| 30 |
+
window.dataLayer = window.dataLayer || [];
|
| 31 |
+
function gtag(){dataLayer.push(arguments);}
|
| 32 |
+
gtag('js', new Date());
|
| 33 |
+
gtag('config', 'G-SZH9MSH6K5');
|
| 34 |
+
</script>
|
| 35 |
+
</head>
|
| 36 |
+
<body>
|
| 37 |
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
| 38 |
+
<div id="root"></div>
|
| 39 |
+
</body>
|
| 40 |
+
</html>
|
frontend/src/App.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect } from 'react';
|
| 2 |
+
import { Routes, Route, useLocation } from 'react-router-dom';
|
| 3 |
+
import Home from './pages/Home';
|
| 4 |
+
import Plugin from './pages/Plugin';
|
| 5 |
+
import EnhancedPlugin from './pages/EnhancedPlugin';
|
| 6 |
+
import Glossary from './pages/Glossary';
|
| 7 |
+
import Category from './pages/Category';
|
| 8 |
+
import CircuitDesigner from './pages/CircuitDesigner';
|
| 9 |
+
import Error from './pages/Error';
|
| 10 |
+
import Navbar from './components/Navbar';
|
| 11 |
+
import Footer from './components/Footer';
|
| 12 |
+
import FloatingActionButton from './components/FloatingActionButton';
|
| 13 |
+
import ErrorBoundary from './components/ErrorBoundary';
|
| 14 |
+
import analytics from './services/analytics';
|
| 15 |
+
|
| 16 |
+
function App() {
|
| 17 |
+
const location = useLocation();
|
| 18 |
+
|
| 19 |
+
// Initialize Google Analytics
|
| 20 |
+
useEffect(() => {
|
| 21 |
+
analytics.initialize();
|
| 22 |
+
}, []);
|
| 23 |
+
|
| 24 |
+
// Track page views on route changes
|
| 25 |
+
useEffect(() => {
|
| 26 |
+
const getPageTitle = (pathname) => {
|
| 27 |
+
const routes = {
|
| 28 |
+
'/': 'Home - GXS QuantumNexus',
|
| 29 |
+
'/glossary': 'Glossary - GXS QuantumNexus',
|
| 30 |
+
'/circuit-designer': 'Circuit Designer - GXS QuantumNexus'
|
| 31 |
+
};
|
| 32 |
+
|
| 33 |
+
// Handle dynamic routes
|
| 34 |
+
if (pathname.startsWith('/plugin/')) {
|
| 35 |
+
const pluginKey = pathname.split('/')[2];
|
| 36 |
+
return `${pluginKey} Plugin - GXS QuantumNexus`;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
if (pathname.startsWith('/legacy-plugin/')) {
|
| 40 |
+
const pluginKey = pathname.split('/')[2];
|
| 41 |
+
return `${pluginKey} Legacy Plugin - GXS QuantumNexus`;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
if (pathname.startsWith('/category/')) {
|
| 45 |
+
const category = pathname.split('/')[2];
|
| 46 |
+
return `${category} Category - GXS QuantumNexus`;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
return routes[pathname] || 'GXS QuantumNexus';
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
const title = getPageTitle(location.pathname);
|
| 53 |
+
analytics.trackPageView(location.pathname, title);
|
| 54 |
+
}, [location]);
|
| 55 |
+
|
| 56 |
+
return (
|
| 57 |
+
<ErrorBoundary>
|
| 58 |
+
<div className="App app-shell min-h-screen bg-base-100 text-base-content">
|
| 59 |
+
<Navbar />
|
| 60 |
+
<main>
|
| 61 |
+
<Routes>
|
| 62 |
+
<Route path="/" element={<Home />} />
|
| 63 |
+
<Route path="/plugin/:pluginKey" element={<EnhancedPlugin />} />
|
| 64 |
+
<Route path="/legacy-plugin/:pluginKey" element={<Plugin />} />
|
| 65 |
+
<Route path="/glossary" element={<Glossary />} />
|
| 66 |
+
<Route path="/category/:category" element={<Category />} />
|
| 67 |
+
<Route path="/circuit-designer" element={<CircuitDesigner />} />
|
| 68 |
+
<Route path="*" element={<Error />} />
|
| 69 |
+
</Routes>
|
| 70 |
+
</main>
|
| 71 |
+
<Footer />
|
| 72 |
+
<FloatingActionButton />
|
| 73 |
+
</div>
|
| 74 |
+
</ErrorBoundary>
|
| 75 |
+
);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
export default App;
|
frontend/src/components/AccessibilityProvider.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { createContext, useContext, useState, useEffect } from 'react';
|
| 2 |
+
import PropTypes from 'prop-types';
|
| 3 |
+
|
| 4 |
+
const AccessibilityContext = createContext();
|
| 5 |
+
|
| 6 |
+
export const useAccessibility = () => {
|
| 7 |
+
const context = useContext(AccessibilityContext);
|
| 8 |
+
if (!context) {
|
| 9 |
+
throw new Error('useAccessibility must be used within AccessibilityProvider');
|
| 10 |
+
}
|
| 11 |
+
return context;
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
const AccessibilityProvider = ({ children }) => {
|
| 15 |
+
const [reducedMotion, setReducedMotion] = useState(false);
|
| 16 |
+
const [highContrast, setHighContrast] = useState(false);
|
| 17 |
+
const [fontSize, setFontSize] = useState('medium');
|
| 18 |
+
const [announcements, setAnnouncements] = useState([]);
|
| 19 |
+
|
| 20 |
+
// Detect user preferences
|
| 21 |
+
useEffect(() => {
|
| 22 |
+
// Check for reduced motion preference
|
| 23 |
+
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
| 24 |
+
setReducedMotion(mediaQuery.matches);
|
| 25 |
+
|
| 26 |
+
const handleChange = (e) => setReducedMotion(e.matches);
|
| 27 |
+
mediaQuery.addEventListener('change', handleChange);
|
| 28 |
+
|
| 29 |
+
return () => mediaQuery.removeEventListener('change', handleChange);
|
| 30 |
+
}, []);
|
| 31 |
+
|
| 32 |
+
useEffect(() => {
|
| 33 |
+
// Check for high contrast preference
|
| 34 |
+
const mediaQuery = window.matchMedia('(prefers-contrast: high)');
|
| 35 |
+
setHighContrast(mediaQuery.matches);
|
| 36 |
+
|
| 37 |
+
const handleChange = (e) => setHighContrast(e.matches);
|
| 38 |
+
mediaQuery.addEventListener('change', handleChange);
|
| 39 |
+
|
| 40 |
+
return () => mediaQuery.removeEventListener('change', handleChange);
|
| 41 |
+
}, []);
|
| 42 |
+
|
| 43 |
+
// Announce to screen readers
|
| 44 |
+
const announce = (message, priority = 'polite') => {
|
| 45 |
+
const id = Date.now();
|
| 46 |
+
const announcement = { id, message, priority };
|
| 47 |
+
|
| 48 |
+
setAnnouncements(prev => [...prev, announcement]);
|
| 49 |
+
|
| 50 |
+
// Remove announcement after it's been read
|
| 51 |
+
setTimeout(() => {
|
| 52 |
+
setAnnouncements(prev => prev.filter(a => a.id !== id));
|
| 53 |
+
}, 1000);
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
// Skip to main content
|
| 57 |
+
const skipToMain = () => {
|
| 58 |
+
const main = document.querySelector('main');
|
| 59 |
+
if (main) {
|
| 60 |
+
main.focus();
|
| 61 |
+
main.scrollIntoView();
|
| 62 |
+
}
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
// Focus management
|
| 66 |
+
const focusElement = (selector) => {
|
| 67 |
+
const element = document.querySelector(selector);
|
| 68 |
+
if (element) {
|
| 69 |
+
element.focus();
|
| 70 |
+
return true;
|
| 71 |
+
}
|
| 72 |
+
return false;
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
const value = {
|
| 76 |
+
reducedMotion,
|
| 77 |
+
highContrast,
|
| 78 |
+
fontSize,
|
| 79 |
+
setFontSize,
|
| 80 |
+
announce,
|
| 81 |
+
skipToMain,
|
| 82 |
+
focusElement,
|
| 83 |
+
announcements,
|
| 84 |
+
};
|
| 85 |
+
|
| 86 |
+
return (
|
| 87 |
+
<AccessibilityContext.Provider value={value}>
|
| 88 |
+
{children}
|
| 89 |
+
|
| 90 |
+
{/* Screen reader announcements */}
|
| 91 |
+
<div className="sr-only" aria-live="polite" aria-atomic="true">
|
| 92 |
+
{announcements
|
| 93 |
+
.filter(a => a.priority === 'polite')
|
| 94 |
+
.map(a => (
|
| 95 |
+
<div key={a.id}>{a.message}</div>
|
| 96 |
+
))}
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
<div className="sr-only" aria-live="assertive" aria-atomic="true">
|
| 100 |
+
{announcements
|
| 101 |
+
.filter(a => a.priority === 'assertive')
|
| 102 |
+
.map(a => (
|
| 103 |
+
<div key={a.id}>{a.message}</div>
|
| 104 |
+
))}
|
| 105 |
+
</div>
|
| 106 |
+
|
| 107 |
+
{/* Skip to main content link */}
|
| 108 |
+
<button
|
| 109 |
+
onClick={skipToMain}
|
| 110 |
+
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 bg-primary-500 text-white px-4 py-2 rounded-lg z-50 focus:z-[9999]"
|
| 111 |
+
>
|
| 112 |
+
Skip to main content
|
| 113 |
+
</button>
|
| 114 |
+
</AccessibilityContext.Provider>
|
| 115 |
+
);
|
| 116 |
+
};
|
| 117 |
+
|
| 118 |
+
AccessibilityProvider.propTypes = {
|
| 119 |
+
children: PropTypes.node.isRequired,
|
| 120 |
+
};
|
| 121 |
+
|
| 122 |
+
export default AccessibilityProvider;
|
frontend/src/components/ErrorBoundary.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { motion } from 'framer-motion';
|
| 3 |
+
import { AlertTriangle, RefreshCw, Home } from 'lucide-react';
|
| 4 |
+
import Button from '../design-system/components/Button';
|
| 5 |
+
import Card from '../design-system/components/Card';
|
| 6 |
+
|
| 7 |
+
class ErrorBoundary extends React.Component {
|
| 8 |
+
constructor(props) {
|
| 9 |
+
super(props);
|
| 10 |
+
this.state = {
|
| 11 |
+
hasError: false,
|
| 12 |
+
error: null,
|
| 13 |
+
errorInfo: null,
|
| 14 |
+
retryCount: 0
|
| 15 |
+
};
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
static getDerivedStateFromError(error) {
|
| 19 |
+
return { hasError: true };
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
componentDidCatch(error, errorInfo) {
|
| 23 |
+
this.setState({
|
| 24 |
+
error,
|
| 25 |
+
errorInfo,
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
// Log error to monitoring service in production
|
| 29 |
+
if (process.env.NODE_ENV === 'production') {
|
| 30 |
+
console.error('Error caught by boundary:', error, errorInfo);
|
| 31 |
+
// Here you would typically send to error monitoring service
|
| 32 |
+
// e.g., Sentry.captureException(error, { extra: errorInfo });
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
handleRetry = () => {
|
| 37 |
+
this.setState(prevState => ({
|
| 38 |
+
hasError: false,
|
| 39 |
+
error: null,
|
| 40 |
+
errorInfo: null,
|
| 41 |
+
retryCount: prevState.retryCount + 1
|
| 42 |
+
}));
|
| 43 |
+
};
|
| 44 |
+
|
| 45 |
+
handleGoHome = () => {
|
| 46 |
+
window.location.href = '/';
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
render() {
|
| 50 |
+
if (this.state.hasError) {
|
| 51 |
+
return (
|
| 52 |
+
<div className="min-h-screen bg-gradient-to-br from-neutral-50 to-neutral-100 flex items-center justify-center p-4">
|
| 53 |
+
<motion.div
|
| 54 |
+
initial={{ opacity: 0, y: 20 }}
|
| 55 |
+
animate={{ opacity: 1, y: 0 }}
|
| 56 |
+
className="w-full max-w-md"
|
| 57 |
+
>
|
| 58 |
+
<Card variant="elevated" padding="lg" className="text-center">
|
| 59 |
+
<motion.div
|
| 60 |
+
initial={{ scale: 0 }}
|
| 61 |
+
animate={{ scale: 1 }}
|
| 62 |
+
transition={{ delay: 0.2, type: "spring" }}
|
| 63 |
+
className="w-16 h-16 bg-error-100 rounded-full flex items-center justify-center mx-auto mb-4"
|
| 64 |
+
>
|
| 65 |
+
<AlertTriangle size={32} className="text-error-500" />
|
| 66 |
+
</motion.div>
|
| 67 |
+
|
| 68 |
+
<Card.Title className="text-xl mb-2">
|
| 69 |
+
Oops! Something went wrong
|
| 70 |
+
</Card.Title>
|
| 71 |
+
|
| 72 |
+
<Card.Description className="mb-6">
|
| 73 |
+
We encountered an unexpected error. This has been logged and our team will investigate.
|
| 74 |
+
</Card.Description>
|
| 75 |
+
|
| 76 |
+
{process.env.NODE_ENV === 'development' && this.state.error && (
|
| 77 |
+
<details className="text-left mb-6 p-4 bg-neutral-50 rounded-lg">
|
| 78 |
+
<summary className="cursor-pointer text-sm font-medium text-neutral-700 mb-2">
|
| 79 |
+
Error Details (Development)
|
| 80 |
+
</summary>
|
| 81 |
+
<pre className="text-xs text-error-600 overflow-auto">
|
| 82 |
+
{this.state.error.toString()}
|
| 83 |
+
{this.state.errorInfo.componentStack}
|
| 84 |
+
</pre>
|
| 85 |
+
</details>
|
| 86 |
+
)}
|
| 87 |
+
|
| 88 |
+
<div className="flex gap-3">
|
| 89 |
+
<Button
|
| 90 |
+
variant="outline"
|
| 91 |
+
onClick={this.handleRetry}
|
| 92 |
+
icon={<RefreshCw size={16} />}
|
| 93 |
+
className="flex-1"
|
| 94 |
+
>
|
| 95 |
+
Try Again
|
| 96 |
+
</Button>
|
| 97 |
+
<Button
|
| 98 |
+
onClick={this.handleGoHome}
|
| 99 |
+
icon={<Home size={16} />}
|
| 100 |
+
className="flex-1"
|
| 101 |
+
>
|
| 102 |
+
Go Home
|
| 103 |
+
</Button>
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
+
{this.state.retryCount > 2 && (
|
| 107 |
+
<motion.p
|
| 108 |
+
initial={{ opacity: 0 }}
|
| 109 |
+
animate={{ opacity: 1 }}
|
| 110 |
+
className="text-sm text-neutral-600 mt-4"
|
| 111 |
+
>
|
| 112 |
+
Still having issues? Try refreshing the page or contact support.
|
| 113 |
+
</motion.p>
|
| 114 |
+
)}
|
| 115 |
+
</Card>
|
| 116 |
+
</motion.div>
|
| 117 |
+
</div>
|
| 118 |
+
);
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
return this.props.children;
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
export default ErrorBoundary;
|
frontend/src/components/FloatingActionButton.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import { Search, Zap, BookOpen, Facebook, Settings } from 'lucide-react';
|
| 4 |
+
|
| 5 |
+
const FloatingActionButton = () => {
|
| 6 |
+
const [isOpen, setIsOpen] = useState(false);
|
| 7 |
+
|
| 8 |
+
const actions = [
|
| 9 |
+
//{ icon: Search, label: 'Search', action: () => console.log('Search') },
|
| 10 |
+
//{ icon: Zap, label: 'Quick Run', action: () => console.log('Quick Run') },
|
| 11 |
+
{ icon: BookOpen, label: 'Docs', action: () => window.open('/glossary', '_blank') },
|
| 12 |
+
{ icon: Facebook, label: 'Facebook', action: () => window.open('https://Facebook.com/ChuyenDoiXanh', '_blank') },
|
| 13 |
+
//{ icon: Settings, label: 'Settings', action: () => console.log('Settings') },
|
| 14 |
+
];
|
| 15 |
+
|
| 16 |
+
return (
|
| 17 |
+
<div className="fixed bottom-6 right-6 z-50">
|
| 18 |
+
<AnimatePresence>
|
| 19 |
+
{isOpen && (
|
| 20 |
+
<motion.div
|
| 21 |
+
initial={{ opacity: 0, scale: 0.8 }}
|
| 22 |
+
animate={{ opacity: 1, scale: 1 }}
|
| 23 |
+
exit={{ opacity: 0, scale: 0.8 }}
|
| 24 |
+
className="absolute bottom-16 right-0 space-y-3"
|
| 25 |
+
>
|
| 26 |
+
{actions.map((action, index) => (
|
| 27 |
+
<motion.button
|
| 28 |
+
key={action.label}
|
| 29 |
+
initial={{ opacity: 0, x: 20 }}
|
| 30 |
+
animate={{
|
| 31 |
+
opacity: 1,
|
| 32 |
+
x: 0,
|
| 33 |
+
transition: { delay: index * 0.1 }
|
| 34 |
+
}}
|
| 35 |
+
exit={{
|
| 36 |
+
opacity: 0,
|
| 37 |
+
x: 20,
|
| 38 |
+
transition: { delay: (actions.length - index) * 0.05 }
|
| 39 |
+
}}
|
| 40 |
+
whileHover={{ scale: 1.1 }}
|
| 41 |
+
whileTap={{ scale: 0.95 }}
|
| 42 |
+
onClick={action.action}
|
| 43 |
+
className="flex items-center gap-3 bg-white/90 backdrop-blur-md rounded-full px-4 py-3 shadow-lg border border-white/20 hover:bg-white/95 transition-all group"
|
| 44 |
+
>
|
| 45 |
+
<action.icon size={20} className="text-primary group-hover:text-primary-focus" />
|
| 46 |
+
<span className="text-sm font-medium text-gray-700 whitespace-nowrap">
|
| 47 |
+
{action.label}
|
| 48 |
+
</span>
|
| 49 |
+
</motion.button>
|
| 50 |
+
))}
|
| 51 |
+
</motion.div>
|
| 52 |
+
)}
|
| 53 |
+
</AnimatePresence>
|
| 54 |
+
|
| 55 |
+
<motion.button
|
| 56 |
+
whileHover={{ scale: 1.1 }}
|
| 57 |
+
whileTap={{ scale: 0.9 }}
|
| 58 |
+
onClick={() => setIsOpen(!isOpen)}
|
| 59 |
+
className="w-14 h-14 bg-gradient-to-r from-primary to-secondary rounded-full shadow-lg flex items-center justify-center text-white hover:shadow-xl transition-all"
|
| 60 |
+
>
|
| 61 |
+
<motion.div
|
| 62 |
+
animate={{ rotate: isOpen ? 45 : 0 }}
|
| 63 |
+
transition={{ type: "spring", stiffness: 200, damping: 10 }}
|
| 64 |
+
>
|
| 65 |
+
<Zap size={24} />
|
| 66 |
+
</motion.div>
|
| 67 |
+
</motion.button>
|
| 68 |
+
</div>
|
| 69 |
+
);
|
| 70 |
+
};
|
| 71 |
+
|
| 72 |
+
export default FloatingActionButton;
|
frontend/src/components/Footer.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Link } from 'react-router-dom';
|
| 3 |
+
|
| 4 |
+
function Footer() {
|
| 5 |
+
return (
|
| 6 |
+
<footer className="border-t border-base-200 mt-12">
|
| 7 |
+
<div className="container mx-auto px-4 py-10">
|
| 8 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
| 9 |
+
<div>
|
| 10 |
+
<h5 className="font-semibold mb-2">GXS QuantumNexus</h5>
|
| 11 |
+
<p className="text-base-content/60">
|
| 12 |
+
A high-fidelity quantum computing simulation platform for exploring quantum protocols,
|
| 13 |
+
algorithms, and concepts.
|
| 14 |
+
</p>
|
| 15 |
+
</div>
|
| 16 |
+
<div>
|
| 17 |
+
<h5 className="font-semibold mb-2">Quick Links</h5>
|
| 18 |
+
<ul className="space-y-2 text-base-content/70">
|
| 19 |
+
<li>
|
| 20 |
+
<Link to="/" className="hover:text-primary transition-colors">
|
| 21 |
+
<i className="fas fa-home mr-2"></i>Home
|
| 22 |
+
</Link>
|
| 23 |
+
</li>
|
| 24 |
+
<li>
|
| 25 |
+
<Link to="/glossary" className="hover:text-primary transition-colors">
|
| 26 |
+
<i className="fas fa-book mr-2"></i>Glossary
|
| 27 |
+
</Link>
|
| 28 |
+
</li>
|
| 29 |
+
<li>
|
| 30 |
+
<a href="https://www.facebook.com/ChuyenDoiXanh" className="hover:text-primary transition-colors" target="_blank" rel="noopener noreferrer">
|
| 31 |
+
<i className="fab fa-Facebook mr-2"></i>Facebook
|
| 32 |
+
</a>
|
| 33 |
+
</li>
|
| 34 |
+
</ul>
|
| 35 |
+
</div>
|
| 36 |
+
<div>
|
| 37 |
+
<ul className="space-y-2 text-base-content/70">
|
| 38 |
+
<li>
|
| 39 |
+
<a href="mailto:contact@p4cng.biz.vn" className="hover:text-primary transition-colors">
|
| 40 |
+
<i className="fas fa-envelope mr-2"></i>Email
|
| 41 |
+
</a>
|
| 42 |
+
</li>
|
| 43 |
+
</ul>
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
<div className="mt-8 flex flex-col md:flex-row items-center justify-between text-sm text-base-content/60">
|
| 47 |
+
<p>© {new Date().getFullYear()} GXS Innovation Hub</p>
|
| 48 |
+
</div>
|
| 49 |
+
</div>
|
| 50 |
+
</footer>
|
| 51 |
+
);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
export default Footer;
|
frontend/src/components/Navbar.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from 'react';
|
| 2 |
+
import { Link, useLocation } from 'react-router-dom';
|
| 3 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 4 |
+
import { Sun, Moon, Menu, X } from 'lucide-react';
|
| 5 |
+
|
| 6 |
+
function Navbar() {
|
| 7 |
+
const [isOpen, setIsOpen] = useState(false);
|
| 8 |
+
const [theme, setTheme] = useState(() => {
|
| 9 |
+
// Check for saved theme in localStorage or default to quantumlight
|
| 10 |
+
return localStorage.getItem('theme') || 'quantumlight';
|
| 11 |
+
});
|
| 12 |
+
const location = useLocation();
|
| 13 |
+
|
| 14 |
+
useEffect(() => {
|
| 15 |
+
const html = document.documentElement;
|
| 16 |
+
const head = document.querySelector('head');
|
| 17 |
+
|
| 18 |
+
// Set data-theme on both html and head for compatibility
|
| 19 |
+
html.setAttribute('data-theme', theme);
|
| 20 |
+
head.setAttribute('data-theme', theme);
|
| 21 |
+
|
| 22 |
+
// Handle Tailwind dark mode
|
| 23 |
+
if (theme === 'quantumdark') {
|
| 24 |
+
html.classList.add('dark');
|
| 25 |
+
} else {
|
| 26 |
+
html.classList.remove('dark');
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Save theme to localStorage
|
| 30 |
+
localStorage.setItem('theme', theme);
|
| 31 |
+
}, [theme]);
|
| 32 |
+
|
| 33 |
+
const toggleNavbar = () => setIsOpen(!isOpen);
|
| 34 |
+
|
| 35 |
+
return (
|
| 36 |
+
<motion.nav
|
| 37 |
+
initial={{ y: -100 }}
|
| 38 |
+
animate={{ y: 0 }}
|
| 39 |
+
transition={{ type: "spring", stiffness: 100, damping: 20 }}
|
| 40 |
+
className="fixed top-0 w-full z-50 bg-base-100/80 backdrop-blur-md border-b border-base-200/50"
|
| 41 |
+
>
|
| 42 |
+
<div className="container mx-auto px-4">
|
| 43 |
+
<div className="flex items-center justify-between h-16">
|
| 44 |
+
<motion.div
|
| 45 |
+
whileHover={{ scale: 1.05 }}
|
| 46 |
+
whileTap={{ scale: 0.95 }}
|
| 47 |
+
>
|
| 48 |
+
<Link className="flex items-center gap-2 text-xl font-bold" to="/">
|
| 49 |
+
<motion.i
|
| 50 |
+
animate={{ rotate: 360 }}
|
| 51 |
+
transition={{ duration: 8, repeat: Infinity, ease: "linear" }}
|
| 52 |
+
className="fas fa-atom text-primary"
|
| 53 |
+
/>
|
| 54 |
+
<span className="bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
| 55 |
+
GXS QuantumNexus
|
| 56 |
+
</span>
|
| 57 |
+
</Link>
|
| 58 |
+
</motion.div>
|
| 59 |
+
|
| 60 |
+
{/* Desktop Menu */}
|
| 61 |
+
<div className="hidden lg:flex items-center space-x-1">
|
| 62 |
+
{[
|
| 63 |
+
{ path: '/', label: 'Home', icon: 'fas fa-home' },
|
| 64 |
+
//{ path: '/circuit-designer', label: 'Circuit Designer', icon: 'fas fa-project-diagram' },
|
| 65 |
+
{ path: '/glossary', label: 'Glossary', icon: 'fas fa-book' },
|
| 66 |
+
].map((item) => (
|
| 67 |
+
<motion.div key={item.path} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
|
| 68 |
+
<Link
|
| 69 |
+
to={item.path}
|
| 70 |
+
className={`px-4 py-2 rounded-lg transition-all flex items-center gap-2 ${
|
| 71 |
+
location.pathname === item.path
|
| 72 |
+
? 'bg-primary text-primary-content'
|
| 73 |
+
: 'hover:bg-base-200'
|
| 74 |
+
}`}
|
| 75 |
+
>
|
| 76 |
+
<i className={`${item.icon} text-sm`}></i>
|
| 77 |
+
{item.label}
|
| 78 |
+
</Link>
|
| 79 |
+
</motion.div>
|
| 80 |
+
))}
|
| 81 |
+
|
| 82 |
+
<motion.a
|
| 83 |
+
whileHover={{ scale: 1.05 }}
|
| 84 |
+
whileTap={{ scale: 0.95 }}
|
| 85 |
+
href="https://www.facebook.com/ChuyenDoiXanh"
|
| 86 |
+
target="_blank"
|
| 87 |
+
rel="noopener noreferrer"
|
| 88 |
+
className="px-4 py-2 rounded-lg hover:bg-base-200 transition-all flex items-center gap-2"
|
| 89 |
+
>
|
| 90 |
+
<i className="fab fa-Facebook text-sm"></i>
|
| 91 |
+
Facebook
|
| 92 |
+
</motion.a>
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
<div className="flex items-center gap-2">
|
| 96 |
+
{/* Theme Toggle */}
|
| 97 |
+
<motion.button
|
| 98 |
+
whileHover={{ scale: 1.1 }}
|
| 99 |
+
whileTap={{ scale: 0.9 }}
|
| 100 |
+
onClick={() => setTheme(theme === 'quantumlight' ? 'quantumdark' : 'quantumlight')}
|
| 101 |
+
className="p-2 rounded-lg hover:bg-base-200 transition-all"
|
| 102 |
+
>
|
| 103 |
+
<AnimatePresence mode="wait">
|
| 104 |
+
{theme === 'quantumlight' ? (
|
| 105 |
+
<motion.div
|
| 106 |
+
key="sun"
|
| 107 |
+
initial={{ rotate: -90, opacity: 0 }}
|
| 108 |
+
animate={{ rotate: 0, opacity: 1 }}
|
| 109 |
+
exit={{ rotate: 90, opacity: 0 }}
|
| 110 |
+
transition={{ duration: 0.2 }}
|
| 111 |
+
>
|
| 112 |
+
<Sun size={20} />
|
| 113 |
+
</motion.div>
|
| 114 |
+
) : (
|
| 115 |
+
<motion.div
|
| 116 |
+
key="moon"
|
| 117 |
+
initial={{ rotate: -90, opacity: 0 }}
|
| 118 |
+
animate={{ rotate: 0, opacity: 1 }}
|
| 119 |
+
exit={{ rotate: 90, opacity: 0 }}
|
| 120 |
+
transition={{ duration: 0.2 }}
|
| 121 |
+
>
|
| 122 |
+
<Moon size={20} />
|
| 123 |
+
</motion.div>
|
| 124 |
+
)}
|
| 125 |
+
</AnimatePresence>
|
| 126 |
+
</motion.button>
|
| 127 |
+
|
| 128 |
+
{/* Mobile Menu Toggle */}
|
| 129 |
+
<motion.button
|
| 130 |
+
whileHover={{ scale: 1.1 }}
|
| 131 |
+
whileTap={{ scale: 0.9 }}
|
| 132 |
+
onClick={toggleNavbar}
|
| 133 |
+
className="lg:hidden p-2 rounded-lg hover:bg-base-200 transition-all"
|
| 134 |
+
>
|
| 135 |
+
<AnimatePresence mode="wait">
|
| 136 |
+
{isOpen ? (
|
| 137 |
+
<motion.div
|
| 138 |
+
key="close"
|
| 139 |
+
initial={{ rotate: -90, opacity: 0 }}
|
| 140 |
+
animate={{ rotate: 0, opacity: 1 }}
|
| 141 |
+
exit={{ rotate: 90, opacity: 0 }}
|
| 142 |
+
transition={{ duration: 0.2 }}
|
| 143 |
+
>
|
| 144 |
+
<X size={20} />
|
| 145 |
+
</motion.div>
|
| 146 |
+
) : (
|
| 147 |
+
<motion.div
|
| 148 |
+
key="menu"
|
| 149 |
+
initial={{ rotate: -90, opacity: 0 }}
|
| 150 |
+
animate={{ rotate: 0, opacity: 1 }}
|
| 151 |
+
exit={{ rotate: 90, opacity: 0 }}
|
| 152 |
+
transition={{ duration: 0.2 }}
|
| 153 |
+
>
|
| 154 |
+
<Menu size={20} />
|
| 155 |
+
</motion.div>
|
| 156 |
+
)}
|
| 157 |
+
</AnimatePresence>
|
| 158 |
+
</motion.button>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
|
| 162 |
+
{/* Mobile Menu */}
|
| 163 |
+
<AnimatePresence>
|
| 164 |
+
{isOpen && (
|
| 165 |
+
<motion.div
|
| 166 |
+
initial={{ opacity: 0, height: 0 }}
|
| 167 |
+
animate={{ opacity: 1, height: 'auto' }}
|
| 168 |
+
exit={{ opacity: 0, height: 0 }}
|
| 169 |
+
transition={{ duration: 0.3 }}
|
| 170 |
+
className="lg:hidden border-t border-base-200/50 mt-2 pt-4 pb-4"
|
| 171 |
+
>
|
| 172 |
+
<div className="space-y-2">
|
| 173 |
+
{[
|
| 174 |
+
{ path: '/', label: 'Home', icon: 'fas fa-home' },
|
| 175 |
+
//{ path: '/circuit-designer', label: 'Circuit Designer', icon: 'fas fa-project-diagram' },
|
| 176 |
+
{ path: '/glossary', label: 'Glossary', icon: 'fas fa-book' },
|
| 177 |
+
].map((item, index) => (
|
| 178 |
+
<motion.div
|
| 179 |
+
key={item.path}
|
| 180 |
+
initial={{ opacity: 0, x: -20 }}
|
| 181 |
+
animate={{ opacity: 1, x: 0 }}
|
| 182 |
+
transition={{ delay: index * 0.1 }}
|
| 183 |
+
>
|
| 184 |
+
<Link
|
| 185 |
+
to={item.path}
|
| 186 |
+
onClick={() => setIsOpen(false)}
|
| 187 |
+
className={`block px-4 py-3 rounded-lg transition-all flex items-center gap-3 ${
|
| 188 |
+
location.pathname === item.path
|
| 189 |
+
? 'bg-primary text-primary-content'
|
| 190 |
+
: 'hover:bg-base-200'
|
| 191 |
+
}`}
|
| 192 |
+
>
|
| 193 |
+
<i className={item.icon}></i>
|
| 194 |
+
{item.label}
|
| 195 |
+
</Link>
|
| 196 |
+
</motion.div>
|
| 197 |
+
))}
|
| 198 |
+
|
| 199 |
+
<motion.a
|
| 200 |
+
initial={{ opacity: 0, x: -20 }}
|
| 201 |
+
animate={{ opacity: 1, x: 0 }}
|
| 202 |
+
transition={{ delay: 0.3 }}
|
| 203 |
+
href="https://Facebook.com/ChuyenDoiXanh"
|
| 204 |
+
target="_blank"
|
| 205 |
+
rel="noopener noreferrer"
|
| 206 |
+
onClick={() => setIsOpen(false)}
|
| 207 |
+
className="block px-4 py-3 rounded-lg hover:bg-base-200 transition-all flex items-center gap-3"
|
| 208 |
+
>
|
| 209 |
+
<i className="fab fa-Facebook"></i>
|
| 210 |
+
Facebook
|
| 211 |
+
</motion.a>
|
| 212 |
+
</div>
|
| 213 |
+
</motion.div>
|
| 214 |
+
)}
|
| 215 |
+
</AnimatePresence>
|
| 216 |
+
</div>
|
| 217 |
+
</motion.nav>
|
| 218 |
+
);
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
export default Navbar;
|
frontend/src/components/QuantumParticles.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect, useRef } from 'react';
|
| 2 |
+
|
| 3 |
+
const QuantumParticles = () => {
|
| 4 |
+
const canvasRef = useRef(null);
|
| 5 |
+
|
| 6 |
+
useEffect(() => {
|
| 7 |
+
const canvas = canvasRef.current;
|
| 8 |
+
if (!canvas) return;
|
| 9 |
+
|
| 10 |
+
const ctx = canvas.getContext('2d');
|
| 11 |
+
const particles = [];
|
| 12 |
+
const particleCount = 50;
|
| 13 |
+
|
| 14 |
+
// Set canvas size
|
| 15 |
+
const resizeCanvas = () => {
|
| 16 |
+
canvas.width = window.innerWidth;
|
| 17 |
+
canvas.height = window.innerHeight;
|
| 18 |
+
};
|
| 19 |
+
resizeCanvas();
|
| 20 |
+
window.addEventListener('resize', resizeCanvas);
|
| 21 |
+
|
| 22 |
+
// Particle class
|
| 23 |
+
class Particle {
|
| 24 |
+
constructor() {
|
| 25 |
+
this.x = Math.random() * canvas.width;
|
| 26 |
+
this.y = Math.random() * canvas.height;
|
| 27 |
+
this.vx = (Math.random() - 0.5) * 0.5;
|
| 28 |
+
this.vy = (Math.random() - 0.5) * 0.5;
|
| 29 |
+
this.radius = Math.random() * 2 + 1;
|
| 30 |
+
this.opacity = Math.random() * 0.5 + 0.2;
|
| 31 |
+
this.hue = Math.random() * 60 + 200; // Blue to purple range
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
update() {
|
| 35 |
+
this.x += this.vx;
|
| 36 |
+
this.y += this.vy;
|
| 37 |
+
|
| 38 |
+
// Wrap around edges
|
| 39 |
+
if (this.x < 0) this.x = canvas.width;
|
| 40 |
+
if (this.x > canvas.width) this.x = 0;
|
| 41 |
+
if (this.y < 0) this.y = canvas.height;
|
| 42 |
+
if (this.y > canvas.height) this.y = 0;
|
| 43 |
+
|
| 44 |
+
// Quantum-like behavior - occasional teleportation
|
| 45 |
+
if (Math.random() < 0.001) {
|
| 46 |
+
this.x = Math.random() * canvas.width;
|
| 47 |
+
this.y = Math.random() * canvas.height;
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
draw() {
|
| 52 |
+
ctx.beginPath();
|
| 53 |
+
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
| 54 |
+
ctx.fillStyle = `hsla(${this.hue}, 70%, 60%, ${this.opacity})`;
|
| 55 |
+
ctx.fill();
|
| 56 |
+
|
| 57 |
+
// Add glow effect
|
| 58 |
+
ctx.shadowBlur = 10;
|
| 59 |
+
ctx.shadowColor = `hsla(${this.hue}, 70%, 60%, 0.8)`;
|
| 60 |
+
ctx.fill();
|
| 61 |
+
ctx.shadowBlur = 0;
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// Initialize particles
|
| 66 |
+
for (let i = 0; i < particleCount; i++) {
|
| 67 |
+
particles.push(new Particle());
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// Animation loop
|
| 71 |
+
const animate = () => {
|
| 72 |
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 73 |
+
|
| 74 |
+
particles.forEach(particle => {
|
| 75 |
+
particle.update();
|
| 76 |
+
particle.draw();
|
| 77 |
+
});
|
| 78 |
+
|
| 79 |
+
// Draw connections between nearby particles
|
| 80 |
+
particles.forEach((particle, i) => {
|
| 81 |
+
particles.slice(i + 1).forEach(otherParticle => {
|
| 82 |
+
const dx = particle.x - otherParticle.x;
|
| 83 |
+
const dy = particle.y - otherParticle.y;
|
| 84 |
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
| 85 |
+
|
| 86 |
+
if (distance < 100) {
|
| 87 |
+
ctx.beginPath();
|
| 88 |
+
ctx.moveTo(particle.x, particle.y);
|
| 89 |
+
ctx.lineTo(otherParticle.x, otherParticle.y);
|
| 90 |
+
ctx.strokeStyle = `hsla(220, 70%, 60%, ${0.1 * (1 - distance / 100)})`;
|
| 91 |
+
ctx.lineWidth = 0.5;
|
| 92 |
+
ctx.stroke();
|
| 93 |
+
}
|
| 94 |
+
});
|
| 95 |
+
});
|
| 96 |
+
|
| 97 |
+
requestAnimationFrame(animate);
|
| 98 |
+
};
|
| 99 |
+
|
| 100 |
+
animate();
|
| 101 |
+
|
| 102 |
+
return () => {
|
| 103 |
+
window.removeEventListener('resize', resizeCanvas);
|
| 104 |
+
};
|
| 105 |
+
}, []);
|
| 106 |
+
|
| 107 |
+
return (
|
| 108 |
+
<canvas
|
| 109 |
+
ref={canvasRef}
|
| 110 |
+
className="absolute inset-0 pointer-events-none"
|
| 111 |
+
style={{ zIndex: 1 }}
|
| 112 |
+
/>
|
| 113 |
+
);
|
| 114 |
+
};
|
| 115 |
+
|
| 116 |
+
export default QuantumParticles;
|
frontend/src/components/SkeletonLoader.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { motion } from 'framer-motion';
|
| 3 |
+
|
| 4 |
+
const SkeletonCard = () => (
|
| 5 |
+
<div className="card bg-base-100 border border-base-200 shadow-sm">
|
| 6 |
+
<div className="card-body">
|
| 7 |
+
<div className="flex items-center mb-3">
|
| 8 |
+
<motion.div
|
| 9 |
+
animate={{ opacity: [0.5, 1, 0.5] }}
|
| 10 |
+
transition={{ duration: 1.5, repeat: Infinity }}
|
| 11 |
+
className="w-10 h-10 rounded-full bg-base-300 mr-3"
|
| 12 |
+
/>
|
| 13 |
+
<motion.div
|
| 14 |
+
animate={{ opacity: [0.5, 1, 0.5] }}
|
| 15 |
+
transition={{ duration: 1.5, repeat: Infinity, delay: 0.2 }}
|
| 16 |
+
className="h-6 bg-base-300 rounded w-32"
|
| 17 |
+
/>
|
| 18 |
+
</div>
|
| 19 |
+
<motion.div
|
| 20 |
+
animate={{ opacity: [0.5, 1, 0.5] }}
|
| 21 |
+
transition={{ duration: 1.5, repeat: Infinity, delay: 0.4 }}
|
| 22 |
+
className="space-y-2 mb-4"
|
| 23 |
+
>
|
| 24 |
+
<div className="h-4 bg-base-300 rounded w-full" />
|
| 25 |
+
<div className="h-4 bg-base-300 rounded w-3/4" />
|
| 26 |
+
</motion.div>
|
| 27 |
+
<motion.div
|
| 28 |
+
animate={{ opacity: [0.5, 1, 0.5] }}
|
| 29 |
+
transition={{ duration: 1.5, repeat: Infinity, delay: 0.6 }}
|
| 30 |
+
className="h-10 bg-base-300 rounded w-32"
|
| 31 |
+
/>
|
| 32 |
+
</div>
|
| 33 |
+
</div>
|
| 34 |
+
);
|
| 35 |
+
|
| 36 |
+
const SkeletonGrid = ({ count = 6 }) => (
|
| 37 |
+
<div className="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
| 38 |
+
{Array.from({ length: count }, (_, i) => (
|
| 39 |
+
<SkeletonCard key={i} />
|
| 40 |
+
))}
|
| 41 |
+
</div>
|
| 42 |
+
);
|
| 43 |
+
|
| 44 |
+
const SkeletonLoader = ({ type = 'grid', count }) => {
|
| 45 |
+
if (type === 'grid') {
|
| 46 |
+
return <SkeletonGrid count={count} />;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
return <SkeletonCard />;
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
export default SkeletonLoader;
|
frontend/src/components/plugin/EnhancedPluginParameterForm.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useCallback, useMemo } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import { RotateCcw, Settings, HelpCircle, Zap } from 'lucide-react';
|
| 4 |
+
import PropTypes from 'prop-types';
|
| 5 |
+
import Button from '../../design-system/components/Button';
|
| 6 |
+
import Input from '../../design-system/components/Input';
|
| 7 |
+
import Card from '../../design-system/components/Card';
|
| 8 |
+
|
| 9 |
+
const EnhancedPluginParameterForm = ({
|
| 10 |
+
parameters,
|
| 11 |
+
initialValues,
|
| 12 |
+
onSubmit,
|
| 13 |
+
loading,
|
| 14 |
+
onParameterChange,
|
| 15 |
+
showAdvanced = false
|
| 16 |
+
}) => {
|
| 17 |
+
const [values, setValues] = useState(initialValues || {});
|
| 18 |
+
const [errors, setErrors] = useState({});
|
| 19 |
+
const [showAdvancedParams, setShowAdvancedParams] = useState(showAdvanced);
|
| 20 |
+
const [focusedParam, setFocusedParam] = useState(null);
|
| 21 |
+
|
| 22 |
+
// Categorize parameters
|
| 23 |
+
const { basicParams, advancedParams } = useMemo(() => {
|
| 24 |
+
const basic = parameters.filter(p => !p.advanced);
|
| 25 |
+
const advanced = parameters.filter(p => p.advanced);
|
| 26 |
+
return { basicParams: basic, advancedParams: advanced };
|
| 27 |
+
}, [parameters]);
|
| 28 |
+
|
| 29 |
+
const validateParameter = useCallback((param, value) => {
|
| 30 |
+
const errors = [];
|
| 31 |
+
|
| 32 |
+
if (param.required && (value === undefined || value === null || value === '')) {
|
| 33 |
+
errors.push('This field is required');
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
if (param.type === 'int' || param.type === 'float') {
|
| 37 |
+
const numValue = parseFloat(value);
|
| 38 |
+
if (isNaN(numValue)) {
|
| 39 |
+
errors.push('Must be a valid number');
|
| 40 |
+
} else {
|
| 41 |
+
if (param.min !== undefined && numValue < param.min) {
|
| 42 |
+
errors.push(`Must be at least ${param.min}`);
|
| 43 |
+
}
|
| 44 |
+
if (param.max !== undefined && numValue > param.max) {
|
| 45 |
+
errors.push(`Must be at most ${param.max}`);
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
if (param.type === 'string' && param.max_length && value.length > param.max_length) {
|
| 51 |
+
errors.push(`Must be at most ${param.max_length} characters`);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
return errors;
|
| 55 |
+
}, []);
|
| 56 |
+
|
| 57 |
+
const handleChange = useCallback((paramName, value) => {
|
| 58 |
+
setValues(prev => {
|
| 59 |
+
const newValues = { ...prev, [paramName]: value };
|
| 60 |
+
|
| 61 |
+
// Validate the parameter
|
| 62 |
+
const param = parameters.find(p => p.name === paramName);
|
| 63 |
+
if (param) {
|
| 64 |
+
const paramErrors = validateParameter(param, value);
|
| 65 |
+
setErrors(prev => ({
|
| 66 |
+
...prev,
|
| 67 |
+
[paramName]: paramErrors.length > 0 ? paramErrors[0] : undefined
|
| 68 |
+
}));
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
onParameterChange?.(newValues);
|
| 72 |
+
return newValues;
|
| 73 |
+
});
|
| 74 |
+
}, [parameters, validateParameter, onParameterChange]);
|
| 75 |
+
|
| 76 |
+
const handleSubmit = useCallback((e) => {
|
| 77 |
+
e.preventDefault();
|
| 78 |
+
|
| 79 |
+
// Validate all parameters
|
| 80 |
+
const newErrors = {};
|
| 81 |
+
let hasErrors = false;
|
| 82 |
+
|
| 83 |
+
parameters.forEach(param => {
|
| 84 |
+
const paramErrors = validateParameter(param, values[param.name]);
|
| 85 |
+
if (paramErrors.length > 0) {
|
| 86 |
+
newErrors[param.name] = paramErrors[0];
|
| 87 |
+
hasErrors = true;
|
| 88 |
+
}
|
| 89 |
+
});
|
| 90 |
+
|
| 91 |
+
setErrors(newErrors);
|
| 92 |
+
|
| 93 |
+
if (!hasErrors) {
|
| 94 |
+
onSubmit(values);
|
| 95 |
+
}
|
| 96 |
+
}, [parameters, values, validateParameter, onSubmit]);
|
| 97 |
+
|
| 98 |
+
const handleReset = useCallback(() => {
|
| 99 |
+
setValues(initialValues || {});
|
| 100 |
+
setErrors({});
|
| 101 |
+
}, [initialValues]);
|
| 102 |
+
|
| 103 |
+
const renderParameterInput = (param) => {
|
| 104 |
+
const value = values[param.name] ?? param.default ?? '';
|
| 105 |
+
const error = errors[param.name];
|
| 106 |
+
const isFocused = focusedParam === param.name;
|
| 107 |
+
|
| 108 |
+
const inputProps = {
|
| 109 |
+
value,
|
| 110 |
+
onChange: (e) => handleChange(param.name, e.target.value),
|
| 111 |
+
onFocus: () => setFocusedParam(param.name),
|
| 112 |
+
onBlur: () => setFocusedParam(null),
|
| 113 |
+
error,
|
| 114 |
+
required: param.required,
|
| 115 |
+
disabled: loading,
|
| 116 |
+
};
|
| 117 |
+
|
| 118 |
+
switch (param.type) {
|
| 119 |
+
case 'int':
|
| 120 |
+
case 'float':
|
| 121 |
+
return (
|
| 122 |
+
<Input
|
| 123 |
+
{...inputProps}
|
| 124 |
+
type="number"
|
| 125 |
+
label={param.description || param.name}
|
| 126 |
+
helperText={`${param.type}${param.min !== undefined ? `, min: ${param.min}` : ''}${param.max !== undefined ? `, max: ${param.max}` : ''}`}
|
| 127 |
+
min={param.min}
|
| 128 |
+
max={param.max}
|
| 129 |
+
step={param.type === 'float' ? 'any' : '1'}
|
| 130 |
+
/>
|
| 131 |
+
);
|
| 132 |
+
|
| 133 |
+
case 'bool':
|
| 134 |
+
return (
|
| 135 |
+
<div className="space-y-2">
|
| 136 |
+
<label className="block text-sm font-medium text-neutral-700">
|
| 137 |
+
{param.description || param.name}
|
| 138 |
+
{param.required && <span className="text-error-500 ml-1">*</span>}
|
| 139 |
+
</label>
|
| 140 |
+
<motion.button
|
| 141 |
+
type="button"
|
| 142 |
+
onClick={() => handleChange(param.name, !value)}
|
| 143 |
+
disabled={loading}
|
| 144 |
+
whileHover={{ scale: 1.02 }}
|
| 145 |
+
whileTap={{ scale: 0.98 }}
|
| 146 |
+
className={`
|
| 147 |
+
relative inline-flex h-6 w-11 items-center rounded-full transition-colors
|
| 148 |
+
${value ? 'bg-primary-500' : 'bg-neutral-200'}
|
| 149 |
+
${loading ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
| 150 |
+
`}
|
| 151 |
+
>
|
| 152 |
+
<motion.span
|
| 153 |
+
layout
|
| 154 |
+
className="inline-block h-4 w-4 transform rounded-full bg-base-100 dark:bg-neutral-200 shadow-sm transition-transform"
|
| 155 |
+
animate={{ x: value ? 24 : 4 }}
|
| 156 |
+
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
| 157 |
+
/>
|
| 158 |
+
</motion.button>
|
| 159 |
+
</div>
|
| 160 |
+
);
|
| 161 |
+
|
| 162 |
+
case 'select':
|
| 163 |
+
return (
|
| 164 |
+
<div className="space-y-2">
|
| 165 |
+
<label className="block text-sm font-medium text-neutral-700">
|
| 166 |
+
{param.description || param.name}
|
| 167 |
+
{param.required && <span className="text-error-500 ml-1">*</span>}
|
| 168 |
+
</label>
|
| 169 |
+
<select
|
| 170 |
+
value={value}
|
| 171 |
+
onChange={(e) => handleChange(param.name, e.target.value)}
|
| 172 |
+
disabled={loading}
|
| 173 |
+
className="w-full rounded-lg border border-neutral-300 px-4 py-2.5 text-base transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500/20 focus:border-primary-500 disabled:bg-neutral-50 disabled:text-neutral-500"
|
| 174 |
+
>
|
| 175 |
+
{param.options?.map((option) => (
|
| 176 |
+
<option key={option} value={option}>
|
| 177 |
+
{option}
|
| 178 |
+
</option>
|
| 179 |
+
))}
|
| 180 |
+
</select>
|
| 181 |
+
</div>
|
| 182 |
+
);
|
| 183 |
+
|
| 184 |
+
default:
|
| 185 |
+
return (
|
| 186 |
+
<Input
|
| 187 |
+
{...inputProps}
|
| 188 |
+
type="text"
|
| 189 |
+
label={param.description || param.name}
|
| 190 |
+
helperText={param.max_length ? `Max ${param.max_length} characters` : undefined}
|
| 191 |
+
maxLength={param.max_length}
|
| 192 |
+
/>
|
| 193 |
+
);
|
| 194 |
+
}
|
| 195 |
+
};
|
| 196 |
+
|
| 197 |
+
return (
|
| 198 |
+
<Card variant="elevated" padding="lg">
|
| 199 |
+
<Card.Header>
|
| 200 |
+
<div className="flex items-center justify-between">
|
| 201 |
+
<Card.Title className="flex items-center gap-2">
|
| 202 |
+
<Settings size={20} className="text-primary-500" />
|
| 203 |
+
Parameters
|
| 204 |
+
</Card.Title>
|
| 205 |
+
{advancedParams.length > 0 && (
|
| 206 |
+
<Button
|
| 207 |
+
variant="ghost"
|
| 208 |
+
size="sm"
|
| 209 |
+
onClick={() => setShowAdvancedParams(!showAdvancedParams)}
|
| 210 |
+
>
|
| 211 |
+
Advanced {showAdvancedParams ? '−' : '+'}
|
| 212 |
+
</Button>
|
| 213 |
+
)}
|
| 214 |
+
</div>
|
| 215 |
+
</Card.Header>
|
| 216 |
+
|
| 217 |
+
<form onSubmit={handleSubmit} className="space-y-6">
|
| 218 |
+
{/* Basic Parameters */}
|
| 219 |
+
<div className="space-y-4">
|
| 220 |
+
{basicParams.map((param) => (
|
| 221 |
+
<motion.div
|
| 222 |
+
key={param.name}
|
| 223 |
+
initial={{ opacity: 0, y: 20 }}
|
| 224 |
+
animate={{ opacity: 1, y: 0 }}
|
| 225 |
+
transition={{ duration: 0.3 }}
|
| 226 |
+
>
|
| 227 |
+
{renderParameterInput(param)}
|
| 228 |
+
</motion.div>
|
| 229 |
+
))}
|
| 230 |
+
</div>
|
| 231 |
+
|
| 232 |
+
{/* Advanced Parameters */}
|
| 233 |
+
<AnimatePresence>
|
| 234 |
+
{showAdvancedParams && advancedParams.length > 0 && (
|
| 235 |
+
<motion.div
|
| 236 |
+
initial={{ opacity: 0, height: 0 }}
|
| 237 |
+
animate={{ opacity: 1, height: 'auto' }}
|
| 238 |
+
exit={{ opacity: 0, height: 0 }}
|
| 239 |
+
transition={{ duration: 0.3 }}
|
| 240 |
+
className="space-y-4 pt-4 border-t border-neutral-200 dark:border-neutral-700"
|
| 241 |
+
>
|
| 242 |
+
<div className="flex items-center gap-2 text-sm text-neutral-600">
|
| 243 |
+
<HelpCircle size={16} />
|
| 244 |
+
Advanced Parameters
|
| 245 |
+
</div>
|
| 246 |
+
{advancedParams.map((param) => (
|
| 247 |
+
<motion.div
|
| 248 |
+
key={param.name}
|
| 249 |
+
initial={{ opacity: 0, x: -20 }}
|
| 250 |
+
animate={{ opacity: 1, x: 0 }}
|
| 251 |
+
transition={{ duration: 0.3 }}
|
| 252 |
+
>
|
| 253 |
+
{renderParameterInput(param)}
|
| 254 |
+
</motion.div>
|
| 255 |
+
))}
|
| 256 |
+
</motion.div>
|
| 257 |
+
)}
|
| 258 |
+
</AnimatePresence>
|
| 259 |
+
|
| 260 |
+
{/* Action Buttons */}
|
| 261 |
+
<div className="flex flex-col sm:flex-row gap-3 pt-4">
|
| 262 |
+
<Button
|
| 263 |
+
type="submit"
|
| 264 |
+
variant="primary"
|
| 265 |
+
loading={loading}
|
| 266 |
+
icon={<Zap size={16} />}
|
| 267 |
+
className="flex-1 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white border-0 shadow-lg min-h-[48px] sm:min-h-[44px]"
|
| 268 |
+
>
|
| 269 |
+
{loading ? 'Running Simulation...' : 'Run Simulation'}
|
| 270 |
+
</Button>
|
| 271 |
+
|
| 272 |
+
<Button
|
| 273 |
+
type="button"
|
| 274 |
+
variant="outline"
|
| 275 |
+
onClick={handleReset}
|
| 276 |
+
disabled={loading}
|
| 277 |
+
icon={<RotateCcw size={16} />}
|
| 278 |
+
className="border-2 border-neutral-400 hover:border-neutral-600 text-neutral-700 dark:text-neutral-300 hover:bg-base-200 dark:hover:bg-neutral-700 min-h-[48px] sm:min-h-[44px] sm:flex-shrink-0"
|
| 279 |
+
>
|
| 280 |
+
Reset
|
| 281 |
+
</Button>
|
| 282 |
+
</div>
|
| 283 |
+
</form>
|
| 284 |
+
</Card>
|
| 285 |
+
);
|
| 286 |
+
};
|
| 287 |
+
|
| 288 |
+
EnhancedPluginParameterForm.propTypes = {
|
| 289 |
+
parameters: PropTypes.array.isRequired,
|
| 290 |
+
initialValues: PropTypes.object,
|
| 291 |
+
onSubmit: PropTypes.func.isRequired,
|
| 292 |
+
loading: PropTypes.bool,
|
| 293 |
+
onParameterChange: PropTypes.func,
|
| 294 |
+
showAdvanced: PropTypes.bool,
|
| 295 |
+
};
|
| 296 |
+
|
| 297 |
+
export default EnhancedPluginParameterForm;
|
frontend/src/components/plugin/EnhancedPluginResultsPanel.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useMemo, useCallback } from 'react';
|
| 2 |
+
import { motion } from 'framer-motion';
|
| 3 |
+
import {
|
| 4 |
+
Activity,
|
| 5 |
+
BarChart3,
|
| 6 |
+
FileText,
|
| 7 |
+
Download,
|
| 8 |
+
Share2,
|
| 9 |
+
Maximize2,
|
| 10 |
+
Copy,
|
| 11 |
+
CheckCircle2
|
| 12 |
+
} from 'lucide-react';
|
| 13 |
+
import PropTypes from 'prop-types';
|
| 14 |
+
import Button from '../../design-system/components/Button';
|
| 15 |
+
import Card from '../../design-system/components/Card';
|
| 16 |
+
import SimpleTabs from './SimpleTabs';
|
| 17 |
+
import QuantumVisualization from './QuantumVisualization';
|
| 18 |
+
|
| 19 |
+
const EnhancedPluginResultsPanel = ({ result, loading, onExport, onShare }) => {
|
| 20 |
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
| 21 |
+
const [copied, setCopied] = useState(false);
|
| 22 |
+
|
| 23 |
+
const handleCopyData = useCallback(async () => {
|
| 24 |
+
if (!result || !result.output) return;
|
| 25 |
+
|
| 26 |
+
try {
|
| 27 |
+
await navigator.clipboard.writeText(JSON.stringify(result.output, null, 2));
|
| 28 |
+
setCopied(true);
|
| 29 |
+
setTimeout(() => setCopied(false), 2000);
|
| 30 |
+
} catch (err) {
|
| 31 |
+
console.error('Failed to copy data:', err);
|
| 32 |
+
}
|
| 33 |
+
}, [result]);
|
| 34 |
+
|
| 35 |
+
const tabs = useMemo(() => {
|
| 36 |
+
if (!result) {
|
| 37 |
+
return [
|
| 38 |
+
{
|
| 39 |
+
id: 'visualization',
|
| 40 |
+
label: 'Visualization',
|
| 41 |
+
icon: <Activity size={16} />,
|
| 42 |
+
available: true,
|
| 43 |
+
content: <QuantumVisualization result={null} />
|
| 44 |
+
}
|
| 45 |
+
];
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
return [
|
| 49 |
+
{
|
| 50 |
+
id: 'visualization',
|
| 51 |
+
label: 'Visualization',
|
| 52 |
+
icon: <Activity size={16} />,
|
| 53 |
+
available: true,
|
| 54 |
+
content: <QuantumVisualization result={result} />
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
id: 'data',
|
| 58 |
+
label: 'Raw Data',
|
| 59 |
+
icon: <BarChart3 size={16} />,
|
| 60 |
+
available: !!(result && result.output),
|
| 61 |
+
content: (
|
| 62 |
+
<motion.div
|
| 63 |
+
initial={{ opacity: 0 }}
|
| 64 |
+
animate={{ opacity: 1 }}
|
| 65 |
+
className="space-y-4"
|
| 66 |
+
>
|
| 67 |
+
<div className="flex items-center justify-between">
|
| 68 |
+
<h4 className="text-sm font-medium text-neutral-700">Raw Output Data</h4>
|
| 69 |
+
<Button
|
| 70 |
+
variant="ghost"
|
| 71 |
+
size="sm"
|
| 72 |
+
onClick={handleCopyData}
|
| 73 |
+
icon={copied ? <CheckCircle2 size={14} /> : <Copy size={14} />}
|
| 74 |
+
>
|
| 75 |
+
{copied ? 'Copied!' : 'Copy JSON'}
|
| 76 |
+
</Button>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
<div className="bg-neutral-50 rounded-lg p-4 max-h-96 overflow-auto">
|
| 80 |
+
<pre className="text-sm text-neutral-800 whitespace-pre-wrap">
|
| 81 |
+
{result && result.output ? JSON.stringify(result.output, null, 2) : 'No data available'}
|
| 82 |
+
</pre>
|
| 83 |
+
</div>
|
| 84 |
+
</motion.div>
|
| 85 |
+
)
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
id: 'log',
|
| 89 |
+
label: 'Process Log',
|
| 90 |
+
icon: <FileText size={16} />,
|
| 91 |
+
available: !!(result && result.log),
|
| 92 |
+
content: (
|
| 93 |
+
<motion.div
|
| 94 |
+
initial={{ opacity: 0 }}
|
| 95 |
+
animate={{ opacity: 1 }}
|
| 96 |
+
className="space-y-4"
|
| 97 |
+
>
|
| 98 |
+
<h4 className="text-sm font-medium text-neutral-700">Process Log</h4>
|
| 99 |
+
|
| 100 |
+
<div className="bg-neutral-900 rounded-lg p-4 max-h-96 overflow-auto">
|
| 101 |
+
<pre className="text-sm text-green-400 font-mono whitespace-pre-wrap">
|
| 102 |
+
{(result && result.log) ? result.log : 'No log data available.'}
|
| 103 |
+
</pre>
|
| 104 |
+
</div>
|
| 105 |
+
</motion.div>
|
| 106 |
+
)
|
| 107 |
+
},
|
| 108 |
+
].filter(tab => tab.available);
|
| 109 |
+
}, [result, copied, handleCopyData]);
|
| 110 |
+
|
| 111 |
+
const handleExport = () => {
|
| 112 |
+
if (!result?.output) return;
|
| 113 |
+
|
| 114 |
+
const dataStr = JSON.stringify(result.output, null, 2);
|
| 115 |
+
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
| 116 |
+
const url = URL.createObjectURL(dataBlob);
|
| 117 |
+
const link = document.createElement('a');
|
| 118 |
+
link.href = url;
|
| 119 |
+
link.download = `quantum-simulation-${Date.now()}.json`;
|
| 120 |
+
document.body.appendChild(link);
|
| 121 |
+
link.click();
|
| 122 |
+
document.body.removeChild(link);
|
| 123 |
+
URL.revokeObjectURL(url);
|
| 124 |
+
|
| 125 |
+
onExport?.(result);
|
| 126 |
+
};
|
| 127 |
+
|
| 128 |
+
if (loading) {
|
| 129 |
+
return (
|
| 130 |
+
<Card variant="elevated" padding="lg">
|
| 131 |
+
<div className="flex flex-col items-center justify-center py-12">
|
| 132 |
+
<motion.div
|
| 133 |
+
animate={{ rotate: 360 }}
|
| 134 |
+
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
| 135 |
+
className="mb-4"
|
| 136 |
+
>
|
| 137 |
+
<Activity size={48} className="text-primary-500" />
|
| 138 |
+
</motion.div>
|
| 139 |
+
<motion.h3
|
| 140 |
+
initial={{ opacity: 0 }}
|
| 141 |
+
animate={{ opacity: 1 }}
|
| 142 |
+
className="text-lg font-semibold text-neutral-900 mb-2"
|
| 143 |
+
>
|
| 144 |
+
Processing Quantum Simulation
|
| 145 |
+
</motion.h3>
|
| 146 |
+
<motion.p
|
| 147 |
+
initial={{ opacity: 0 }}
|
| 148 |
+
animate={{ opacity: 1 }}
|
| 149 |
+
transition={{ delay: 0.2 }}
|
| 150 |
+
className="text-neutral-600 text-center max-w-md"
|
| 151 |
+
>
|
| 152 |
+
Your quantum computation is being executed. This may take a few moments depending on the complexity.
|
| 153 |
+
</motion.p>
|
| 154 |
+
|
| 155 |
+
{/* Animated progress indicators */}
|
| 156 |
+
<div className="flex gap-2 mt-6">
|
| 157 |
+
{[0, 1, 2].map((i) => (
|
| 158 |
+
<motion.div
|
| 159 |
+
key={i}
|
| 160 |
+
animate={{
|
| 161 |
+
scale: [1, 1.2, 1],
|
| 162 |
+
opacity: [0.3, 1, 0.3]
|
| 163 |
+
}}
|
| 164 |
+
transition={{
|
| 165 |
+
duration: 1.5,
|
| 166 |
+
repeat: Infinity,
|
| 167 |
+
delay: i * 0.2
|
| 168 |
+
}}
|
| 169 |
+
className="w-2 h-2 bg-primary-500 rounded-full"
|
| 170 |
+
/>
|
| 171 |
+
))}
|
| 172 |
+
</div>
|
| 173 |
+
</div>
|
| 174 |
+
</Card>
|
| 175 |
+
);
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
if (!result) {
|
| 179 |
+
return (
|
| 180 |
+
<Card variant="outlined" padding="lg">
|
| 181 |
+
<div className="flex flex-col items-center justify-center py-12 text-neutral-500">
|
| 182 |
+
<Activity size={48} className="mb-4 opacity-50" />
|
| 183 |
+
<h3 className="text-lg font-semibold mb-2">Ready for Simulation</h3>
|
| 184 |
+
<p className="text-center max-w-md">
|
| 185 |
+
Configure your parameters and run a simulation to see quantum results and visualizations.
|
| 186 |
+
</p>
|
| 187 |
+
</div>
|
| 188 |
+
</Card>
|
| 189 |
+
);
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
return (
|
| 193 |
+
<motion.div
|
| 194 |
+
initial={{ opacity: 0, y: 20 }}
|
| 195 |
+
animate={{ opacity: 1, y: 0 }}
|
| 196 |
+
transition={{ duration: 0.5 }}
|
| 197 |
+
className={isFullscreen ? 'fixed inset-4 z-50 bg-base-100 dark:bg-base-200 rounded-xl shadow-2xl' : ''}
|
| 198 |
+
>
|
| 199 |
+
<Card variant="elevated" padding="none" className="h-full">
|
| 200 |
+
<div className="p-4 sm:p-6 border-b border-neutral-200 dark:border-neutral-700 bg-base-100 dark:bg-base-200">
|
| 201 |
+
{/* Main Header */}
|
| 202 |
+
<div className="flex items-center justify-between mb-3 sm:mb-0">
|
| 203 |
+
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
| 204 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-gradient-to-br from-primary-500 to-secondary-500 flex items-center justify-center flex-shrink-0">
|
| 205 |
+
<Activity size={16} className="sm:w-5 sm:h-5 text-white" />
|
| 206 |
+
</div>
|
| 207 |
+
<div className="min-w-0 flex-1">
|
| 208 |
+
<h3 className="text-base sm:text-lg font-semibold text-neutral-900 truncate">
|
| 209 |
+
Simulation Results
|
| 210 |
+
</h3>
|
| 211 |
+
<p className="text-xs sm:text-sm text-neutral-600 hidden sm:block">
|
| 212 |
+
Quantum computation completed successfully
|
| 213 |
+
</p>
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
|
| 217 |
+
{/* Mobile: Only show fullscreen button */}
|
| 218 |
+
<div className="sm:hidden flex-shrink-0">
|
| 219 |
+
<button
|
| 220 |
+
onClick={() => setIsFullscreen(!isFullscreen)}
|
| 221 |
+
className="p-2 rounded-lg bg-base-200 hover:bg-neutral-200 dark:hover:bg-neutral-700 text-neutral-600 dark:text-neutral-200 transition-colors min-h-[44px] min-w-[44px] flex items-center justify-center"
|
| 222 |
+
title={isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}
|
| 223 |
+
>
|
| 224 |
+
<Maximize2 size={16} />
|
| 225 |
+
</button>
|
| 226 |
+
</div>
|
| 227 |
+
|
| 228 |
+
{/* Desktop: Show all buttons */}
|
| 229 |
+
<div className="hidden sm:flex items-center gap-2 flex-shrink-0">
|
| 230 |
+
<Button
|
| 231 |
+
variant="ghost"
|
| 232 |
+
size="sm"
|
| 233 |
+
onClick={handleCopyData}
|
| 234 |
+
icon={copied ? <CheckCircle2 size={16} /> : <Copy size={16} />}
|
| 235 |
+
disabled={!result?.output}
|
| 236 |
+
>
|
| 237 |
+
{copied ? 'Copied!' : 'Copy'}
|
| 238 |
+
</Button>
|
| 239 |
+
|
| 240 |
+
<Button
|
| 241 |
+
variant="ghost"
|
| 242 |
+
size="sm"
|
| 243 |
+
onClick={handleExport}
|
| 244 |
+
icon={<Download size={16} />}
|
| 245 |
+
disabled={!result?.output}
|
| 246 |
+
>
|
| 247 |
+
Export
|
| 248 |
+
</Button>
|
| 249 |
+
|
| 250 |
+
<Button
|
| 251 |
+
variant="ghost"
|
| 252 |
+
size="sm"
|
| 253 |
+
onClick={() => onShare?.(result)}
|
| 254 |
+
icon={<Share2 size={16} />}
|
| 255 |
+
disabled={!result?.output}
|
| 256 |
+
>
|
| 257 |
+
Share
|
| 258 |
+
</Button>
|
| 259 |
+
|
| 260 |
+
<Button
|
| 261 |
+
variant="ghost"
|
| 262 |
+
size="sm"
|
| 263 |
+
onClick={() => setIsFullscreen(!isFullscreen)}
|
| 264 |
+
icon={<Maximize2 size={16} />}
|
| 265 |
+
>
|
| 266 |
+
{isFullscreen ? 'Exit' : 'Fullscreen'}
|
| 267 |
+
</Button>
|
| 268 |
+
</div>
|
| 269 |
+
</div>
|
| 270 |
+
|
| 271 |
+
{/* Mobile Action Buttons Row */}
|
| 272 |
+
<div className="flex gap-2 sm:hidden overflow-x-auto pb-1">
|
| 273 |
+
<button
|
| 274 |
+
onClick={handleCopyData}
|
| 275 |
+
disabled={!result?.output}
|
| 276 |
+
className={`
|
| 277 |
+
flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium whitespace-nowrap transition-all min-h-[44px]
|
| 278 |
+
${copied
|
| 279 |
+
? 'bg-green-100 text-green-700'
|
| 280 |
+
: 'bg-base-200 text-neutral-700 dark:text-neutral-200 hover:bg-neutral-200 dark:hover:bg-neutral-700 disabled:opacity-50'
|
| 281 |
+
}
|
| 282 |
+
`}
|
| 283 |
+
>
|
| 284 |
+
{copied ? <CheckCircle2 size={14} /> : <Copy size={14} />}
|
| 285 |
+
{copied ? 'Copied!' : 'Copy'}
|
| 286 |
+
</button>
|
| 287 |
+
|
| 288 |
+
<button
|
| 289 |
+
onClick={handleExport}
|
| 290 |
+
disabled={!result?.output}
|
| 291 |
+
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium bg-blue-100 text-blue-700 hover:bg-blue-200 disabled:opacity-50 whitespace-nowrap min-h-[44px]"
|
| 292 |
+
>
|
| 293 |
+
<Download size={14} />
|
| 294 |
+
Export
|
| 295 |
+
</button>
|
| 296 |
+
|
| 297 |
+
<button
|
| 298 |
+
onClick={() => onShare?.(result)}
|
| 299 |
+
disabled={!result?.output}
|
| 300 |
+
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium bg-purple-100 text-purple-700 hover:bg-purple-200 disabled:opacity-50 whitespace-nowrap min-h-[44px]"
|
| 301 |
+
>
|
| 302 |
+
<Share2 size={14} />
|
| 303 |
+
Share
|
| 304 |
+
</button>
|
| 305 |
+
</div>
|
| 306 |
+
</div>
|
| 307 |
+
|
| 308 |
+
<div className="p-4 sm:p-6">
|
| 309 |
+
<SimpleTabs tabs={tabs} defaultTab="visualization" />
|
| 310 |
+
</div>
|
| 311 |
+
</Card>
|
| 312 |
+
|
| 313 |
+
{isFullscreen && (
|
| 314 |
+
<motion.div
|
| 315 |
+
initial={{ opacity: 0 }}
|
| 316 |
+
animate={{ opacity: 1 }}
|
| 317 |
+
exit={{ opacity: 0 }}
|
| 318 |
+
className="fixed inset-0 bg-black/50 z-40"
|
| 319 |
+
onClick={() => setIsFullscreen(false)}
|
| 320 |
+
/>
|
| 321 |
+
)}
|
| 322 |
+
</motion.div>
|
| 323 |
+
);
|
| 324 |
+
};
|
| 325 |
+
|
| 326 |
+
EnhancedPluginResultsPanel.propTypes = {
|
| 327 |
+
result: PropTypes.object,
|
| 328 |
+
loading: PropTypes.bool,
|
| 329 |
+
onExport: PropTypes.func,
|
| 330 |
+
onShare: PropTypes.func,
|
| 331 |
+
};
|
| 332 |
+
|
| 333 |
+
export default EnhancedPluginResultsPanel;
|
frontend/src/components/plugin/FullscreenCircuitViewer.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useRef } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import { X, ZoomIn, ZoomOut, RotateCcw, Maximize2 } from 'lucide-react';
|
| 4 |
+
import PropTypes from 'prop-types';
|
| 5 |
+
|
| 6 |
+
const FullscreenCircuitViewer = ({ circuitSvg, isOpen, onClose }) => {
|
| 7 |
+
const [zoom, setZoom] = useState(1);
|
| 8 |
+
const [isDragging, setIsDragging] = useState(false);
|
| 9 |
+
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
|
| 10 |
+
const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 });
|
| 11 |
+
const containerRef = useRef(null);
|
| 12 |
+
|
| 13 |
+
const handleZoomIn = () => setZoom(prev => Math.min(prev + 0.3, 5));
|
| 14 |
+
const handleZoomOut = () => setZoom(prev => Math.max(prev - 0.3, 0.3));
|
| 15 |
+
const handleResetZoom = () => setZoom(1);
|
| 16 |
+
|
| 17 |
+
// Keyboard support
|
| 18 |
+
React.useEffect(() => {
|
| 19 |
+
const handleKeyDown = (e) => {
|
| 20 |
+
if (!isOpen) return;
|
| 21 |
+
|
| 22 |
+
switch (e.key) {
|
| 23 |
+
case 'Escape':
|
| 24 |
+
onClose();
|
| 25 |
+
break;
|
| 26 |
+
case '+':
|
| 27 |
+
case '=':
|
| 28 |
+
e.preventDefault();
|
| 29 |
+
handleZoomIn();
|
| 30 |
+
break;
|
| 31 |
+
case '-':
|
| 32 |
+
e.preventDefault();
|
| 33 |
+
handleZoomOut();
|
| 34 |
+
break;
|
| 35 |
+
case '0':
|
| 36 |
+
e.preventDefault();
|
| 37 |
+
handleResetZoom();
|
| 38 |
+
break;
|
| 39 |
+
default:
|
| 40 |
+
break;
|
| 41 |
+
}
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
document.addEventListener('keydown', handleKeyDown);
|
| 45 |
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
| 46 |
+
}, [isOpen, onClose]);
|
| 47 |
+
|
| 48 |
+
const handleMouseDown = (e) => {
|
| 49 |
+
if (e.target.closest('.zoom-controls')) return;
|
| 50 |
+
setIsDragging(true);
|
| 51 |
+
setDragStart({ x: e.clientX, y: e.clientY });
|
| 52 |
+
if (containerRef.current) {
|
| 53 |
+
setScrollPos({
|
| 54 |
+
x: containerRef.current.scrollLeft,
|
| 55 |
+
y: containerRef.current.scrollTop
|
| 56 |
+
});
|
| 57 |
+
}
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
const handleMouseMove = (e) => {
|
| 61 |
+
if (!isDragging || !containerRef.current) return;
|
| 62 |
+
|
| 63 |
+
const deltaX = e.clientX - dragStart.x;
|
| 64 |
+
const deltaY = e.clientY - dragStart.y;
|
| 65 |
+
|
| 66 |
+
containerRef.current.scrollLeft = scrollPos.x - deltaX;
|
| 67 |
+
containerRef.current.scrollTop = scrollPos.y - deltaY;
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
const handleMouseUp = () => {
|
| 71 |
+
setIsDragging(false);
|
| 72 |
+
};
|
| 73 |
+
|
| 74 |
+
if (!isOpen || !circuitSvg) return null;
|
| 75 |
+
|
| 76 |
+
return (
|
| 77 |
+
<AnimatePresence>
|
| 78 |
+
<motion.div
|
| 79 |
+
initial={{ opacity: 0 }}
|
| 80 |
+
animate={{ opacity: 1 }}
|
| 81 |
+
exit={{ opacity: 0 }}
|
| 82 |
+
className="fixed inset-0 z-50 bg-black/90 backdrop-blur-sm flex items-center justify-center"
|
| 83 |
+
onClick={onClose}
|
| 84 |
+
>
|
| 85 |
+
<motion.div
|
| 86 |
+
initial={{ opacity: 0, scale: 0.9 }}
|
| 87 |
+
animate={{ opacity: 1, scale: 1 }}
|
| 88 |
+
exit={{ opacity: 0, scale: 0.9 }}
|
| 89 |
+
onClick={(e) => e.stopPropagation()}
|
| 90 |
+
className="w-full h-full max-w-7xl max-h-[90vh] bg-white dark:bg-neutral-900 rounded-lg shadow-2xl overflow-hidden m-4"
|
| 91 |
+
>
|
| 92 |
+
{/* Header */}
|
| 93 |
+
<div className="flex items-center justify-between p-4 border-b border-neutral-200 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800">
|
| 94 |
+
<div>
|
| 95 |
+
<h3 className="text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
| 96 |
+
Quantum Circuit Diagram
|
| 97 |
+
</h3>
|
| 98 |
+
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
| 99 |
+
Full-screen view with advanced zoom and pan controls
|
| 100 |
+
</p>
|
| 101 |
+
</div>
|
| 102 |
+
|
| 103 |
+
{/* Controls */}
|
| 104 |
+
<div className="flex items-center gap-2">
|
| 105 |
+
<div className="zoom-controls flex items-center gap-1 bg-white dark:bg-neutral-800 rounded-lg p-1 border border-neutral-200 dark:border-neutral-600">
|
| 106 |
+
<button
|
| 107 |
+
onClick={handleZoomOut}
|
| 108 |
+
className="p-2 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded text-neutral-600 dark:text-neutral-400 transition-colors"
|
| 109 |
+
title="Zoom Out"
|
| 110 |
+
>
|
| 111 |
+
<ZoomOut size={16} />
|
| 112 |
+
</button>
|
| 113 |
+
|
| 114 |
+
<span className="text-sm text-neutral-600 dark:text-neutral-400 min-w-[4rem] text-center px-2">
|
| 115 |
+
{Math.round(zoom * 100)}%
|
| 116 |
+
</span>
|
| 117 |
+
|
| 118 |
+
<button
|
| 119 |
+
onClick={handleZoomIn}
|
| 120 |
+
className="p-2 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded text-neutral-600 dark:text-neutral-400 transition-colors"
|
| 121 |
+
title="Zoom In"
|
| 122 |
+
>
|
| 123 |
+
<ZoomIn size={16} />
|
| 124 |
+
</button>
|
| 125 |
+
|
| 126 |
+
<button
|
| 127 |
+
onClick={handleResetZoom}
|
| 128 |
+
className="p-2 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded text-neutral-600 dark:text-neutral-400 transition-colors"
|
| 129 |
+
title="Reset Zoom"
|
| 130 |
+
>
|
| 131 |
+
<RotateCcw size={16} />
|
| 132 |
+
</button>
|
| 133 |
+
</div>
|
| 134 |
+
|
| 135 |
+
<button
|
| 136 |
+
onClick={onClose}
|
| 137 |
+
className="p-2 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded text-neutral-600 dark:text-neutral-400 transition-colors"
|
| 138 |
+
title="Close Fullscreen"
|
| 139 |
+
>
|
| 140 |
+
<X size={20} />
|
| 141 |
+
</button>
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
|
| 145 |
+
{/* Circuit Container */}
|
| 146 |
+
<div
|
| 147 |
+
ref={containerRef}
|
| 148 |
+
className="flex-1 overflow-auto bg-neutral-50 dark:bg-neutral-900 cursor-grab active:cursor-grabbing"
|
| 149 |
+
style={{
|
| 150 |
+
height: 'calc(100% - 80px)',
|
| 151 |
+
scrollbarWidth: 'thin',
|
| 152 |
+
scrollbarColor: '#cbd5e1 #f1f5f9'
|
| 153 |
+
}}
|
| 154 |
+
onMouseDown={handleMouseDown}
|
| 155 |
+
onMouseMove={handleMouseMove}
|
| 156 |
+
onMouseUp={handleMouseUp}
|
| 157 |
+
onMouseLeave={handleMouseUp}
|
| 158 |
+
>
|
| 159 |
+
<div className="flex items-center justify-center min-h-full p-8">
|
| 160 |
+
<div
|
| 161 |
+
dangerouslySetInnerHTML={{ __html: circuitSvg }}
|
| 162 |
+
className="transition-transform duration-200 bg-white dark:bg-neutral-800 rounded-lg p-6 shadow-lg"
|
| 163 |
+
style={{
|
| 164 |
+
transform: `scale(${zoom})`,
|
| 165 |
+
transformOrigin: 'center center'
|
| 166 |
+
}}
|
| 167 |
+
/>
|
| 168 |
+
</div>
|
| 169 |
+
</div>
|
| 170 |
+
|
| 171 |
+
{/* Footer with instructions */}
|
| 172 |
+
<div className="p-3 bg-neutral-50 dark:bg-neutral-800 border-t border-neutral-200 dark:border-neutral-700">
|
| 173 |
+
<div className="flex items-center justify-center gap-6 text-xs text-neutral-500 dark:text-neutral-400">
|
| 174 |
+
<span className="flex items-center gap-1">
|
| 175 |
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
| 176 |
+
<path d="M9 11H1m22 0h-8M9 11l3-3m-3 3l3 3"/>
|
| 177 |
+
</svg>
|
| 178 |
+
Drag to pan around the circuit
|
| 179 |
+
</span>
|
| 180 |
+
<span className="flex items-center gap-1">
|
| 181 |
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
| 182 |
+
<circle cx="11" cy="11" r="8"/>
|
| 183 |
+
<path d="M21 21l-4.35-4.35"/>
|
| 184 |
+
</svg>
|
| 185 |
+
Use zoom controls or mouse wheel
|
| 186 |
+
</span>
|
| 187 |
+
<span className="flex items-center gap-1">
|
| 188 |
+
<kbd className="px-1 py-0.5 bg-neutral-200 dark:bg-neutral-700 rounded text-xs">Esc</kbd>
|
| 189 |
+
Close fullscreen
|
| 190 |
+
</span>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
</motion.div>
|
| 194 |
+
</motion.div>
|
| 195 |
+
</AnimatePresence>
|
| 196 |
+
);
|
| 197 |
+
};
|
| 198 |
+
|
| 199 |
+
FullscreenCircuitViewer.propTypes = {
|
| 200 |
+
circuitSvg: PropTypes.string,
|
| 201 |
+
isOpen: PropTypes.bool.isRequired,
|
| 202 |
+
onClose: PropTypes.func.isRequired,
|
| 203 |
+
};
|
| 204 |
+
|
| 205 |
+
export default FullscreenCircuitViewer;
|
frontend/src/components/plugin/PluginExplanation.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import PropTypes from 'prop-types';
|
| 3 |
+
import { Card, Alert } from 'react-bootstrap';
|
| 4 |
+
|
| 5 |
+
const PluginExplanation = ({ educationalContent, miniExplanation, onLearnMore }) => (
|
| 6 |
+
<div className="plugin-explanation">
|
| 7 |
+
{miniExplanation && (
|
| 8 |
+
<Alert variant="info" className="mb-3">
|
| 9 |
+
<div dangerouslySetInnerHTML={{ __html: miniExplanation }} />
|
| 10 |
+
{onLearnMore && (
|
| 11 |
+
<div className="text-end mt-2">
|
| 12 |
+
<button className="btn btn-link btn-sm" onClick={onLearnMore}>
|
| 13 |
+
Learn More
|
| 14 |
+
</button>
|
| 15 |
+
</div>
|
| 16 |
+
)}
|
| 17 |
+
</Alert>
|
| 18 |
+
)}
|
| 19 |
+
{educationalContent && (
|
| 20 |
+
<Card className="mb-3">
|
| 21 |
+
<Card.Body>
|
| 22 |
+
<div dangerouslySetInnerHTML={{ __html: educationalContent }} />
|
| 23 |
+
</Card.Body>
|
| 24 |
+
</Card>
|
| 25 |
+
)}
|
| 26 |
+
</div>
|
| 27 |
+
);
|
| 28 |
+
|
| 29 |
+
PluginExplanation.propTypes = {
|
| 30 |
+
educationalContent: PropTypes.string,
|
| 31 |
+
miniExplanation: PropTypes.string,
|
| 32 |
+
onLearnMore: PropTypes.func,
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
export default PluginExplanation;
|
frontend/src/components/plugin/PluginLayout.css
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.plugin-layout {
|
| 2 |
+
min-height: 80vh;
|
| 3 |
+
}
|
| 4 |
+
.plugin-sidebar {
|
| 5 |
+
background: transparent;
|
| 6 |
+
border-right: 1px solid rgba(229,231,235,1);
|
| 7 |
+
min-height: 100vh;
|
| 8 |
+
padding-top: 2rem;
|
| 9 |
+
position: sticky;
|
| 10 |
+
top: 4rem;
|
| 11 |
+
}
|
| 12 |
+
.plugin-main-content {
|
| 13 |
+
padding: 2rem 2rem 2rem 2rem;
|
| 14 |
+
}
|
| 15 |
+
@media (max-width: 1024px) {
|
| 16 |
+
.plugin-sidebar {
|
| 17 |
+
min-height: auto;
|
| 18 |
+
padding-top: 1rem;
|
| 19 |
+
position: static;
|
| 20 |
+
border-right: none;
|
| 21 |
+
border-bottom: 1px solid rgba(229,231,235,1);
|
| 22 |
+
margin-bottom: 1rem;
|
| 23 |
+
}
|
| 24 |
+
.plugin-main-content {
|
| 25 |
+
padding: 1rem;
|
| 26 |
+
}
|
| 27 |
+
}
|
frontend/src/components/plugin/PluginLayout.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import PropTypes from 'prop-types';
|
| 3 |
+
import './PluginLayout.css';
|
| 4 |
+
|
| 5 |
+
const PluginLayout = ({ sidebar, children }) => (
|
| 6 |
+
<div className="container mx-auto px-4 plugin-layout">
|
| 7 |
+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
| 8 |
+
<aside className="lg:col-span-3 xl:col-span-2 plugin-sidebar">
|
| 9 |
+
{sidebar}
|
| 10 |
+
</aside>
|
| 11 |
+
<main className="lg:col-span-9 xl:col-span-10 plugin-main-content">
|
| 12 |
+
{children}
|
| 13 |
+
</main>
|
| 14 |
+
</div>
|
| 15 |
+
</div>
|
| 16 |
+
);
|
| 17 |
+
|
| 18 |
+
PluginLayout.propTypes = {
|
| 19 |
+
sidebar: PropTypes.node,
|
| 20 |
+
children: PropTypes.node,
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
export default PluginLayout;
|
frontend/src/components/plugin/PluginParameterForm.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import PropTypes from 'prop-types';
|
| 3 |
+
import { Button, Form } from 'react-bootstrap';
|
| 4 |
+
|
| 5 |
+
const PluginParameterForm = ({ parameters, initialValues, onSubmit, loading }) => {
|
| 6 |
+
const [values, setValues] = useState(initialValues || {});
|
| 7 |
+
const [errors, setErrors] = useState({});
|
| 8 |
+
|
| 9 |
+
const handleChange = (e, param) => {
|
| 10 |
+
const { name, value, type, checked } = e.target;
|
| 11 |
+
setValues((prev) => ({
|
| 12 |
+
...prev,
|
| 13 |
+
[name]: type === 'checkbox' ? checked : value,
|
| 14 |
+
}));
|
| 15 |
+
};
|
| 16 |
+
|
| 17 |
+
const handleSubmit = (e) => {
|
| 18 |
+
e.preventDefault();
|
| 19 |
+
setErrors({});
|
| 20 |
+
onSubmit(values);
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
return (
|
| 24 |
+
<Form onSubmit={handleSubmit} className="plugin-parameter-form">
|
| 25 |
+
{parameters.map((param) => (
|
| 26 |
+
<Form.Group className="mb-3" controlId={`param-${param.name}`} key={param.name}>
|
| 27 |
+
<Form.Label>{param.description || param.name} {param.type === 'int' && <span className="text-muted">(int{param.min !== undefined ? `, min: ${param.min}` : ''}{param.max !== undefined ? `, max: ${param.max}` : ''})</span>} {param.type === 'float' && <span className="text-muted">(float{param.min !== undefined ? `, min: ${param.min}` : ''}{param.max !== undefined ? `, max: ${param.max}` : ''})</span>}</Form.Label>
|
| 28 |
+
{param.type === 'int' || param.type === 'float' ? (
|
| 29 |
+
<Form.Control
|
| 30 |
+
type="number"
|
| 31 |
+
name={param.name}
|
| 32 |
+
value={values[param.name] ?? param.default ?? ''}
|
| 33 |
+
min={param.min}
|
| 34 |
+
max={param.max}
|
| 35 |
+
step={param.type === 'float' ? 'any' : '1'}
|
| 36 |
+
onChange={(e) => handleChange(e, param)}
|
| 37 |
+
required={param.default === undefined}
|
| 38 |
+
/>
|
| 39 |
+
) : param.type === 'bool' ? (
|
| 40 |
+
<Form.Check
|
| 41 |
+
type="checkbox"
|
| 42 |
+
name={param.name}
|
| 43 |
+
label="Yes"
|
| 44 |
+
checked={!!values[param.name]}
|
| 45 |
+
onChange={(e) => handleChange(e, param)}
|
| 46 |
+
/>
|
| 47 |
+
) : param.type === 'select' ? (
|
| 48 |
+
<Form.Select
|
| 49 |
+
name={param.name}
|
| 50 |
+
value={values[param.name] ?? param.default ?? ''}
|
| 51 |
+
onChange={(e) => handleChange(e, param)}
|
| 52 |
+
>
|
| 53 |
+
{param.options.map((opt) => (
|
| 54 |
+
<option value={opt} key={opt}>{opt}</option>
|
| 55 |
+
))}
|
| 56 |
+
</Form.Select>
|
| 57 |
+
) : (
|
| 58 |
+
<Form.Control
|
| 59 |
+
type="text"
|
| 60 |
+
name={param.name}
|
| 61 |
+
value={values[param.name] ?? param.default ?? ''}
|
| 62 |
+
maxLength={param.max_length}
|
| 63 |
+
onChange={(e) => handleChange(e, param)}
|
| 64 |
+
required={param.default === undefined}
|
| 65 |
+
/>
|
| 66 |
+
)}
|
| 67 |
+
{errors[param.name] && <Form.Text className="text-danger">{errors[param.name]}</Form.Text>}
|
| 68 |
+
</Form.Group>
|
| 69 |
+
))}
|
| 70 |
+
<Button variant="primary" type="submit" disabled={loading} className="w-100 mb-2">
|
| 71 |
+
{loading ? 'Running...' : 'Run Simulation'}
|
| 72 |
+
</Button>
|
| 73 |
+
<Button variant="outline-secondary" type="button" className="w-100" onClick={() => setValues(initialValues || {})}>
|
| 74 |
+
Reset Parameters
|
| 75 |
+
</Button>
|
| 76 |
+
</Form>
|
| 77 |
+
);
|
| 78 |
+
};
|
| 79 |
+
|
| 80 |
+
PluginParameterForm.propTypes = {
|
| 81 |
+
parameters: PropTypes.array.isRequired,
|
| 82 |
+
initialValues: PropTypes.object,
|
| 83 |
+
onSubmit: PropTypes.func.isRequired,
|
| 84 |
+
loading: PropTypes.bool,
|
| 85 |
+
};
|
| 86 |
+
|
| 87 |
+
export default PluginParameterForm;
|
frontend/src/components/plugin/PluginResultsPanel.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import PropTypes from 'prop-types';
|
| 3 |
+
import { Tabs, Tab, Card } from 'react-bootstrap';
|
| 4 |
+
|
| 5 |
+
const PluginResultsPanel = ({ result, loading }) => {
|
| 6 |
+
const [activeTab, setActiveTab] = useState('visualization');
|
| 7 |
+
|
| 8 |
+
return (
|
| 9 |
+
<Card className="plugin-results-panel mb-4">
|
| 10 |
+
<Card.Header>
|
| 11 |
+
<Tabs
|
| 12 |
+
id="plugin-results-tabs"
|
| 13 |
+
activeKey={activeTab}
|
| 14 |
+
onSelect={(k) => setActiveTab(k)}
|
| 15 |
+
className="mb-0"
|
| 16 |
+
>
|
| 17 |
+
<Tab eventKey="visualization" title="Visualization" />
|
| 18 |
+
<Tab eventKey="raw" title="Raw Data" />
|
| 19 |
+
<Tab eventKey="log" title="Process Log" />
|
| 20 |
+
</Tabs>
|
| 21 |
+
</Card.Header>
|
| 22 |
+
<Card.Body>
|
| 23 |
+
{loading && <div className="text-center text-muted">Processing...</div>}
|
| 24 |
+
{!loading && result && (
|
| 25 |
+
<>
|
| 26 |
+
{activeTab === 'visualization' && (
|
| 27 |
+
<>
|
| 28 |
+
{result.output && result.output.circuit_svg && (
|
| 29 |
+
<div className="mb-3">
|
| 30 |
+
<div
|
| 31 |
+
className="circuit-svg"
|
| 32 |
+
style={{
|
| 33 |
+
backgroundColor: '#fff',
|
| 34 |
+
padding: '1rem',
|
| 35 |
+
borderRadius: '8px',
|
| 36 |
+
marginBottom: '1.5rem',
|
| 37 |
+
textAlign: 'center',
|
| 38 |
+
overflow: 'auto',
|
| 39 |
+
maxWidth: '100%'
|
| 40 |
+
}}
|
| 41 |
+
>
|
| 42 |
+
<div
|
| 43 |
+
dangerouslySetInnerHTML={{ __html: result.output.circuit_svg }}
|
| 44 |
+
style={{
|
| 45 |
+
display: 'inline-block',
|
| 46 |
+
maxWidth: '100%',
|
| 47 |
+
height: 'auto'
|
| 48 |
+
}}
|
| 49 |
+
/>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
)}
|
| 53 |
+
{result.output && result.output.probabilities && (
|
| 54 |
+
<div className="mb-3">
|
| 55 |
+
{/* Render probability distribution as a bar chart if available */}
|
| 56 |
+
{/* You can use a chart library or a simple table here */}
|
| 57 |
+
<h6>Probability Distribution</h6>
|
| 58 |
+
<pre style={{ background: '#f8f9fa', padding: '1em', borderRadius: '6px' }}>{JSON.stringify(result.output.probabilities, null, 2)}</pre>
|
| 59 |
+
</div>
|
| 60 |
+
)}
|
| 61 |
+
</>
|
| 62 |
+
)}
|
| 63 |
+
{activeTab === 'raw' && (
|
| 64 |
+
<pre style={{ background: '#f8f9fa', padding: '1em', borderRadius: '6px' }}>{JSON.stringify(result.output, null, 2)}</pre>
|
| 65 |
+
)}
|
| 66 |
+
{activeTab === 'log' && (
|
| 67 |
+
<pre style={{ background: '#f8f9fa', padding: '1em', borderRadius: '6px' }}>{result.log || 'No log available.'}</pre>
|
| 68 |
+
)}
|
| 69 |
+
</>
|
| 70 |
+
)}
|
| 71 |
+
{!loading && !result && <div className="text-center text-muted">No results yet.</div>}
|
| 72 |
+
</Card.Body>
|
| 73 |
+
</Card>
|
| 74 |
+
);
|
| 75 |
+
};
|
| 76 |
+
|
| 77 |
+
PluginResultsPanel.propTypes = {
|
| 78 |
+
result: PropTypes.object,
|
| 79 |
+
loading: PropTypes.bool,
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
export default PluginResultsPanel;
|
frontend/src/components/plugin/QuantumVisualization.js
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect, useRef, useState } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import { BarChart3, Activity, Zap, Info, Maximize2 } from 'lucide-react';
|
| 4 |
+
import PropTypes from 'prop-types';
|
| 5 |
+
import FullscreenCircuitViewer from './FullscreenCircuitViewer';
|
| 6 |
+
|
| 7 |
+
const QuantumVisualization = ({ result, type = 'auto' }) => {
|
| 8 |
+
const [activeVisualization, setActiveVisualization] = useState('circuit');
|
| 9 |
+
const [showFullscreen, setShowFullscreen] = useState(false);
|
| 10 |
+
|
| 11 |
+
// Bloch Sphere Visualization
|
| 12 |
+
const BlochSphere = ({ stateVector }) => {
|
| 13 |
+
const canvasRef = useRef(null);
|
| 14 |
+
|
| 15 |
+
useEffect(() => {
|
| 16 |
+
if (!canvasRef.current || !stateVector) return;
|
| 17 |
+
|
| 18 |
+
const canvas = canvasRef.current;
|
| 19 |
+
const ctx = canvas.getContext('2d');
|
| 20 |
+
const centerX = canvas.width / 2;
|
| 21 |
+
const centerY = canvas.height / 2;
|
| 22 |
+
const radius = Math.min(centerX, centerY) - 20;
|
| 23 |
+
|
| 24 |
+
// Clear canvas
|
| 25 |
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 26 |
+
|
| 27 |
+
// Draw sphere outline
|
| 28 |
+
ctx.beginPath();
|
| 29 |
+
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
|
| 30 |
+
ctx.strokeStyle = '#e5e7eb';
|
| 31 |
+
ctx.lineWidth = 2;
|
| 32 |
+
ctx.stroke();
|
| 33 |
+
|
| 34 |
+
// Draw axes
|
| 35 |
+
ctx.strokeStyle = '#9ca3af';
|
| 36 |
+
ctx.lineWidth = 1;
|
| 37 |
+
|
| 38 |
+
// X axis
|
| 39 |
+
ctx.beginPath();
|
| 40 |
+
ctx.moveTo(centerX - radius, centerY);
|
| 41 |
+
ctx.lineTo(centerX + radius, centerY);
|
| 42 |
+
ctx.stroke();
|
| 43 |
+
|
| 44 |
+
// Y axis (vertical)
|
| 45 |
+
ctx.beginPath();
|
| 46 |
+
ctx.moveTo(centerX, centerY - radius);
|
| 47 |
+
ctx.lineTo(centerX, centerY + radius);
|
| 48 |
+
ctx.stroke();
|
| 49 |
+
|
| 50 |
+
// Calculate state vector position on Bloch sphere
|
| 51 |
+
if (stateVector && stateVector.length >= 2) {
|
| 52 |
+
const alpha = stateVector[0];
|
| 53 |
+
const beta = stateVector[1];
|
| 54 |
+
|
| 55 |
+
// Convert to Bloch sphere coordinates
|
| 56 |
+
const theta = 2 * Math.acos(Math.abs(alpha));
|
| 57 |
+
const phi = Math.arg ? Math.arg(beta / alpha) : 0;
|
| 58 |
+
|
| 59 |
+
const x = radius * Math.sin(theta) * Math.cos(phi);
|
| 60 |
+
const z = radius * Math.cos(theta);
|
| 61 |
+
|
| 62 |
+
// Project to 2D (simple projection)
|
| 63 |
+
const projX = centerX + x;
|
| 64 |
+
const projY = centerY - z; // Flip Y for canvas coordinates
|
| 65 |
+
|
| 66 |
+
// Draw state vector
|
| 67 |
+
ctx.beginPath();
|
| 68 |
+
ctx.moveTo(centerX, centerY);
|
| 69 |
+
ctx.lineTo(projX, projY);
|
| 70 |
+
ctx.strokeStyle = '#6366f1';
|
| 71 |
+
ctx.lineWidth = 3;
|
| 72 |
+
ctx.stroke();
|
| 73 |
+
|
| 74 |
+
// Draw state point
|
| 75 |
+
ctx.beginPath();
|
| 76 |
+
ctx.arc(projX, projY, 6, 0, 2 * Math.PI);
|
| 77 |
+
ctx.fillStyle = '#6366f1';
|
| 78 |
+
ctx.fill();
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
// Add labels
|
| 82 |
+
ctx.fillStyle = '#374151';
|
| 83 |
+
ctx.font = '12px Inter';
|
| 84 |
+
ctx.textAlign = 'center';
|
| 85 |
+
ctx.fillText('|0⟩', centerX, centerY - radius - 10);
|
| 86 |
+
ctx.fillText('|1⟩', centerX, centerY + radius + 20);
|
| 87 |
+
ctx.textAlign = 'left';
|
| 88 |
+
ctx.fillText('|+⟩', centerX + radius + 10, centerY + 5);
|
| 89 |
+
ctx.textAlign = 'right';
|
| 90 |
+
ctx.fillText('|-⟩', centerX - radius - 10, centerY + 5);
|
| 91 |
+
}, [stateVector]);
|
| 92 |
+
|
| 93 |
+
return (
|
| 94 |
+
<canvas
|
| 95 |
+
ref={canvasRef}
|
| 96 |
+
width={300}
|
| 97 |
+
height={300}
|
| 98 |
+
className="border border-neutral-200 dark:border-neutral-600 rounded-lg bg-base-100 dark:bg-base-200"
|
| 99 |
+
/>
|
| 100 |
+
);
|
| 101 |
+
};
|
| 102 |
+
|
| 103 |
+
// Probability Bar Chart
|
| 104 |
+
const ProbabilityChart = ({ probabilities }) => {
|
| 105 |
+
if (!probabilities || typeof probabilities !== 'object') return null;
|
| 106 |
+
|
| 107 |
+
const entries = Object.entries(probabilities);
|
| 108 |
+
const maxProb = Math.max(...entries.map(([, prob]) => prob));
|
| 109 |
+
|
| 110 |
+
return (
|
| 111 |
+
<div className="space-y-3">
|
| 112 |
+
{entries.map(([state, probability]) => (
|
| 113 |
+
<motion.div
|
| 114 |
+
key={state}
|
| 115 |
+
initial={{ opacity: 0, x: -20 }}
|
| 116 |
+
animate={{ opacity: 1, x: 0 }}
|
| 117 |
+
className="flex items-center gap-3"
|
| 118 |
+
>
|
| 119 |
+
<div className="w-12 text-sm font-mono text-neutral-600">|{state}⟩</div>
|
| 120 |
+
<div className="flex-1 bg-base-200 dark:bg-neutral-800 rounded-full h-6 relative overflow-hidden">
|
| 121 |
+
<motion.div
|
| 122 |
+
initial={{ width: 0 }}
|
| 123 |
+
animate={{ width: `${(probability / maxProb) * 100}%` }}
|
| 124 |
+
transition={{ duration: 0.8, ease: "easeOut" }}
|
| 125 |
+
className="h-full bg-gradient-to-r from-primary-400 to-primary-600 rounded-full"
|
| 126 |
+
/>
|
| 127 |
+
<div className="absolute inset-0 flex items-center justify-center text-xs font-medium text-neutral-700">
|
| 128 |
+
{(probability * 100).toFixed(1)}%
|
| 129 |
+
</div>
|
| 130 |
+
</div>
|
| 131 |
+
</motion.div>
|
| 132 |
+
))}
|
| 133 |
+
</div>
|
| 134 |
+
);
|
| 135 |
+
};
|
| 136 |
+
|
| 137 |
+
// Circuit SVG Display
|
| 138 |
+
const CircuitDisplay = ({ circuitSvg }) => {
|
| 139 |
+
const [zoom, setZoom] = useState(1);
|
| 140 |
+
const [isDragging, setIsDragging] = useState(false);
|
| 141 |
+
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
|
| 142 |
+
const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 });
|
| 143 |
+
const containerRef = useRef(null);
|
| 144 |
+
|
| 145 |
+
if (!circuitSvg) return null;
|
| 146 |
+
|
| 147 |
+
const handleZoomIn = () => setZoom(prev => Math.min(prev + 0.2, 3));
|
| 148 |
+
const handleZoomOut = () => setZoom(prev => Math.max(prev - 0.2, 0.5));
|
| 149 |
+
const handleResetZoom = () => setZoom(1);
|
| 150 |
+
|
| 151 |
+
const handleMouseDown = (e) => {
|
| 152 |
+
setIsDragging(true);
|
| 153 |
+
setDragStart({ x: e.clientX, y: e.clientY });
|
| 154 |
+
};
|
| 155 |
+
|
| 156 |
+
const handleMouseMove = (e) => {
|
| 157 |
+
if (!isDragging || !containerRef.current) return;
|
| 158 |
+
|
| 159 |
+
const deltaX = e.clientX - dragStart.x;
|
| 160 |
+
const deltaY = e.clientY - dragStart.y;
|
| 161 |
+
|
| 162 |
+
containerRef.current.scrollLeft = scrollPos.x - deltaX;
|
| 163 |
+
containerRef.current.scrollTop = scrollPos.y - deltaY;
|
| 164 |
+
};
|
| 165 |
+
|
| 166 |
+
const handleMouseUp = () => {
|
| 167 |
+
if (containerRef.current) {
|
| 168 |
+
setScrollPos({
|
| 169 |
+
x: containerRef.current.scrollLeft,
|
| 170 |
+
y: containerRef.current.scrollTop
|
| 171 |
+
});
|
| 172 |
+
}
|
| 173 |
+
setIsDragging(false);
|
| 174 |
+
};
|
| 175 |
+
|
| 176 |
+
return (
|
| 177 |
+
<motion.div
|
| 178 |
+
initial={{ opacity: 0, scale: 0.95 }}
|
| 179 |
+
animate={{ opacity: 1, scale: 1 }}
|
| 180 |
+
className="bg-base-100 dark:bg-base-200 p-6 rounded-xl border border-neutral-200 dark:border-neutral-700 overflow-hidden"
|
| 181 |
+
>
|
| 182 |
+
<div className="mb-4 flex items-center justify-between">
|
| 183 |
+
<div>
|
| 184 |
+
<h4 className="text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">
|
| 185 |
+
Quantum Circuit Diagram
|
| 186 |
+
</h4>
|
| 187 |
+
<p className="text-xs text-neutral-500 dark:text-neutral-400">
|
| 188 |
+
Scroll or drag to navigate • Use zoom controls for better viewing
|
| 189 |
+
</p>
|
| 190 |
+
</div>
|
| 191 |
+
|
| 192 |
+
{/* Controls */}
|
| 193 |
+
<div className="flex items-center gap-2">
|
| 194 |
+
{/* Zoom Controls */}
|
| 195 |
+
<div className="flex items-center gap-1 sm:gap-2 bg-base-200 dark:bg-neutral-800 rounded-lg p-1">
|
| 196 |
+
<button
|
| 197 |
+
onClick={handleZoomOut}
|
| 198 |
+
className="p-2 sm:p-1 hover:bg-neutral-200 dark:hover:bg-neutral-700 rounded text-neutral-600 dark:text-neutral-400 transition-colors min-h-[44px] sm:min-h-[32px] min-w-[44px] sm:min-w-[32px] flex items-center justify-center"
|
| 199 |
+
title="Zoom Out"
|
| 200 |
+
>
|
| 201 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
| 202 |
+
<circle cx="11" cy="11" r="8"/>
|
| 203 |
+
<path d="M21 21l-4.35-4.35"/>
|
| 204 |
+
<line x1="8" y1="11" x2="14" y2="11"/>
|
| 205 |
+
</svg>
|
| 206 |
+
</button>
|
| 207 |
+
|
| 208 |
+
<span className="text-xs text-neutral-600 dark:text-neutral-400 min-w-[3rem] text-center px-1">
|
| 209 |
+
{Math.round(zoom * 100)}%
|
| 210 |
+
</span>
|
| 211 |
+
|
| 212 |
+
<button
|
| 213 |
+
onClick={handleZoomIn}
|
| 214 |
+
className="p-2 sm:p-1 hover:bg-neutral-200 dark:hover:bg-neutral-700 rounded text-neutral-600 dark:text-neutral-400 transition-colors min-h-[44px] sm:min-h-[32px] min-w-[44px] sm:min-w-[32px] flex items-center justify-center"
|
| 215 |
+
title="Zoom In"
|
| 216 |
+
>
|
| 217 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
| 218 |
+
<circle cx="11" cy="11" r="8"/>
|
| 219 |
+
<path d="M21 21l-4.35-4.35"/>
|
| 220 |
+
<line x1="8" y1="11" x2="14" y2="11"/>
|
| 221 |
+
<line x1="11" y1="8" x2="11" y2="14"/>
|
| 222 |
+
</svg>
|
| 223 |
+
</button>
|
| 224 |
+
|
| 225 |
+
<button
|
| 226 |
+
onClick={handleResetZoom}
|
| 227 |
+
className="p-2 sm:p-1 hover:bg-neutral-200 dark:hover:bg-neutral-700 rounded text-neutral-600 dark:text-neutral-400 transition-colors text-xs min-h-[44px] sm:min-h-[32px] px-2 sm:px-1"
|
| 228 |
+
title="Reset Zoom"
|
| 229 |
+
>
|
| 230 |
+
<span className="hidden sm:inline">Reset</span>
|
| 231 |
+
<span className="sm:hidden">↺</span>
|
| 232 |
+
</button>
|
| 233 |
+
</div>
|
| 234 |
+
|
| 235 |
+
{/* Fullscreen Button */}
|
| 236 |
+
<button
|
| 237 |
+
onClick={() => setShowFullscreen(true)}
|
| 238 |
+
className="p-2 bg-primary-500 hover:bg-primary-600 text-white rounded-lg transition-colors min-h-[44px] sm:min-h-[40px] min-w-[44px] sm:min-w-[40px] flex items-center justify-center"
|
| 239 |
+
title="View in Fullscreen"
|
| 240 |
+
>
|
| 241 |
+
<Maximize2 size={16} />
|
| 242 |
+
</button>
|
| 243 |
+
</div>
|
| 244 |
+
</div>
|
| 245 |
+
|
| 246 |
+
<div
|
| 247 |
+
ref={containerRef}
|
| 248 |
+
className="overflow-auto bg-base-100 dark:bg-neutral-800 rounded-lg border border-neutral-100 dark:border-neutral-600 p-2 sm:p-4 cursor-grab active:cursor-grabbing touch-pan-x touch-pan-y"
|
| 249 |
+
style={{
|
| 250 |
+
maxHeight: '400px',
|
| 251 |
+
scrollbarWidth: 'thin',
|
| 252 |
+
scrollbarColor: '#cbd5e1 #f1f5f9',
|
| 253 |
+
WebkitOverflowScrolling: 'touch'
|
| 254 |
+
}}
|
| 255 |
+
onMouseDown={handleMouseDown}
|
| 256 |
+
onMouseMove={handleMouseMove}
|
| 257 |
+
onMouseUp={handleMouseUp}
|
| 258 |
+
onMouseLeave={handleMouseUp}
|
| 259 |
+
onTouchStart={(e) => {
|
| 260 |
+
const touch = e.touches[0];
|
| 261 |
+
handleMouseDown({ clientX: touch.clientX, clientY: touch.clientY });
|
| 262 |
+
}}
|
| 263 |
+
onTouchMove={(e) => {
|
| 264 |
+
const touch = e.touches[0];
|
| 265 |
+
handleMouseMove({ clientX: touch.clientX, clientY: touch.clientY });
|
| 266 |
+
}}
|
| 267 |
+
onTouchEnd={handleMouseUp}
|
| 268 |
+
>
|
| 269 |
+
<div
|
| 270 |
+
dangerouslySetInnerHTML={{ __html: circuitSvg }}
|
| 271 |
+
className="min-w-max transition-transform duration-200"
|
| 272 |
+
style={{
|
| 273 |
+
minWidth: 'fit-content',
|
| 274 |
+
display: 'inline-block',
|
| 275 |
+
transform: `scale(${zoom})`,
|
| 276 |
+
transformOrigin: 'top left'
|
| 277 |
+
}}
|
| 278 |
+
/>
|
| 279 |
+
</div>
|
| 280 |
+
|
| 281 |
+
{/* Navigation hints */}
|
| 282 |
+
<div className="mt-3 flex items-center justify-between text-xs text-neutral-400 dark:text-neutral-500">
|
| 283 |
+
<span className="flex items-center gap-1">
|
| 284 |
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
| 285 |
+
<path d="M9 11H1m22 0h-8M9 11l3-3m-3 3l3 3"/>
|
| 286 |
+
</svg>
|
| 287 |
+
<span className="hidden sm:inline">Drag to pan • Scroll to navigate</span>
|
| 288 |
+
<span className="sm:hidden">Drag or swipe to navigate</span>
|
| 289 |
+
</span>
|
| 290 |
+
|
| 291 |
+
<span className="flex items-center gap-1">
|
| 292 |
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
| 293 |
+
<circle cx="11" cy="11" r="8"/>
|
| 294 |
+
<path d="M21 21l-4.35-4.35"/>
|
| 295 |
+
</svg>
|
| 296 |
+
Use zoom controls for detail
|
| 297 |
+
</span>
|
| 298 |
+
</div>
|
| 299 |
+
</motion.div>
|
| 300 |
+
);
|
| 301 |
+
};
|
| 302 |
+
|
| 303 |
+
const visualizations = [
|
| 304 |
+
{
|
| 305 |
+
id: 'circuit',
|
| 306 |
+
label: 'Circuit',
|
| 307 |
+
icon: <Activity size={16} />,
|
| 308 |
+
component: <CircuitDisplay circuitSvg={result?.output?.circuit_svg} />,
|
| 309 |
+
available: !!result?.output?.circuit_svg,
|
| 310 |
+
},
|
| 311 |
+
{
|
| 312 |
+
id: 'probabilities',
|
| 313 |
+
label: 'Probabilities',
|
| 314 |
+
icon: <BarChart3 size={16} />,
|
| 315 |
+
component: <ProbabilityChart probabilities={result?.output?.probabilities} />,
|
| 316 |
+
available: !!result?.output?.probabilities,
|
| 317 |
+
},
|
| 318 |
+
{
|
| 319 |
+
id: 'bloch',
|
| 320 |
+
label: 'Bloch Sphere',
|
| 321 |
+
icon: <Zap size={16} />,
|
| 322 |
+
component: <BlochSphere stateVector={result?.output?.state_vector} />,
|
| 323 |
+
available: !!result?.output?.state_vector,
|
| 324 |
+
},
|
| 325 |
+
];
|
| 326 |
+
|
| 327 |
+
const availableVisualizations = visualizations.filter(viz => viz.available);
|
| 328 |
+
|
| 329 |
+
if (!result || availableVisualizations.length === 0) {
|
| 330 |
+
return (
|
| 331 |
+
<div className="flex flex-col items-center justify-center py-12 text-neutral-500">
|
| 332 |
+
<Info size={48} className="mb-4 opacity-50" />
|
| 333 |
+
<p className="text-lg font-medium">No visualization data available</p>
|
| 334 |
+
<p className="text-sm">Run a simulation to see results</p>
|
| 335 |
+
</div>
|
| 336 |
+
);
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
return (
|
| 340 |
+
<div className="space-y-6">
|
| 341 |
+
{availableVisualizations.length > 1 && (
|
| 342 |
+
<div className="flex gap-2 p-1 bg-base-200 rounded-lg">
|
| 343 |
+
{availableVisualizations.map((viz) => (
|
| 344 |
+
<button
|
| 345 |
+
key={viz.id}
|
| 346 |
+
onClick={() => setActiveVisualization(viz.id)}
|
| 347 |
+
className={`
|
| 348 |
+
flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-all
|
| 349 |
+
${activeVisualization === viz.id
|
| 350 |
+
? 'bg-base-100 text-neutral-900 dark:text-neutral-100 shadow-sm'
|
| 351 |
+
: 'text-neutral-600 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-100 hover:bg-base-100/50'
|
| 352 |
+
}
|
| 353 |
+
`}
|
| 354 |
+
>
|
| 355 |
+
{viz.icon}
|
| 356 |
+
{viz.label}
|
| 357 |
+
</button>
|
| 358 |
+
))}
|
| 359 |
+
</div>
|
| 360 |
+
)}
|
| 361 |
+
|
| 362 |
+
<AnimatePresence mode="wait">
|
| 363 |
+
{availableVisualizations.map((viz) => (
|
| 364 |
+
viz.id === activeVisualization && (
|
| 365 |
+
<motion.div
|
| 366 |
+
key={viz.id}
|
| 367 |
+
initial={{ opacity: 0, y: 20 }}
|
| 368 |
+
animate={{ opacity: 1, y: 0 }}
|
| 369 |
+
exit={{ opacity: 0, y: -20 }}
|
| 370 |
+
transition={{ duration: 0.3 }}
|
| 371 |
+
>
|
| 372 |
+
{viz.component}
|
| 373 |
+
</motion.div>
|
| 374 |
+
)
|
| 375 |
+
))}
|
| 376 |
+
</AnimatePresence>
|
| 377 |
+
|
| 378 |
+
{/* Fullscreen Circuit Viewer */}
|
| 379 |
+
<FullscreenCircuitViewer
|
| 380 |
+
circuitSvg={result?.output?.circuit_svg}
|
| 381 |
+
isOpen={showFullscreen}
|
| 382 |
+
onClose={() => setShowFullscreen(false)}
|
| 383 |
+
/>
|
| 384 |
+
</div>
|
| 385 |
+
);
|
| 386 |
+
};
|
| 387 |
+
|
| 388 |
+
QuantumVisualization.propTypes = {
|
| 389 |
+
result: PropTypes.object,
|
| 390 |
+
type: PropTypes.string,
|
| 391 |
+
};
|
| 392 |
+
|
| 393 |
+
export default QuantumVisualization;
|
frontend/src/components/plugin/SimpleTabs.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import PropTypes from 'prop-types';
|
| 4 |
+
|
| 5 |
+
const SimpleTabs = ({ tabs, defaultTab = null }) => {
|
| 6 |
+
const [activeTab, setActiveTab] = useState(defaultTab || (tabs.length > 0 ? tabs[0].id : ''));
|
| 7 |
+
|
| 8 |
+
return (
|
| 9 |
+
<div className="w-full">
|
| 10 |
+
{/* Tab Headers */}
|
| 11 |
+
<div className="flex bg-base-200 rounded-lg p-1 mb-4 gap-1 overflow-x-auto">
|
| 12 |
+
{tabs.map((tab) => (
|
| 13 |
+
<button
|
| 14 |
+
key={tab.id}
|
| 15 |
+
onClick={() => setActiveTab(tab.id)}
|
| 16 |
+
className={`
|
| 17 |
+
flex items-center gap-2 px-3 py-3 sm:px-4 sm:py-2 rounded-md border-0 text-sm font-medium cursor-pointer transition-all duration-200 whitespace-nowrap min-h-[44px] sm:min-h-[36px] flex-1 sm:flex-initial
|
| 18 |
+
${activeTab === tab.id
|
| 19 |
+
? 'bg-base-100 text-neutral-900 dark:text-neutral-100 shadow-sm'
|
| 20 |
+
: 'bg-transparent text-neutral-600 dark:text-neutral-300 hover:text-neutral-800 dark:hover:text-neutral-100'
|
| 21 |
+
}
|
| 22 |
+
`}
|
| 23 |
+
>
|
| 24 |
+
{tab.icon}
|
| 25 |
+
<span className="hidden sm:inline">
|
| 26 |
+
{tab.label}
|
| 27 |
+
</span>
|
| 28 |
+
<span className="sm:hidden">
|
| 29 |
+
{tab.label.split(' ')[0]}
|
| 30 |
+
</span>
|
| 31 |
+
</button>
|
| 32 |
+
))}
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
{/* Tab Content */}
|
| 36 |
+
<AnimatePresence mode="wait">
|
| 37 |
+
{tabs.map((tab) => (
|
| 38 |
+
activeTab === tab.id && (
|
| 39 |
+
<motion.div
|
| 40 |
+
key={tab.id}
|
| 41 |
+
initial={{ opacity: 0, y: 10 }}
|
| 42 |
+
animate={{ opacity: 1, y: 0 }}
|
| 43 |
+
exit={{ opacity: 0, y: -10 }}
|
| 44 |
+
transition={{ duration: 0.2 }}
|
| 45 |
+
>
|
| 46 |
+
{tab.content}
|
| 47 |
+
</motion.div>
|
| 48 |
+
)
|
| 49 |
+
))}
|
| 50 |
+
</AnimatePresence>
|
| 51 |
+
</div>
|
| 52 |
+
);
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
SimpleTabs.propTypes = {
|
| 56 |
+
tabs: PropTypes.arrayOf(
|
| 57 |
+
PropTypes.shape({
|
| 58 |
+
id: PropTypes.string.isRequired,
|
| 59 |
+
label: PropTypes.string.isRequired,
|
| 60 |
+
icon: PropTypes.node,
|
| 61 |
+
content: PropTypes.node.isRequired,
|
| 62 |
+
})
|
| 63 |
+
).isRequired,
|
| 64 |
+
defaultTab: PropTypes.string,
|
| 65 |
+
};
|
| 66 |
+
|
| 67 |
+
export default SimpleTabs;
|
frontend/src/design-system/components/Button.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { forwardRef } from 'react';
|
| 2 |
+
import { motion } from 'framer-motion';
|
| 3 |
+
import { Loader2 } from 'lucide-react';
|
| 4 |
+
import PropTypes from 'prop-types';
|
| 5 |
+
|
| 6 |
+
const Button = forwardRef(({
|
| 7 |
+
children,
|
| 8 |
+
variant = 'primary',
|
| 9 |
+
size = 'md',
|
| 10 |
+
loading = false,
|
| 11 |
+
disabled = false,
|
| 12 |
+
icon,
|
| 13 |
+
iconPosition = 'left',
|
| 14 |
+
fullWidth = false,
|
| 15 |
+
className = '',
|
| 16 |
+
onClick,
|
| 17 |
+
type = 'button',
|
| 18 |
+
...props
|
| 19 |
+
}, ref) => {
|
| 20 |
+
const baseClasses = 'inline-flex items-center justify-center font-medium rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
|
| 21 |
+
|
| 22 |
+
const variants = {
|
| 23 |
+
primary: 'bg-primary-500 hover:bg-primary-600 text-white focus:ring-primary-500 shadow-sm hover:shadow-md border-0',
|
| 24 |
+
secondary: 'bg-secondary-500 hover:bg-secondary-600 text-white focus:ring-secondary-500 shadow-sm hover:shadow-md border-0',
|
| 25 |
+
outline: 'border-2 border-primary-500 bg-transparent hover:bg-primary-500 text-primary-500 hover:text-white focus:ring-primary-500 shadow-sm',
|
| 26 |
+
ghost: 'hover:bg-primary-100 hover:text-primary-700 text-current focus:ring-primary-500 border-0',
|
| 27 |
+
danger: 'bg-error-500 hover:bg-error-600 text-white focus:ring-error-500 shadow-sm hover:shadow-md border-0',
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
const sizes = {
|
| 31 |
+
sm: 'px-3 py-2 text-sm gap-1.5 min-h-[44px] sm:py-1.5 sm:min-h-[36px]',
|
| 32 |
+
md: 'px-4 py-3 text-base gap-2 min-h-[48px] sm:py-2 sm:min-h-[40px]',
|
| 33 |
+
lg: 'px-6 py-4 text-lg gap-2.5 min-h-[52px] sm:py-3 sm:min-h-[44px]',
|
| 34 |
+
xl: 'px-8 py-5 text-xl gap-3 min-h-[56px] sm:py-4 sm:min-h-[48px]',
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
const classes = `
|
| 38 |
+
${baseClasses}
|
| 39 |
+
${variants[variant]}
|
| 40 |
+
${sizes[size]}
|
| 41 |
+
${fullWidth ? 'w-full' : ''}
|
| 42 |
+
${className}
|
| 43 |
+
`.trim();
|
| 44 |
+
|
| 45 |
+
const content = (
|
| 46 |
+
<>
|
| 47 |
+
{loading && <Loader2 size={16} className="animate-spin" />}
|
| 48 |
+
{!loading && icon && iconPosition === 'left' && icon}
|
| 49 |
+
<span>{children}</span>
|
| 50 |
+
{!loading && icon && iconPosition === 'right' && icon}
|
| 51 |
+
</>
|
| 52 |
+
);
|
| 53 |
+
|
| 54 |
+
return (
|
| 55 |
+
<motion.button
|
| 56 |
+
ref={ref}
|
| 57 |
+
type={type}
|
| 58 |
+
className={classes}
|
| 59 |
+
disabled={disabled || loading}
|
| 60 |
+
onClick={onClick}
|
| 61 |
+
whileHover={{ scale: disabled || loading ? 1 : 1.02 }}
|
| 62 |
+
whileTap={{ scale: disabled || loading ? 1 : 0.98 }}
|
| 63 |
+
transition={{ type: "spring", stiffness: 400, damping: 17 }}
|
| 64 |
+
{...props}
|
| 65 |
+
>
|
| 66 |
+
{content}
|
| 67 |
+
</motion.button>
|
| 68 |
+
);
|
| 69 |
+
});
|
| 70 |
+
|
| 71 |
+
Button.displayName = 'Button';
|
| 72 |
+
|
| 73 |
+
Button.propTypes = {
|
| 74 |
+
children: PropTypes.node.isRequired,
|
| 75 |
+
variant: PropTypes.oneOf(['primary', 'secondary', 'outline', 'ghost', 'danger']),
|
| 76 |
+
size: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
|
| 77 |
+
loading: PropTypes.bool,
|
| 78 |
+
disabled: PropTypes.bool,
|
| 79 |
+
icon: PropTypes.node,
|
| 80 |
+
iconPosition: PropTypes.oneOf(['left', 'right']),
|
| 81 |
+
fullWidth: PropTypes.bool,
|
| 82 |
+
className: PropTypes.string,
|
| 83 |
+
onClick: PropTypes.func,
|
| 84 |
+
type: PropTypes.oneOf(['button', 'submit', 'reset']),
|
| 85 |
+
};
|
| 86 |
+
|
| 87 |
+
export default Button;
|
frontend/src/design-system/components/Card.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { forwardRef } from 'react';
|
| 2 |
+
import { motion } from 'framer-motion';
|
| 3 |
+
import PropTypes from 'prop-types';
|
| 4 |
+
|
| 5 |
+
const Card = forwardRef(({
|
| 6 |
+
children,
|
| 7 |
+
variant = 'default',
|
| 8 |
+
padding = 'md',
|
| 9 |
+
hover = false,
|
| 10 |
+
className = '',
|
| 11 |
+
...props
|
| 12 |
+
}, ref) => {
|
| 13 |
+
const baseClasses = 'rounded-xl border transition-all duration-200';
|
| 14 |
+
|
| 15 |
+
const variants = {
|
| 16 |
+
default: 'border-neutral-200 dark:border-neutral-500 shadow-sm bg-base-100 dark:bg-base-200',
|
| 17 |
+
elevated: 'border-neutral-200 dark:border-neutral-500 shadow-md bg-base-100 dark:bg-base-200',
|
| 18 |
+
outlined: 'border-neutral-300 dark:border-neutral-400 shadow-none bg-base-100 dark:bg-base-200',
|
| 19 |
+
glass: 'border-white/20 bg-base-100/80 dark:bg-base-200/80 backdrop-blur-md shadow-lg',
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
const paddings = {
|
| 23 |
+
none: 'p-0',
|
| 24 |
+
sm: 'p-3',
|
| 25 |
+
md: 'p-4 sm:p-6',
|
| 26 |
+
lg: 'p-6 sm:p-8',
|
| 27 |
+
xl: 'p-8 sm:p-10',
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
const hoverClasses = hover ? 'hover:shadow-lg hover:-translate-y-1' : '';
|
| 31 |
+
|
| 32 |
+
const classes = `
|
| 33 |
+
${baseClasses}
|
| 34 |
+
${variants[variant]}
|
| 35 |
+
${paddings[padding]}
|
| 36 |
+
${hoverClasses}
|
| 37 |
+
${className}
|
| 38 |
+
`.trim();
|
| 39 |
+
|
| 40 |
+
const CardComponent = hover ? motion.div : 'div';
|
| 41 |
+
const motionProps = hover ? {
|
| 42 |
+
whileHover: { y: -4, transition: { type: "spring", stiffness: 300 } },
|
| 43 |
+
transition: { type: "spring", stiffness: 400, damping: 17 }
|
| 44 |
+
} : {};
|
| 45 |
+
|
| 46 |
+
return (
|
| 47 |
+
<CardComponent
|
| 48 |
+
ref={ref}
|
| 49 |
+
className={classes}
|
| 50 |
+
{...motionProps}
|
| 51 |
+
{...props}
|
| 52 |
+
>
|
| 53 |
+
{children}
|
| 54 |
+
</CardComponent>
|
| 55 |
+
);
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
Card.displayName = 'Card';
|
| 59 |
+
|
| 60 |
+
const CardHeader = ({ children, className = '', ...props }) => (
|
| 61 |
+
<div className={`pb-3 sm:pb-4 border-b border-neutral-200 dark:border-neutral-500 mb-4 sm:mb-6 ${className}`} {...props}>
|
| 62 |
+
{children}
|
| 63 |
+
</div>
|
| 64 |
+
);
|
| 65 |
+
|
| 66 |
+
const CardTitle = ({ children, className = '', ...props }) => (
|
| 67 |
+
<h3 className={`text-base sm:text-lg font-semibold text-neutral-900 dark:text-neutral-100 ${className}`} {...props}>
|
| 68 |
+
{children}
|
| 69 |
+
</h3>
|
| 70 |
+
);
|
| 71 |
+
|
| 72 |
+
const CardDescription = ({ children, className = '', ...props }) => (
|
| 73 |
+
<p className={`text-sm text-neutral-600 dark:text-neutral-200 mt-1 ${className}`} {...props}>
|
| 74 |
+
{children}
|
| 75 |
+
</p>
|
| 76 |
+
);
|
| 77 |
+
|
| 78 |
+
const CardContent = ({ children, className = '', ...props }) => (
|
| 79 |
+
<div className={className} {...props}>
|
| 80 |
+
{children}
|
| 81 |
+
</div>
|
| 82 |
+
);
|
| 83 |
+
|
| 84 |
+
const CardFooter = ({ children, className = '', ...props }) => (
|
| 85 |
+
<div className={`pt-4 border-t border-neutral-200 dark:border-neutral-500 mt-6 ${className}`} {...props}>
|
| 86 |
+
{children}
|
| 87 |
+
</div>
|
| 88 |
+
);
|
| 89 |
+
|
| 90 |
+
Card.propTypes = {
|
| 91 |
+
children: PropTypes.node.isRequired,
|
| 92 |
+
variant: PropTypes.oneOf(['default', 'elevated', 'outlined', 'glass']),
|
| 93 |
+
padding: PropTypes.oneOf(['none', 'sm', 'md', 'lg', 'xl']),
|
| 94 |
+
hover: PropTypes.bool,
|
| 95 |
+
className: PropTypes.string,
|
| 96 |
+
};
|
| 97 |
+
|
| 98 |
+
CardHeader.propTypes = {
|
| 99 |
+
children: PropTypes.node.isRequired,
|
| 100 |
+
className: PropTypes.string,
|
| 101 |
+
};
|
| 102 |
+
|
| 103 |
+
CardTitle.propTypes = {
|
| 104 |
+
children: PropTypes.node.isRequired,
|
| 105 |
+
className: PropTypes.string,
|
| 106 |
+
};
|
| 107 |
+
|
| 108 |
+
CardDescription.propTypes = {
|
| 109 |
+
children: PropTypes.node.isRequired,
|
| 110 |
+
className: PropTypes.string,
|
| 111 |
+
};
|
| 112 |
+
|
| 113 |
+
CardContent.propTypes = {
|
| 114 |
+
children: PropTypes.node.isRequired,
|
| 115 |
+
className: PropTypes.string,
|
| 116 |
+
};
|
| 117 |
+
|
| 118 |
+
CardFooter.propTypes = {
|
| 119 |
+
children: PropTypes.node.isRequired,
|
| 120 |
+
className: PropTypes.string,
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
Card.Header = CardHeader;
|
| 124 |
+
Card.Title = CardTitle;
|
| 125 |
+
Card.Description = CardDescription;
|
| 126 |
+
Card.Content = CardContent;
|
| 127 |
+
Card.Footer = CardFooter;
|
| 128 |
+
|
| 129 |
+
export default Card;
|
frontend/src/design-system/components/Input.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { forwardRef, useState } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import { AlertCircle, Eye, EyeOff } from 'lucide-react';
|
| 4 |
+
import PropTypes from 'prop-types';
|
| 5 |
+
|
| 6 |
+
const Input = forwardRef(({
|
| 7 |
+
label,
|
| 8 |
+
type = 'text',
|
| 9 |
+
placeholder,
|
| 10 |
+
value,
|
| 11 |
+
onChange,
|
| 12 |
+
onBlur,
|
| 13 |
+
onFocus,
|
| 14 |
+
error,
|
| 15 |
+
helperText,
|
| 16 |
+
required = false,
|
| 17 |
+
disabled = false,
|
| 18 |
+
size = 'md',
|
| 19 |
+
icon,
|
| 20 |
+
iconPosition = 'left',
|
| 21 |
+
className = '',
|
| 22 |
+
...props
|
| 23 |
+
}, ref) => {
|
| 24 |
+
const [focused, setFocused] = useState(false);
|
| 25 |
+
const [showPassword, setShowPassword] = useState(false);
|
| 26 |
+
|
| 27 |
+
const inputType = type === 'password' && showPassword ? 'text' : type;
|
| 28 |
+
|
| 29 |
+
const baseClasses = 'w-full rounded-lg border transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-1 text-base sm:text-sm md:text-base';
|
| 30 |
+
|
| 31 |
+
const sizes = {
|
| 32 |
+
sm: 'px-3 py-2 text-sm min-h-[40px]',
|
| 33 |
+
md: 'px-4 py-3 text-base min-h-[48px] sm:py-2.5 sm:min-h-[44px]',
|
| 34 |
+
lg: 'px-5 py-4 text-lg min-h-[52px] sm:py-3 sm:min-h-[48px]',
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
const stateClasses = error
|
| 38 |
+
? 'border-error-300 focus:border-error-500 focus:ring-error-500/20'
|
| 39 |
+
: focused
|
| 40 |
+
? 'border-primary-300 focus:border-primary-500 focus:ring-primary-500/20'
|
| 41 |
+
: 'border-neutral-300 hover:border-neutral-400';
|
| 42 |
+
|
| 43 |
+
const disabledClasses = disabled
|
| 44 |
+
? 'bg-base-200 text-neutral-500 cursor-not-allowed'
|
| 45 |
+
: 'bg-base-100 text-neutral-900 dark:text-neutral-100';
|
| 46 |
+
|
| 47 |
+
const iconClasses = icon ? (iconPosition === 'left' ? 'pl-10' : 'pr-10') : '';
|
| 48 |
+
const passwordToggleClasses = type === 'password' ? 'pr-10' : '';
|
| 49 |
+
|
| 50 |
+
const inputClasses = `
|
| 51 |
+
${baseClasses}
|
| 52 |
+
${sizes[size]}
|
| 53 |
+
${stateClasses}
|
| 54 |
+
${disabledClasses}
|
| 55 |
+
${iconClasses}
|
| 56 |
+
${passwordToggleClasses}
|
| 57 |
+
${className}
|
| 58 |
+
`.trim();
|
| 59 |
+
|
| 60 |
+
const handleFocus = (e) => {
|
| 61 |
+
setFocused(true);
|
| 62 |
+
onFocus?.(e);
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
const handleBlur = (e) => {
|
| 66 |
+
setFocused(false);
|
| 67 |
+
onBlur?.(e);
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
return (
|
| 71 |
+
<div className="space-y-2">
|
| 72 |
+
{label && (
|
| 73 |
+
<motion.label
|
| 74 |
+
className={`block text-sm font-medium ${error ? 'text-error-700' : 'text-neutral-700'}`}
|
| 75 |
+
animate={{ color: error ? '#b91c1c' : focused ? '#4f46e5' : '#374151' }}
|
| 76 |
+
transition={{ duration: 0.2 }}
|
| 77 |
+
>
|
| 78 |
+
{label}
|
| 79 |
+
{required && <span className="text-error-500 ml-1">*</span>}
|
| 80 |
+
</motion.label>
|
| 81 |
+
)}
|
| 82 |
+
|
| 83 |
+
<div className="relative">
|
| 84 |
+
{icon && (
|
| 85 |
+
<div className={`absolute inset-y-0 ${iconPosition === 'left' ? 'left-0 pl-3' : 'right-0 pr-3'} flex items-center pointer-events-none`}>
|
| 86 |
+
<div className={`${error ? 'text-error-400' : focused ? 'text-primary-500' : 'text-neutral-400'} transition-colors duration-200`}>
|
| 87 |
+
{icon}
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
)}
|
| 91 |
+
|
| 92 |
+
<input
|
| 93 |
+
ref={ref}
|
| 94 |
+
type={inputType}
|
| 95 |
+
placeholder={placeholder}
|
| 96 |
+
value={value}
|
| 97 |
+
onChange={onChange}
|
| 98 |
+
onFocus={handleFocus}
|
| 99 |
+
onBlur={handleBlur}
|
| 100 |
+
disabled={disabled}
|
| 101 |
+
required={required}
|
| 102 |
+
className={inputClasses}
|
| 103 |
+
{...props}
|
| 104 |
+
/>
|
| 105 |
+
|
| 106 |
+
{type === 'password' && (
|
| 107 |
+
<button
|
| 108 |
+
type="button"
|
| 109 |
+
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
| 110 |
+
onClick={() => setShowPassword(!showPassword)}
|
| 111 |
+
tabIndex={-1}
|
| 112 |
+
>
|
| 113 |
+
<motion.div
|
| 114 |
+
whileHover={{ scale: 1.1 }}
|
| 115 |
+
whileTap={{ scale: 0.9 }}
|
| 116 |
+
className="text-neutral-400 hover:text-neutral-600 transition-colors duration-200"
|
| 117 |
+
>
|
| 118 |
+
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
|
| 119 |
+
</motion.div>
|
| 120 |
+
</button>
|
| 121 |
+
)}
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
<AnimatePresence>
|
| 125 |
+
{(error || helperText) && (
|
| 126 |
+
<motion.div
|
| 127 |
+
initial={{ opacity: 0, y: -10 }}
|
| 128 |
+
animate={{ opacity: 1, y: 0 }}
|
| 129 |
+
exit={{ opacity: 0, y: -10 }}
|
| 130 |
+
transition={{ duration: 0.2 }}
|
| 131 |
+
className={`flex items-center gap-2 text-sm ${error ? 'text-error-600' : 'text-neutral-600'}`}
|
| 132 |
+
>
|
| 133 |
+
{error && <AlertCircle size={14} />}
|
| 134 |
+
<span>{error || helperText}</span>
|
| 135 |
+
</motion.div>
|
| 136 |
+
)}
|
| 137 |
+
</AnimatePresence>
|
| 138 |
+
</div>
|
| 139 |
+
);
|
| 140 |
+
});
|
| 141 |
+
|
| 142 |
+
Input.displayName = 'Input';
|
| 143 |
+
|
| 144 |
+
Input.propTypes = {
|
| 145 |
+
label: PropTypes.string,
|
| 146 |
+
type: PropTypes.string,
|
| 147 |
+
placeholder: PropTypes.string,
|
| 148 |
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
| 149 |
+
onChange: PropTypes.func,
|
| 150 |
+
onBlur: PropTypes.func,
|
| 151 |
+
onFocus: PropTypes.func,
|
| 152 |
+
error: PropTypes.string,
|
| 153 |
+
helperText: PropTypes.string,
|
| 154 |
+
required: PropTypes.bool,
|
| 155 |
+
disabled: PropTypes.bool,
|
| 156 |
+
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
| 157 |
+
icon: PropTypes.node,
|
| 158 |
+
iconPosition: PropTypes.oneOf(['left', 'right']),
|
| 159 |
+
className: PropTypes.string,
|
| 160 |
+
};
|
| 161 |
+
|
| 162 |
+
export default Input;
|