gaialive commited on
Commit
227c43a
·
verified ·
1 Parent(s): 1b788d1

Upload 170 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +71 -0
  2. .gitattributes +3 -0
  3. .github/workflows/fly-deploy.yml +18 -0
  4. .gitignore +101 -0
  5. .idea/.gitignore +10 -0
  6. .idea/QuantumFieldKit.iml +22 -0
  7. .idea/copilot.data.migration.agent.xml +6 -0
  8. .idea/copilot.data.migration.ask.xml +6 -0
  9. .idea/copilot.data.migration.edit.xml +6 -0
  10. .idea/inspectionProfiles/Project_Default.xml +6 -0
  11. .idea/inspectionProfiles/profiles_settings.xml +6 -0
  12. .idea/misc.xml +4 -0
  13. .idea/modules.xml +8 -0
  14. .idea/vcs.xml +6 -0
  15. .idea/workspace.xml +74 -0
  16. Dockerfile +66 -0
  17. HUGGINGFACE_DEPLOYMENT.md +166 -0
  18. LICENSE +21 -0
  19. README.md +246 -11
  20. README_HF.md +28 -0
  21. __init__.py +0 -0
  22. app.py +1453 -0
  23. docker-compose.yml +46 -0
  24. fly.toml +44 -0
  25. frontend/package-lock.json +0 -0
  26. frontend/package.json +53 -0
  27. frontend/postcss.config.js +8 -0
  28. frontend/public/data/glossary_terms.json +218 -0
  29. frontend/public/index.html +40 -0
  30. frontend/src/App.js +78 -0
  31. frontend/src/components/AccessibilityProvider.js +122 -0
  32. frontend/src/components/ErrorBoundary.js +125 -0
  33. frontend/src/components/FloatingActionButton.js +72 -0
  34. frontend/src/components/Footer.js +54 -0
  35. frontend/src/components/Navbar.js +221 -0
  36. frontend/src/components/QuantumParticles.js +116 -0
  37. frontend/src/components/SkeletonLoader.js +52 -0
  38. frontend/src/components/plugin/EnhancedPluginParameterForm.js +297 -0
  39. frontend/src/components/plugin/EnhancedPluginResultsPanel.js +333 -0
  40. frontend/src/components/plugin/FullscreenCircuitViewer.js +205 -0
  41. frontend/src/components/plugin/PluginExplanation.js +35 -0
  42. frontend/src/components/plugin/PluginLayout.css +27 -0
  43. frontend/src/components/plugin/PluginLayout.js +23 -0
  44. frontend/src/components/plugin/PluginParameterForm.js +87 -0
  45. frontend/src/components/plugin/PluginResultsPanel.js +82 -0
  46. frontend/src/components/plugin/QuantumVisualization.js +393 -0
  47. frontend/src/components/plugin/SimpleTabs.js +67 -0
  48. frontend/src/design-system/components/Button.js +87 -0
  49. frontend/src/design-system/components/Card.js +129 -0
  50. 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])&#10;from flask.cli import ScriptInfo, NoAppException&#10;for module in [&quot;main.py&quot;, &quot;wsgi.py&quot;, &quot;app.py&quot;]:&#10; try: locals().update(ScriptInfo(app_import_path=module, create_app=None).load_app().make_shell_context()); print(&quot;\nFlask App: %s&quot; % app.import_name); break&#10; 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])&#10;from flask.cli import ScriptInfo, NoAppException&#10;for module in [&quot;main.py&quot;, &quot;wsgi.py&quot;, &quot;app.py&quot;]:&#10; try: locals().update(ScriptInfo(app_import_path=module, create_app=None).load_app().make_shell_context()); print(&quot;\nFlask App: %s&quot; % app.import_name); break&#10; 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
+ &quot;associatedIndex&quot;: 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
+ &quot;keyToString&quot;: {
37
+ &quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
38
+ &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
39
+ &quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
40
+ &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
41
+ &quot;git-widget-placeholder&quot;: &quot;main&quot;,
42
+ &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
43
+ &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
44
+ &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
45
+ &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
46
+ &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
47
+ &quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
48
+ &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
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
- title: MophongLT
3
- emoji: 📚
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- license: cc-by-nc-nd-4.0
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GXS - QuantumNexus
2
+ [![Website](https://img.shields.io/website?url=https%3A%2F%2Fquantumfieldkit.com&logo=flyio&label=quantumfieldkit.com&style=for-the-badge)](https://quantumfieldkit.com) [![License](https://img.shields.io/Facebook/license/ChuyenDoiXanh?style=for-the-badge)](LICENSE) [![Python Version](https://img.shields.io/badge/Python-3.8+-blue?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/) [![Flask](https://img.shields.io/badge/Flask-2.2.3%2B-lightgrey?style=for-the-badge&logo=flask&logoColor=white)](https://flask.palletsprojects.com/) [![Cirq](https://img.shields.io/badge/Cirq-1.1.0%2B-purple?style=for-the-badge&logo=google&logoColor=white)](https://quantumai.google/cirq) [![Docker](https://img.shields.io/badge/Docker-Containerized-blue?style=for-the-badge&logo=docker&logoColor=white)](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
+ ![System Architecture](static/img/image-1.png)
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>&copy; {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;