Gaston895 commited on
Commit
54c96d3
·
verified ·
1 Parent(s): 5b46341

Upload 13 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Bio Human Atlas Viewer - Docker Ignore
2
+ # Gaston Software Solutions Tec, Uganda
3
+ # AR Dashboard Specific Exclusions
4
+
5
+ # Git
6
+ .git
7
+ .gitignore
8
+ .gitattributes
9
+
10
+ # Documentation
11
+ README.md
12
+ CHANGELOG.md
13
+ LICENSE
14
+ *.md
15
+
16
+ # Development files
17
+ .vscode/
18
+ .idea/
19
+ *.swp
20
+ *.swo
21
+ *~
22
+
23
+ # Python
24
+ __pycache__/
25
+ *.py[cod]
26
+ *$py.class
27
+ *.so
28
+ .Python
29
+ build/
30
+ develop-eggs/
31
+ dist/
32
+ downloads/
33
+ eggs/
34
+ .eggs/
35
+ lib/
36
+ lib64/
37
+ parts/
38
+ sdist/
39
+ var/
40
+ wheels/
41
+ *.egg-info/
42
+ .installed.cfg
43
+ *.egg
44
+ MANIFEST
45
+
46
+ # Virtual environments
47
+ venv/
48
+ env/
49
+ ENV/
50
+ env.bak/
51
+ venv.bak/
52
+ .venv/
53
+
54
+ # Testing
55
+ .pytest_cache/
56
+ .coverage
57
+ htmlcov/
58
+ .tox/
59
+ .cache
60
+ nosetests.xml
61
+ coverage.xml
62
+ *.cover
63
+ .hypothesis/
64
+ test-*.js
65
+ test-*.py
66
+ test_*.py
67
+ *test*.html
68
+
69
+ # Jupyter Notebook
70
+ .ipynb_checkpoints
71
+
72
+ # pyenv
73
+ .python-version
74
+
75
+ # Environment variables
76
+ .env
77
+ .env.local
78
+ .env.development.local
79
+ .env.test.local
80
+ .env.production.local
81
+
82
+ # Node.js
83
+ node_modules/
84
+ npm-debug.log*
85
+ yarn-debug.log*
86
+ yarn-error.log*
87
+
88
+ # Logs
89
+ logs/
90
+ *.log
91
+
92
+ # Runtime data
93
+ pids/
94
+ *.pid
95
+ *.seed
96
+ *.pid.lock
97
+
98
+ # Coverage directory used by tools like istanbul
99
+ coverage/
100
+
101
+ # nyc test coverage
102
+ .nyc_output
103
+
104
+ # Dependency directories
105
+ jspm_packages/
106
+
107
+ # Optional npm cache directory
108
+ .npm
109
+
110
+ # Optional REPL history
111
+ .node_repl_history
112
+
113
+ # Output of 'npm pack'
114
+ *.tgz
115
+
116
+ # Yarn Integrity file
117
+ .yarn-integrity
118
+
119
+ # parcel-bundler cache (https://parceljs.org/)
120
+ .cache
121
+ .parcel-cache
122
+
123
+ # next.js build output
124
+ .next
125
+
126
+ # nuxt.js build output
127
+ .nuxt
128
+
129
+ # vuepress build output
130
+ .vuepress/dist
131
+
132
+ # Serverless directories
133
+ .serverless
134
+
135
+ # FuseBox cache
136
+ .fusebox/
137
+
138
+ # DynamoDB Local files
139
+ .dynamodb/
140
+
141
+ # OS generated files
142
+ .DS_Store
143
+ .DS_Store?
144
+ ._*
145
+ .Spotlight-V100
146
+ .Trashes
147
+ ehthumbs.db
148
+ Thumbs.db
149
+
150
+ # Temporary files
151
+ tmp/
152
+ temp/
153
+ *.tmp
154
+ *.temp
155
+
156
+ # IDE files
157
+ *.sublime-project
158
+ *.sublime-workspace
159
+
160
+ # Local development
161
+ local/
162
+ .local/
163
+
164
+ # Backup files
165
+ *.bak
166
+ *.backup
167
+
168
+ # Database files
169
+ *.db
170
+ *.sqlite
171
+ *.sqlite3
172
+
173
+ # Compiled files
174
+ *.com
175
+ *.class
176
+ *.dll
177
+ *.exe
178
+ *.o
179
+ *.so
180
+
181
+ # Archives
182
+ *.7z
183
+ *.dmg
184
+ *.gz
185
+ *.iso
186
+ *.jar
187
+ *.rar
188
+ *.tar
189
+ *.zip
190
+
191
+ # Media files (if not needed)
192
+ *.mp4
193
+ *.avi
194
+ *.mov
195
+ *.wmv
196
+ *.flv
197
+ *.webm
198
+
199
+ # Large data files
200
+ *.csv
201
+ *.json.gz
202
+ *.parquet
203
+
204
+ # Development scripts
205
+ scripts/dev/
206
+ scripts/test/
207
+ dev-tools/
208
+
209
+ # Documentation build
210
+ docs/_build/
211
+ site/
212
+
213
+ # AR Dashboard specific exclusions
214
+ debug-*.js
215
+ debug-*.html
216
+ debug-*.py
217
+ analyze-*.js
218
+ analyze-*.py
219
+ fix-*.js
220
+ fix-*.py
221
+ deploy-*.bat
222
+ deploy-*.py
223
+ setup-*.py
224
+ install-*.bat
225
+ start-*.bat
226
+ launch-*.bat
227
+ monitor-*.js
228
+ verify-*.js
229
+ check-*.js
230
+ check-*.py
231
+ test_*.js
232
+ Frontend/
233
+ oabrowser/
234
+ 3Dslicer/
235
+ Bio/
236
+ conductor_*/
237
+ spillover_deploy/
238
+ war/
239
+ econ/
240
+ deep/
241
+ deepseek-ai-*/
242
+ DeepSeek-OCR/
243
+ aegis_*/
244
+ langgraph/
245
+ tec/
246
+ tt/
247
+ chatterbox-turbo-demo/
248
+ ChatUI/
249
+ fastsdcpu_temp/
250
+ hf_space_temp/
251
+ New folder/
252
+ output/
253
+ pdb_cache/
254
+ venv_*/
255
+ __pycache__/
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz 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
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz 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
Dockerfile ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Install system dependencies
7
+ RUN apt-get update && apt-get install -y \
8
+ gcc \
9
+ g++ \
10
+ curl \
11
+ && rm -rf /var/lib/apt/lists/* \
12
+ && apt-get clean
13
+
14
+ # Copy requirements first for better caching
15
+ COPY requirements.txt .
16
+
17
+ # Install Python dependencies
18
+ RUN pip install --no-cache-dir --upgrade pip && \
19
+ pip install --no-cache-dir -r requirements.txt
20
+
21
+ # Copy application files
22
+ COPY app.py .
23
+ COPY ar_dashboard.html .
24
+
25
+ # Create necessary directories and move template file
26
+ RUN mkdir -p templates static/css static/js static/images logs && \
27
+ mv ar_dashboard.html templates/ar_dashboard.html
28
+
29
+ # Create non-root user for security
30
+ RUN useradd --create-home --shell /bin/bash app && \
31
+ chown -R app:app /app
32
+ USER app
33
+
34
+ # Expose port
35
+ EXPOSE 7860
36
+
37
+ # Set environment variables
38
+ ENV FLASK_APP=app.py
39
+ ENV FLASK_ENV=production
40
+ ENV PORT=7860
41
+ ENV PYTHONPATH=/app
42
+ ENV PYTHONUNBUFFERED=1
43
+
44
+ # Health check
45
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
46
+ CMD curl -f http://localhost:7860/health || exit 1
47
+
48
+ # Run the application
49
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "2", "--timeout", "120", "--access-logfile", "-", "--error-logfile", "-", "app:app"]
README.md CHANGED
@@ -1,10 +1,49 @@
1
- ---
2
- title: AR
3
- emoji: 🐠
4
- colorFrom: yellow
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: AEGIS AR Dashboard
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # AEGIS BIO DIGITAL LAB 10 - AR Portal Dashboard
12
+
13
+ Welcome to the AEGIS Bio Digital Lab 10 Augmented Reality Portal Dashboard.
14
+
15
+ ## Features
16
+
17
+ - 🌐 Unified portal access for all AEGIS components
18
+ - 📊 Real-time system status monitoring
19
+ - 🎨 Modern, responsive interface
20
+ - 🔧 RESTful API endpoints
21
+ - 🏥 Health monitoring and diagnostics
22
+
23
+ ## Portals
24
+
25
+ 1. **Portal Control** - Main control center
26
+ 2. **SciBERT Analysis** - Scientific analysis tools
27
+ 3. **Conductor** - System orchestration
28
+ 4. **Economic Modal** - Economic modeling
29
+ 5. **War Modal** - Strategic analysis
30
+ 6. **Disease Modal** - Disease tracking
31
+ 7. **DeepSeek** - AI deep analysis
32
+ 8. **Image Modal** - Image processing
33
+ 9. **Drug Development** - Pharmaceutical research
34
+ 10. **Visual Kinetic** - Visual analytics
35
+
36
+ ## API Endpoints
37
+
38
+ - `GET /` - Main dashboard interface
39
+ - `GET /health` - Health check
40
+ - `GET /api/status` - System status
41
+ - `POST /api/update-urls` - Update portal URLs
42
+
43
+ ## Usage
44
+
45
+ The dashboard automatically loads and displays all available portals. Click on any portal card to access the respective system component.
46
+
47
+ ---
48
+
49
+ **© 2025 AEGIS Systems - Advanced Biological Research Platform**
app.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ from flask_cors import CORS
3
+ from datetime import datetime
4
+ import os
5
+
6
+ app = Flask(__name__)
7
+ CORS(app)
8
+
9
+ # In-memory store for the frontend base URL.
10
+ # The Cloudflare Worker calls /api/update-urls whenever the frontend URL changes.
11
+ current_urls = {
12
+ 'base_url': '',
13
+ 'last_updated': '',
14
+ 'portals': []
15
+ }
16
+
17
+ # All 10 AEGIS analysis windows + their metadata
18
+ WINDOW_DEFINITIONS = [
19
+ {
20
+ 'window': 2,
21
+ 'name': 'Tech Analysis',
22
+ 'path': '/window2',
23
+ 'icon': 'cpu',
24
+ 'color': 'blue',
25
+ 'description': 'TEC model — technology threat scores for any year',
26
+ },
27
+ {
28
+ 'window': 3,
29
+ 'name': 'Conductor Synthesis',
30
+ 'path': '/window3',
31
+ 'icon': 'git-branch',
32
+ 'color': 'purple',
33
+ 'description': 'Groq synthesis of tech threats into strategic insights',
34
+ },
35
+ {
36
+ 'window': 4,
37
+ 'name': 'Economic Analysis',
38
+ 'path': '/window4',
39
+ 'icon': 'trending-up',
40
+ 'color': 'yellow',
41
+ 'description': '12 economic impact indicators from conductor results',
42
+ },
43
+ {
44
+ 'window': 5,
45
+ 'name': 'War Analysis',
46
+ 'path': '/window5',
47
+ 'icon': 'shield',
48
+ 'color': 'red',
49
+ 'description': 'Dual AI + live conflict data — war risk predictions',
50
+ },
51
+ {
52
+ 'window': 6,
53
+ 'name': 'Climate Analysis',
54
+ 'path': '/window6',
55
+ 'icon': 'cloud',
56
+ 'color': 'cyan',
57
+ 'description': 'World Bank CCKP — climate-conflict nexus assessment',
58
+ },
59
+ {
60
+ 'window': 7,
61
+ 'name': 'Disease Prediction',
62
+ 'path': '/window7',
63
+ 'icon': 'activity',
64
+ 'color': 'orange',
65
+ 'description': 'AI biosecurity risk — top 3 disease outbreak predictions',
66
+ },
67
+ {
68
+ 'window': 8,
69
+ 'name': 'Drug Discovery',
70
+ 'path': '/window8',
71
+ 'icon': 'zap',
72
+ 'color': 'green',
73
+ 'description': '4-phase pipeline — SMILES, DiffDock, Vina, manufacturing',
74
+ },
75
+ {
76
+ 'window': 9,
77
+ 'name': 'Visual Disease',
78
+ 'path': '/window9',
79
+ 'icon': 'image',
80
+ 'color': 'pink',
81
+ 'description': 'AI-generated pathogen morphology visualizations',
82
+ },
83
+ {
84
+ 'window': 10,
85
+ 'name': 'Visual Kinetic',
86
+ 'path': '/window10',
87
+ 'icon': 'eye',
88
+ 'color': 'indigo',
89
+ 'description': '3D PK/PD simulation with live cardiac monitoring',
90
+ },
91
+ {
92
+ 'window': 11,
93
+ 'name': 'AR Portal',
94
+ 'path': '/window11',
95
+ 'icon': 'scan',
96
+ 'color': 'teal',
97
+ 'description': 'Augmented Reality — scan QR to view all windows remotely',
98
+ },
99
+ ]
100
+
101
+
102
+ def build_portals(base_url: str) -> list:
103
+ """Build portal list from window definitions + a given base URL."""
104
+ return [
105
+ {
106
+ **w,
107
+ 'url': f"{base_url.rstrip('/')}{w['path']}",
108
+ 'qr_url': f"{base_url.rstrip('/')}{w['path']}",
109
+ }
110
+ for w in WINDOW_DEFINITIONS
111
+ ]
112
+
113
+
114
+ # ── Routes ────────────────────────────────────────────────────────────────────
115
+
116
+ @app.route('/')
117
+ def index():
118
+ """AR Portal Dashboard — shows QR grid for all windows."""
119
+ return render_template(
120
+ 'ar_dashboard.html',
121
+ base_url=current_urls.get('base_url', ''),
122
+ last_updated=current_urls.get('last_updated', ''),
123
+ portals=current_urls.get('portals', []),
124
+ )
125
+
126
+
127
+ @app.route('/ping', methods=['GET'])
128
+ def ping():
129
+ """UptimeRobot keep-alive — returns 200 instantly, no heavy work."""
130
+ return jsonify({'status': 'ok', 'message': 'AR Space is alive'}), 200
131
+
132
+
133
+ @app.route('/health')
134
+ def health():
135
+ """Health check."""
136
+ return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()})
137
+
138
+
139
+ @app.route('/api/status')
140
+ def status():
141
+ """Current AR system status — called by the worker."""
142
+ return jsonify({
143
+ 'status': 'online',
144
+ 'base_url': current_urls.get('base_url', ''),
145
+ 'last_updated': current_urls.get('last_updated', ''),
146
+ 'portals_count': len(current_urls.get('portals', [])),
147
+ 'total_windows': len(WINDOW_DEFINITIONS),
148
+ })
149
+
150
+
151
+ @app.route('/api/update-urls', methods=['POST'])
152
+ def update_urls():
153
+ """
154
+ Called by the Cloudflare Worker (or directly) to register the frontend
155
+ base URL. Once set, /api/qr-data returns ready-to-use QR links.
156
+ """
157
+ try:
158
+ data = request.get_json()
159
+ if not data or 'base_url' not in data:
160
+ return jsonify({'error': 'base_url is required'}), 400
161
+
162
+ base_url = data['base_url'].rstrip('/')
163
+ portals = build_portals(base_url)
164
+ timestamp = datetime.now().isoformat()
165
+
166
+ current_urls.update({
167
+ 'base_url': base_url,
168
+ 'last_updated': timestamp,
169
+ 'portals': portals,
170
+ })
171
+
172
+ return jsonify({
173
+ 'success': True,
174
+ 'message': 'URLs updated successfully',
175
+ 'base_url': base_url,
176
+ 'timestamp': timestamp,
177
+ 'portals_count': len(portals),
178
+ })
179
+
180
+ except Exception as e:
181
+ return jsonify({'error': str(e)}), 500
182
+
183
+
184
+ @app.route('/api/qr-data', methods=['GET'])
185
+ def qr_data():
186
+ """
187
+ Returns all window URLs ready for QR-code generation.
188
+ Window 11 frontend calls this via the worker proxy to render the QR grid.
189
+ Optional ?window=N to get a single window's data.
190
+ """
191
+ window_param = request.args.get('window')
192
+ base_url = current_urls.get('base_url', '')
193
+
194
+ if not base_url:
195
+ return jsonify({
196
+ 'error': 'base_url not configured yet',
197
+ 'hint': 'POST {"base_url": "https://your-app.com"} to /api/update-urls',
198
+ 'portals': [],
199
+ }), 503
200
+
201
+ portals = current_urls.get('portals') or build_portals(base_url)
202
+
203
+ if window_param:
204
+ try:
205
+ wnum = int(window_param)
206
+ portal = next((p for p in portals if p['window'] == wnum), None)
207
+ if not portal:
208
+ return jsonify({'error': f'Window {wnum} not found'}), 404
209
+ return jsonify({'success': True, 'portal': portal})
210
+ except ValueError:
211
+ return jsonify({'error': 'window must be an integer'}), 400
212
+
213
+ return jsonify({
214
+ 'success': True,
215
+ 'base_url': base_url,
216
+ 'last_updated': current_urls.get('last_updated', ''),
217
+ 'portals': portals,
218
+ 'total': len(portals),
219
+ })
220
+
221
+
222
+ @app.route('/api/windows', methods=['GET'])
223
+ def windows():
224
+ """Static list of all window definitions (no base_url needed)."""
225
+ return jsonify({
226
+ 'success': True,
227
+ 'windows': WINDOW_DEFINITIONS,
228
+ 'total': len(WINDOW_DEFINITIONS),
229
+ })
230
+
231
+
232
+ if __name__ == '__main__':
233
+ port = int(os.environ.get('PORT', 7860))
234
+ app.run(host='0.0.0.0', port=port, debug=False)
ar_dashboard.html ADDED
@@ -0,0 +1,844 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AEGIS BIO DIGITAL LAB 10 - AR Portal</title>
7
+ <meta name="description" content="AEGIS Bio Digital Lab 10 Augmented Reality Portal Dashboard">
8
+
9
+ <!-- Lucide Icons CDN -->
10
+ <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
11
+
12
+ <style>
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ body {
20
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
21
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
22
+ color: white;
23
+ min-height: 100vh;
24
+ overflow-x: hidden;
25
+ }
26
+
27
+ .header {
28
+ background: rgba(0, 0, 0, 0.4);
29
+ padding: 1.5rem 2rem;
30
+ backdrop-filter: blur(15px);
31
+ border-bottom: 1px solid rgba(0, 255, 255, 0.3);
32
+ position: sticky;
33
+ top: 0;
34
+ z-index: 100;
35
+ }
36
+
37
+ .header h1 {
38
+ font-size: 2.5rem;
39
+ margin-bottom: 0.5rem;
40
+ background: linear-gradient(45deg, #00FFFF, #0080FF);
41
+ -webkit-background-clip: text;
42
+ -webkit-text-fill-color: transparent;
43
+ background-clip: text;
44
+ text-align: center;
45
+ animation: pulse 2s infinite;
46
+ }
47
+
48
+ .header .subtitle {
49
+ font-size: 1.2rem;
50
+ text-align: center;
51
+ opacity: 0.9;
52
+ color: #E3F2FD;
53
+ }
54
+
55
+ .status-bar {
56
+ display: flex;
57
+ justify-content: center;
58
+ align-items: center;
59
+ gap: 2rem;
60
+ margin-top: 1rem;
61
+ flex-wrap: wrap;
62
+ }
63
+
64
+ .status-item {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 0.5rem;
68
+ font-size: 0.9rem;
69
+ opacity: 0.8;
70
+ }
71
+
72
+ .status-online {
73
+ color: #4CAF50;
74
+ }
75
+
76
+ .status-offline {
77
+ color: #f44336;
78
+ }
79
+
80
+ .main-content {
81
+ padding: 2rem;
82
+ max-width: 1400px;
83
+ margin: 0 auto;
84
+ }
85
+
86
+ .hero-section {
87
+ text-align: center;
88
+ margin-bottom: 3rem;
89
+ }
90
+
91
+ .hero-section h2 {
92
+ font-size: 2rem;
93
+ margin-bottom: 1rem;
94
+ color: #00FFFF;
95
+ }
96
+
97
+ .hero-section p {
98
+ font-size: 1.1rem;
99
+ opacity: 0.9;
100
+ max-width: 600px;
101
+ margin: 0 auto;
102
+ line-height: 1.6;
103
+ }
104
+
105
+ .portals-grid {
106
+ display: grid;
107
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
108
+ gap: 1.5rem;
109
+ margin-bottom: 3rem;
110
+ }
111
+
112
+ .portal-card {
113
+ background: rgba(0, 0, 0, 0.4);
114
+ border: 1px solid rgba(0, 255, 255, 0.3);
115
+ border-radius: 15px;
116
+ padding: 1.5rem;
117
+ text-align: center;
118
+ backdrop-filter: blur(10px);
119
+ transition: all 0.3s ease;
120
+ cursor: pointer;
121
+ position: relative;
122
+ overflow: hidden;
123
+ /* Prevent layout shifts during updates */
124
+ min-height: 200px;
125
+ display: flex;
126
+ flex-direction: column;
127
+ justify-content: space-between;
128
+ }
129
+
130
+ .portal-card::before {
131
+ content: '';
132
+ position: absolute;
133
+ top: 0;
134
+ left: -100%;
135
+ width: 100%;
136
+ height: 100%;
137
+ background: linear-gradient(90deg, transparent, rgba(0, 255, 255, 0.1), transparent);
138
+ transition: left 0.5s;
139
+ }
140
+
141
+ .portal-card:hover::before {
142
+ left: 100%;
143
+ }
144
+
145
+ .portal-card:hover {
146
+ transform: translateY(-5px);
147
+ border-color: rgba(0, 255, 255, 0.6);
148
+ box-shadow: 0 10px 30px rgba(0, 255, 255, 0.2);
149
+ }
150
+
151
+ /* Smooth transitions for status updates */
152
+ .status-item {
153
+ transition: all 0.3s ease;
154
+ }
155
+
156
+ .status-online {
157
+ color: #4CAF50;
158
+ transition: color 0.3s ease;
159
+ }
160
+
161
+ .status-offline {
162
+ color: #f44336;
163
+ transition: color 0.3s ease;
164
+ }
165
+
166
+ /* Prevent text jumping during updates */
167
+ .status-item span {
168
+ min-width: 150px;
169
+ display: inline-block;
170
+ text-align: left;
171
+ }
172
+
173
+ /* Smooth icon transitions */
174
+ [data-lucide] {
175
+ transition: all 0.2s ease;
176
+ }
177
+
178
+ .portal-icon {
179
+ width: 60px;
180
+ height: 60px;
181
+ margin: 0 auto 1rem;
182
+ border-radius: 12px;
183
+ display: flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ position: relative;
187
+ z-index: 1;
188
+ }
189
+
190
+ .portal-card h3 {
191
+ font-size: 1.2rem;
192
+ margin-bottom: 0.5rem;
193
+ color: white;
194
+ position: relative;
195
+ z-index: 1;
196
+ }
197
+
198
+ .portal-card p {
199
+ font-size: 0.9rem;
200
+ opacity: 0.8;
201
+ margin-bottom: 1rem;
202
+ position: relative;
203
+ z-index: 1;
204
+ }
205
+
206
+ .launch-btn {
207
+ display: inline-flex;
208
+ align-items: center;
209
+ gap: 0.5rem;
210
+ padding: 0.5rem 1rem;
211
+ background: rgba(0, 255, 255, 0.2);
212
+ border: 1px solid rgba(0, 255, 255, 0.4);
213
+ border-radius: 25px;
214
+ color: #00FFFF;
215
+ text-decoration: none;
216
+ font-size: 0.9rem;
217
+ font-weight: 500;
218
+ transition: all 0.3s ease;
219
+ position: relative;
220
+ z-index: 1;
221
+ }
222
+
223
+ .launch-btn:hover {
224
+ background: rgba(0, 255, 255, 0.3);
225
+ color: white;
226
+ }
227
+
228
+ /* Color variations for different portals */
229
+ .color-blue { background: linear-gradient(135deg, #2196F3, #1976D2); }
230
+ .color-green { background: linear-gradient(135deg, #4CAF50, #388E3C); }
231
+ .color-purple { background: linear-gradient(135deg, #9C27B0, #7B1FA2); }
232
+ .color-yellow { background: linear-gradient(135deg, #FF9800, #F57C00); }
233
+ .color-red { background: linear-gradient(135deg, #f44336, #D32F2F); }
234
+ .color-orange { background: linear-gradient(135deg, #FF5722, #E64A19); }
235
+ .color-indigo { background: linear-gradient(135deg, #3F51B5, #303F9F); }
236
+ .color-pink { background: linear-gradient(135deg, #E91E63, #C2185B); }
237
+ .color-teal { background: linear-gradient(135deg, #009688, #00796B); }
238
+ .color-cyan { background: linear-gradient(135deg, #00BCD4, #0097A7); }
239
+ .color-emerald { background: linear-gradient(135deg, #4CAF50, #2E7D32); }
240
+
241
+ .qr-section {
242
+ background: rgba(0, 0, 0, 0.3);
243
+ border: 1px solid rgba(0, 255, 255, 0.3);
244
+ border-radius: 15px;
245
+ padding: 2rem;
246
+ text-align: center;
247
+ margin-bottom: 2rem;
248
+ }
249
+
250
+ .qr-section h3 {
251
+ color: #00FFFF;
252
+ margin-bottom: 1rem;
253
+ font-size: 1.5rem;
254
+ }
255
+
256
+ .qr-code-container {
257
+ margin: 1rem 0;
258
+ }
259
+
260
+ .qr-code-container img {
261
+ max-width: 200px;
262
+ border-radius: 10px;
263
+ border: 2px solid rgba(0, 255, 255, 0.3);
264
+ }
265
+
266
+ .api-section {
267
+ background: rgba(0, 0, 0, 0.3);
268
+ border: 1px solid rgba(0, 255, 255, 0.3);
269
+ border-radius: 15px;
270
+ padding: 2rem;
271
+ margin-bottom: 2rem;
272
+ }
273
+
274
+ .api-section h3 {
275
+ color: #00FFFF;
276
+ margin-bottom: 1rem;
277
+ font-size: 1.5rem;
278
+ }
279
+
280
+ .api-endpoints {
281
+ display: grid;
282
+ gap: 0.5rem;
283
+ font-family: 'Courier New', monospace;
284
+ font-size: 0.9rem;
285
+ }
286
+
287
+ .api-endpoint {
288
+ background: rgba(0, 0, 0, 0.4);
289
+ padding: 0.75rem;
290
+ border-radius: 8px;
291
+ border-left: 3px solid #00FFFF;
292
+ }
293
+
294
+ .footer {
295
+ background: rgba(0, 0, 0, 0.4);
296
+ padding: 2rem;
297
+ text-align: center;
298
+ border-top: 1px solid rgba(0, 255, 255, 0.3);
299
+ margin-top: 2rem;
300
+ }
301
+
302
+ .footer p {
303
+ opacity: 0.7;
304
+ margin-bottom: 0.5rem;
305
+ }
306
+
307
+ @keyframes pulse {
308
+ 0%, 100% { opacity: 1; }
309
+ 50% { opacity: 0.8; }
310
+ }
311
+
312
+ @keyframes spin {
313
+ from { transform: rotate(0deg); }
314
+ to { transform: rotate(360deg); }
315
+ }
316
+
317
+ .animate-spin {
318
+ animation: spin 1s linear infinite;
319
+ }
320
+
321
+ .error-notification {
322
+ animation: slideIn 0.3s ease-out;
323
+ }
324
+
325
+ @keyframes slideIn {
326
+ from {
327
+ transform: translateX(100%);
328
+ opacity: 0;
329
+ }
330
+ to {
331
+ transform: translateX(0);
332
+ opacity: 1;
333
+ }
334
+ }
335
+
336
+ .portal-card {
337
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
338
+ }
339
+
340
+ .portal-card:active {
341
+ transform: translateY(-2px) scale(0.98);
342
+ }
343
+
344
+ @media (max-width: 768px) {
345
+ .header h1 {
346
+ font-size: 1.8rem;
347
+ }
348
+
349
+ .portals-grid {
350
+ grid-template-columns: 1fr;
351
+ gap: 1rem;
352
+ }
353
+
354
+ .status-bar {
355
+ flex-direction: column;
356
+ gap: 1rem;
357
+ }
358
+ }
359
+ </style>
360
+ </head>
361
+ <body>
362
+ <header class="header">
363
+ <h1>AEGIS BIO DIGITAL LAB 10</h1>
364
+ <div class="subtitle">AUGMENTED REALITY PORTAL DASHBOARD</div>
365
+
366
+ <div class="status-bar">
367
+ <div class="status-item">
368
+ <i data-lucide="globe"></i>
369
+ <span>10 Active Portals</span>
370
+ </div>
371
+ <div class="status-item" id="connection-status">
372
+ <i data-lucide="wifi" id="status-icon"></i>
373
+ <span id="status-text">Checking Connection...</span>
374
+ </div>
375
+ <div class="status-item">
376
+ <i data-lucide="clock"></i>
377
+ <span id="current-time"></span>
378
+ </div>
379
+ </div>
380
+ </header>
381
+
382
+ <main class="main-content">
383
+ <section class="hero-section">
384
+ <h2>🚀 Portal Access Dashboard</h2>
385
+ <p>
386
+ Welcome to the AEGIS Bio Digital Lab 10 AR Portal.
387
+ Access all system components through this unified interface.
388
+ Each portal provides specialized functionality for biological research and analysis.
389
+ </p>
390
+ </section>
391
+
392
+ <section class="portals-grid" id="portals-container">
393
+ <!-- Portals will be dynamically loaded here -->
394
+ </section>
395
+ </main>
396
+
397
+ <footer class="footer">
398
+ <p>🔬 Advanced Biological Research Platform</p>
399
+ <p>🚀 Powered by AEGIS Technology</p>
400
+ <p>&copy; 2025 AEGIS Systems - All Rights Reserved</p>
401
+ </footer>
402
+
403
+ <!-- Server-side data injection -->
404
+ <div id="server-data"
405
+ data-portals='{% if portals %}{{ portals | tojson | safe }}{% else %}[]{% endif %}'
406
+ data-base-url='{% if base_url %}{{ base_url }}{% else %}{% endif %}'
407
+ data-last-updated='{% if last_updated %}{{ last_updated }}{% else %}{% endif %}'
408
+ style="display: none;">
409
+ </div>
410
+
411
+ <script>
412
+ // Get server-side data from HTML data attributes
413
+ const serverDataElement = document.getElementById('server-data');
414
+ window.portalsData = JSON.parse(serverDataElement.getAttribute('data-portals') || '[]');
415
+ window.baseUrl = serverDataElement.getAttribute('data-base-url') || '';
416
+ window.lastUpdated = serverDataElement.getAttribute('data-last-updated') || '';
417
+ </script>
418
+
419
+ <script>
420
+ // Initialize Lucide icons
421
+ lucide.createIcons();
422
+
423
+ // Portal data with icons
424
+ const defaultPortals = [
425
+ { name: 'AEGIS BIO DIGITAL LAB 10 TIME TRAVEL SYSTEM', url: '#', icon: 'settings', color: 'blue', description: 'Main control center' },
426
+ { name: 'AEGIS BIO DIGITAL LAB 10 CROSS-CONTINENTAL CONTAGION', url: '#', icon: 'bar-chart-3', color: 'green', description: 'Scientific analysis tools' },
427
+ { name: 'AEGIS BIO DIGITAL LAB 10 CONDUCTOR', url: '#', icon: 'cpu', color: 'purple', description: 'System orchestration' },
428
+ { name: 'AEGIS Bio Digital Lab 10 ECONOMIC PROCESSOR', url: '#', icon: 'dollar-sign', color: 'yellow', description: 'Economic modeling' },
429
+ { name: 'AEGIS BIO DIGITAL LAB 10 WAR ANALYST EXPERT', url: '#', icon: 'shield', color: 'red', description: 'Strategic analysis' },
430
+ { name: 'AEGIS BIO DIGITAL LAB 10 DISEASE ANALYST EXPERT SYSTEM', url: '#', icon: 'bug', color: 'orange', description: 'Disease tracking' },
431
+ { name: 'AEGIS-BIO LAB-10 VANGUARD', url: '#', icon: 'brain', color: 'indigo', description: 'AI deep analysis' },
432
+ { name: 'AEGIS-BIO LAB-10 3D IMAGE LAB ASSISTANT', url: '#', icon: 'image', color: 'pink', description: 'Image processing' },
433
+ { name: 'AEGIS Bio Digital Lab 10 - Drug Development System', url: '#', icon: 'pill', color: 'teal', description: 'Pharmaceutical research' },
434
+ { name: 'AEGIS BIO DIGITAL LAB 10 Trail - Visual Kinetic System', url: '#', icon: 'eye', color: 'cyan', description: 'Visual analytics' }
435
+ ];
436
+
437
+ // Update time
438
+ function updateTime() {
439
+ const now = new Date();
440
+ document.getElementById('current-time').textContent = now.toLocaleString();
441
+ }
442
+
443
+ // Global state for smooth updates
444
+ let currentPortalsData = null;
445
+ let isInitialLoad = true;
446
+ let updateTimeout = null;
447
+
448
+ // Debounced update function to prevent rapid successive updates
449
+ function debouncedUpdate(callback, delay = 500) {
450
+ if (updateTimeout) {
451
+ clearTimeout(updateTimeout);
452
+ }
453
+ updateTimeout = setTimeout(callback, delay);
454
+ }
455
+
456
+ // Load portals from API or use defaults with smooth updates
457
+ async function loadPortals(forceUpdate = false) {
458
+ const container = document.getElementById('portals-container');
459
+
460
+ // Only show loading state on initial load
461
+ if (isInitialLoad) {
462
+ container.innerHTML = `
463
+ <div style="grid-column: 1 / -1; text-align: center; padding: 2rem;">
464
+ <i data-lucide="loader-2" class="animate-spin" style="width: 48px; height: 48px; margin-bottom: 1rem;"></i>
465
+ <p>Loading portals...</p>
466
+ </div>
467
+ `;
468
+ lucide.createIcons();
469
+ }
470
+
471
+ let portalsData = null;
472
+
473
+ // Try to load from status API first
474
+ try {
475
+ const response = await fetch('/api/status');
476
+ if (response.ok) {
477
+ const data = await response.json();
478
+ if (data.base_url && data.base_url !== '') {
479
+ // Generate portals from base URL
480
+ portalsData = [
481
+ { name: 'AEGIS BIO DIGITAL LAB 10 TIME TRAVEL SYSTEM', url: `${data.base_url}/portal`, icon: 'settings', color: 'blue', description: 'Main control center' },
482
+ { name: 'AEGIS BIO DIGITAL LAB 10 CROSS-CONTINENTAL CONTAGION', url: `${data.base_url}/analysis`, icon: 'bar-chart-3', color: 'green', description: 'Scientific analysis tools' },
483
+ { name: 'AEGIS BIO DIGITAL LAB 10 CONDUCTOR', url: `${data.base_url}/conductor`, icon: 'cpu', color: 'purple', description: 'System orchestration' },
484
+ { name: 'AEGIS Bio Digital Lab 10 ECONOMIC PROCESSOR', url: `${data.base_url}/economic`, icon: 'dollar-sign', color: 'yellow', description: 'Economic modeling' },
485
+ { name: 'AEGIS BIO DIGITAL LAB 10 WAR ANALYST EXPERT', url: `${data.base_url}/war`, icon: 'shield', color: 'red', description: 'Strategic analysis' },
486
+ { name: 'AEGIS BIO DIGITAL LAB 10 DISEASE ANALYST EXPERT SYSTEM', url: `${data.base_url}/disease`, icon: 'bug', color: 'orange', description: 'Disease tracking' },
487
+ { name: 'AEGIS-BIO LAB-10 VANGUARD', url: `${data.base_url}/deepseek`, icon: 'brain', color: 'indigo', description: 'AI deep analysis' },
488
+ { name: 'AEGIS-BIO LAB-10 3D IMAGE LAB ASSISTANT', url: `${data.base_url}/image`, icon: 'image', color: 'pink', description: 'Image processing' },
489
+ { name: 'AEGIS Bio Digital Lab 10 - Drug Development System', url: `${data.base_url}/drug`, icon: 'pill', color: 'teal', description: 'Pharmaceutical research' },
490
+ { name: 'AEGIS BIO DIGITAL LAB 10 Trail - Visual Kinetic System', url: `${data.base_url}/visual-kinetic`, icon: 'eye', color: 'cyan', description: 'Visual analytics' }
491
+ ];
492
+ }
493
+ }
494
+ } catch (error) {
495
+ console.warn('Failed to load portals from API:', error);
496
+ }
497
+
498
+ // Check if portals data is available from server-side injection
499
+ if (!portalsData && window.portalsData && window.portalsData.length > 0) {
500
+ portalsData = window.portalsData;
501
+ }
502
+
503
+ // Fallback to default portals if no data available
504
+ const portalsToUse = portalsData || defaultPortals;
505
+
506
+ // Check if data has actually changed to avoid unnecessary updates
507
+ const newDataString = JSON.stringify(portalsToUse);
508
+ const currentDataString = JSON.stringify(currentPortalsData);
509
+
510
+ if (!forceUpdate && !isInitialLoad && newDataString === currentDataString) {
511
+ // No changes, skip update to prevent flickering
512
+ return;
513
+ }
514
+
515
+ // Store current data
516
+ currentPortalsData = portalsToUse;
517
+
518
+ // Smooth update: only update URLs if structure is the same
519
+ if (!isInitialLoad && portalsToUse.length === (currentPortalsData?.length || 0)) {
520
+ updatePortalURLsSilently(portalsToUse);
521
+ } else {
522
+ // Full rebuild only when necessary
523
+ renderPortalsComplete(portalsToUse);
524
+ }
525
+
526
+ isInitialLoad = false;
527
+ }
528
+
529
+ // Silently update only the URLs without rebuilding the entire DOM
530
+ function updatePortalURLsSilently(portalsData) {
531
+ const portalCards = document.querySelectorAll('.portal-card');
532
+
533
+ portalsData.forEach((portal, index) => {
534
+ const card = portalCards[index];
535
+ if (card) {
536
+ // Update onclick handler
537
+ card.onclick = () => openPortal(portal.url, portal.name);
538
+
539
+ // Update launch button
540
+ const launchBtn = card.querySelector('.launch-btn');
541
+ if (launchBtn) {
542
+ launchBtn.href = portal.url;
543
+ launchBtn.onclick = (e) => {
544
+ e.stopPropagation();
545
+ openPortal(portal.url, portal.name);
546
+ };
547
+ }
548
+ }
549
+ });
550
+ }
551
+
552
+ // Complete portal rendering (used for initial load or structure changes)
553
+ function renderPortalsComplete(portalsData) {
554
+ const container = document.getElementById('portals-container');
555
+
556
+ container.innerHTML = portalsData.map(portal => `
557
+ <div class="portal-card" onclick="openPortal('${portal.url}', '${portal.name}')">
558
+ <div class="portal-icon color-${portal.color}">
559
+ <i data-lucide="${portal.icon}"></i>
560
+ </div>
561
+ <h3>${portal.name}</h3>
562
+ <p>${portal.description}</p>
563
+ <a href="${portal.url}" class="launch-btn" onclick="event.stopPropagation(); openPortal('${portal.url}', '${portal.name}')">
564
+ <span>Launch Portal</span>
565
+ <i data-lucide="external-link"></i>
566
+ </a>
567
+ </div>
568
+ `).join('');
569
+
570
+ lucide.createIcons();
571
+ }
572
+
573
+ // Open portal
574
+ function openPortal(url) {
575
+ if (url && url !== '#') {
576
+ window.open(url, '_blank', 'noopener,noreferrer');
577
+ }
578
+ }
579
+
580
+ // Global state for status updates
581
+ let lastStatusData = null;
582
+
583
+ // Check system status with silent updates
584
+ async function checkStatus() {
585
+ try {
586
+ const response = await fetch('/api/status');
587
+ const data = await response.json();
588
+
589
+ // Check if status data has actually changed
590
+ const newStatusString = JSON.stringify(data);
591
+ const lastStatusString = JSON.stringify(lastStatusData);
592
+
593
+ if (newStatusString === lastStatusString) {
594
+ // No changes, skip DOM updates to prevent flickering
595
+ return;
596
+ }
597
+
598
+ // Store new status data
599
+ lastStatusData = data;
600
+
601
+ const statusIcon = document.getElementById('status-icon');
602
+ const statusText = document.getElementById('status-text');
603
+ const connectionStatus = document.getElementById('connection-status');
604
+
605
+ if (data.status === 'online') {
606
+ statusIcon.setAttribute('data-lucide', 'wifi');
607
+ if (data.base_url && data.base_url !== '') {
608
+ statusText.textContent = `Live URLs Active (${data.portals_count}/${data.total_portals})`;
609
+ connectionStatus.className = 'status-item status-online';
610
+ } else {
611
+ statusText.textContent = 'Online - Waiting for URLs';
612
+ connectionStatus.className = 'status-item status-online';
613
+ }
614
+ } else {
615
+ statusIcon.setAttribute('data-lucide', 'wifi-off');
616
+ statusText.textContent = 'System Offline';
617
+ connectionStatus.className = 'status-item status-offline';
618
+ }
619
+
620
+ // Update last updated time if available
621
+ if (data.last_updated) {
622
+ const lastUpdated = new Date(data.last_updated);
623
+ const timeAgo = getTimeAgo(lastUpdated);
624
+ const timeElement = document.querySelector('.status-item:last-child span');
625
+ if (timeElement) {
626
+ timeElement.textContent = `Updated ${timeAgo}`;
627
+ }
628
+ }
629
+
630
+ // Only recreate icons if status actually changed
631
+ lucide.createIcons();
632
+
633
+ } catch (error) {
634
+ // Only update UI if error state is different from current state
635
+ if (!lastStatusData || lastStatusData.status !== 'error') {
636
+ const statusIcon = document.getElementById('status-icon');
637
+ const statusText = document.getElementById('status-text');
638
+ const connectionStatus = document.getElementById('connection-status');
639
+
640
+ statusIcon.setAttribute('data-lucide', 'wifi-off');
641
+ statusText.textContent = 'Connection Error';
642
+ connectionStatus.className = 'status-item status-offline';
643
+
644
+ lastStatusData = { status: 'error' };
645
+ lucide.createIcons();
646
+ }
647
+ }
648
+ }
649
+
650
+ // Helper function to get time ago
651
+ function getTimeAgo(date) {
652
+ const now = new Date();
653
+ const diffMs = now - date;
654
+ const diffSecs = Math.floor(diffMs / 1000);
655
+ const diffMins = Math.floor(diffSecs / 60);
656
+ const diffHours = Math.floor(diffMins / 60);
657
+
658
+ if (diffSecs < 60) return `${diffSecs}s ago`;
659
+ if (diffMins < 60) return `${diffMins}m ago`;
660
+ if (diffHours < 24) return `${diffHours}h ago`;
661
+ return date.toLocaleDateString();
662
+ }
663
+
664
+ // Update portal URLs function with silent update indicator
665
+ async function updatePortalUrls(newUrls) {
666
+ try {
667
+ // Add subtle update indicator without disrupting layout
668
+ const statusBar = document.querySelector('.status-bar');
669
+ const originalOpacity = statusBar.style.opacity;
670
+ statusBar.style.opacity = '0.7';
671
+
672
+ const response = await fetch('/api/update-urls', {
673
+ method: 'POST',
674
+ headers: {
675
+ 'Content-Type': 'application/json',
676
+ },
677
+ body: JSON.stringify({ urls: newUrls })
678
+ });
679
+
680
+ const data = await response.json();
681
+
682
+ // Restore opacity
683
+ setTimeout(() => {
684
+ statusBar.style.opacity = originalOpacity || '1';
685
+ }, 300);
686
+
687
+ if (data.success) {
688
+ // Use debounced update to prevent flickering
689
+ debouncedUpdate(() => {
690
+ loadPortals();
691
+ }, 100);
692
+ return true;
693
+ } else {
694
+ console.error('Failed to update URLs:', data.error);
695
+ return false;
696
+ }
697
+ } catch (error) {
698
+ console.error('Error updating URLs:', error);
699
+ return false;
700
+ }
701
+ }
702
+
703
+ // Initialize dashboard with optimized updates
704
+ function initDashboard() {
705
+ updateTime();
706
+ loadPortals(true); // Force initial load
707
+ checkStatus();
708
+
709
+ // Update time every second
710
+ setInterval(updateTime, 1000);
711
+
712
+ // Check status and reload portals every 10 seconds with silent updates
713
+ setInterval(() => {
714
+ checkStatus();
715
+ loadPortals(); // Silent update - only updates if data changed
716
+ }, 10000);
717
+ }
718
+
719
+ // Handle keyboard shortcuts
720
+ document.addEventListener('keydown', function(event) {
721
+ // Ctrl/Cmd + R to refresh portals
722
+ if ((event.ctrlKey || event.metaKey) && event.key === 'r') {
723
+ event.preventDefault();
724
+ loadPortals();
725
+ checkStatus();
726
+ }
727
+ });
728
+
729
+ // Handle window focus/blur for status updates with debouncing
730
+ window.addEventListener('focus', function() {
731
+ debouncedUpdate(() => {
732
+ checkStatus();
733
+ loadPortals();
734
+ }, 200);
735
+ });
736
+
737
+ // Initialize when DOM is loaded
738
+ document.addEventListener('DOMContentLoaded', initDashboard);
739
+
740
+ // Handle portal card animations
741
+ document.addEventListener('mouseover', function(event) {
742
+ if (event.target.closest('.portal-card')) {
743
+ event.target.closest('.portal-card').style.transform = 'translateY(-5px) scale(1.02)';
744
+ }
745
+ });
746
+
747
+ document.addEventListener('mouseout', function(event) {
748
+ if (event.target.closest('.portal-card')) {
749
+ event.target.closest('.portal-card').style.transform = 'translateY(0) scale(1)';
750
+ }
751
+ });
752
+
753
+ // Add loading states
754
+ function showLoading(elementId) {
755
+ const element = document.getElementById(elementId);
756
+ if (element) {
757
+ element.innerHTML = '<i data-lucide="loader-2" class="animate-spin"></i> Loading...';
758
+ lucide.createIcons();
759
+ }
760
+ }
761
+
762
+ function hideLoading(elementId, originalContent) {
763
+ const element = document.getElementById(elementId);
764
+ if (element) {
765
+ element.innerHTML = originalContent;
766
+ lucide.createIcons();
767
+ }
768
+ }
769
+
770
+ // Add error handling for portal launches
771
+ function handlePortalError(portalName, error) {
772
+ console.error(`Error launching ${portalName}:`, error);
773
+
774
+ // Show user-friendly error message
775
+ const errorDiv = document.createElement('div');
776
+ errorDiv.className = 'error-notification';
777
+ errorDiv.style.cssText = `
778
+ position: fixed;
779
+ top: 20px;
780
+ right: 20px;
781
+ background: rgba(244, 67, 54, 0.9);
782
+ color: white;
783
+ padding: 1rem;
784
+ border-radius: 8px;
785
+ z-index: 1000;
786
+ max-width: 300px;
787
+ backdrop-filter: blur(10px);
788
+ `;
789
+ errorDiv.innerHTML = `
790
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
791
+ <i data-lucide="alert-circle"></i>
792
+ <span>Failed to launch ${portalName}</span>
793
+ </div>
794
+ `;
795
+
796
+ document.body.appendChild(errorDiv);
797
+ lucide.createIcons();
798
+
799
+ // Remove error message after 5 seconds
800
+ setTimeout(() => {
801
+ if (errorDiv.parentNode) {
802
+ errorDiv.parentNode.removeChild(errorDiv);
803
+ }
804
+ }, 5000);
805
+ }
806
+
807
+ // Enhanced portal opening with error handling
808
+ function openPortalSafe(url, name) {
809
+ try {
810
+ if (url && url !== '#') {
811
+ const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
812
+
813
+ // Check if popup was blocked
814
+ if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
815
+ throw new Error('Popup blocked or failed to open');
816
+ }
817
+ } else {
818
+ throw new Error('Invalid URL');
819
+ }
820
+ } catch (error) {
821
+ handlePortalError(name, error);
822
+ }
823
+ }
824
+
825
+ // Update the openPortal function to use the safer version
826
+ function openPortal(url, name = 'Portal') {
827
+ openPortalSafe(url, name);
828
+ }
829
+
830
+ // Add service worker registration for offline support
831
+ if ('serviceWorker' in navigator) {
832
+ window.addEventListener('load', function() {
833
+ navigator.serviceWorker.register('/sw.js')
834
+ .then(function(registration) {
835
+ console.log('ServiceWorker registration successful');
836
+ })
837
+ .catch(function(error) {
838
+ console.log('ServiceWorker registration failed');
839
+ });
840
+ });
841
+ }
842
+ </script>
843
+ </body>
844
+ </html>
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Flask==2.3.3
2
+ Flask-CORS==4.0.0
3
+ gunicorn==21.2.0
4
+ huggingface_hub>=0.19.0
5
+ python-dotenv==1.0.0
6
+ requests==2.31.0
templates/api-test.html ADDED
File without changes
templates/ar_dashboard.html ADDED
@@ -0,0 +1,662 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AEGIS BIO DIGITAL LAB 10 - AR Portal</title>
7
+ <meta name="description" content="AEGIS Bio Digital Lab 10 Augmented Reality Portal Dashboard">
8
+
9
+ <!-- Lucide Icons CDN -->
10
+ <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
11
+
12
+ <style>
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ body {
20
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
21
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
22
+ color: white;
23
+ min-height: 100vh;
24
+ overflow-x: hidden;
25
+ }
26
+
27
+ .header {
28
+ background: rgba(0, 0, 0, 0.4);
29
+ padding: 1.5rem 2rem;
30
+ backdrop-filter: blur(15px);
31
+ border-bottom: 1px solid rgba(0, 255, 255, 0.3);
32
+ position: sticky;
33
+ top: 0;
34
+ z-index: 100;
35
+ }
36
+
37
+ .header h1 {
38
+ font-size: 2.5rem;
39
+ margin-bottom: 0.5rem;
40
+ background: linear-gradient(45deg, #00FFFF, #0080FF);
41
+ -webkit-background-clip: text;
42
+ -webkit-text-fill-color: transparent;
43
+ background-clip: text;
44
+ text-align: center;
45
+ animation: pulse 2s infinite;
46
+ }
47
+
48
+ .header .subtitle {
49
+ font-size: 1.2rem;
50
+ text-align: center;
51
+ opacity: 0.9;
52
+ color: #E3F2FD;
53
+ }
54
+
55
+ .status-bar {
56
+ display: flex;
57
+ justify-content: center;
58
+ align-items: center;
59
+ gap: 2rem;
60
+ margin-top: 1rem;
61
+ flex-wrap: wrap;
62
+ }
63
+
64
+ .status-item {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 0.5rem;
68
+ font-size: 0.9rem;
69
+ opacity: 0.8;
70
+ }
71
+
72
+ .status-online {
73
+ color: #4CAF50;
74
+ }
75
+
76
+ .status-offline {
77
+ color: #f44336;
78
+ }
79
+
80
+ .main-content {
81
+ padding: 2rem;
82
+ max-width: 1400px;
83
+ margin: 0 auto;
84
+ }
85
+
86
+ .hero-section {
87
+ text-align: center;
88
+ margin-bottom: 3rem;
89
+ }
90
+
91
+ .hero-section h2 {
92
+ font-size: 2rem;
93
+ margin-bottom: 1rem;
94
+ color: #00FFFF;
95
+ }
96
+
97
+ .hero-section p {
98
+ font-size: 1.1rem;
99
+ opacity: 0.9;
100
+ max-width: 600px;
101
+ margin: 0 auto;
102
+ line-height: 1.6;
103
+ }
104
+
105
+ .portals-grid {
106
+ display: grid;
107
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
108
+ gap: 1.5rem;
109
+ margin-bottom: 3rem;
110
+ }
111
+
112
+ .portal-card {
113
+ background: rgba(0, 0, 0, 0.4);
114
+ border: 1px solid rgba(0, 255, 255, 0.3);
115
+ border-radius: 15px;
116
+ padding: 1.5rem;
117
+ text-align: center;
118
+ backdrop-filter: blur(10px);
119
+ transition: all 0.3s ease;
120
+ cursor: pointer;
121
+ position: relative;
122
+ overflow: hidden;
123
+ }
124
+
125
+ .portal-card::before {
126
+ content: '';
127
+ position: absolute;
128
+ top: 0;
129
+ left: -100%;
130
+ width: 100%;
131
+ height: 100%;
132
+ background: linear-gradient(90deg, transparent, rgba(0, 255, 255, 0.1), transparent);
133
+ transition: left 0.5s;
134
+ }
135
+
136
+ .portal-card:hover::before {
137
+ left: 100%;
138
+ }
139
+
140
+ .portal-card:hover {
141
+ transform: translateY(-5px);
142
+ border-color: rgba(0, 255, 255, 0.6);
143
+ box-shadow: 0 10px 30px rgba(0, 255, 255, 0.2);
144
+ }
145
+
146
+ .portal-icon {
147
+ width: 60px;
148
+ height: 60px;
149
+ margin: 0 auto 1rem;
150
+ border-radius: 12px;
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ position: relative;
155
+ z-index: 1;
156
+ }
157
+
158
+ .portal-card h3 {
159
+ font-size: 1.2rem;
160
+ margin-bottom: 0.5rem;
161
+ color: white;
162
+ position: relative;
163
+ z-index: 1;
164
+ }
165
+
166
+ .portal-card p {
167
+ font-size: 0.9rem;
168
+ opacity: 0.8;
169
+ margin-bottom: 1rem;
170
+ position: relative;
171
+ z-index: 1;
172
+ }
173
+
174
+ .launch-btn {
175
+ display: inline-flex;
176
+ align-items: center;
177
+ gap: 0.5rem;
178
+ padding: 0.5rem 1rem;
179
+ background: rgba(0, 255, 255, 0.2);
180
+ border: 1px solid rgba(0, 255, 255, 0.4);
181
+ border-radius: 25px;
182
+ color: #00FFFF;
183
+ text-decoration: none;
184
+ font-size: 0.9rem;
185
+ font-weight: 500;
186
+ transition: all 0.3s ease;
187
+ position: relative;
188
+ z-index: 1;
189
+ }
190
+
191
+ .launch-btn:hover {
192
+ background: rgba(0, 255, 255, 0.3);
193
+ color: white;
194
+ }
195
+
196
+ /* Color variations for different portals */
197
+ .color-blue { background: linear-gradient(135deg, #2196F3, #1976D2); }
198
+ .color-green { background: linear-gradient(135deg, #4CAF50, #388E3C); }
199
+ .color-purple { background: linear-gradient(135deg, #9C27B0, #7B1FA2); }
200
+ .color-yellow { background: linear-gradient(135deg, #FF9800, #F57C00); }
201
+ .color-red { background: linear-gradient(135deg, #f44336, #D32F2F); }
202
+ .color-orange { background: linear-gradient(135deg, #FF5722, #E64A19); }
203
+ .color-indigo { background: linear-gradient(135deg, #3F51B5, #303F9F); }
204
+ .color-pink { background: linear-gradient(135deg, #E91E63, #C2185B); }
205
+ .color-teal { background: linear-gradient(135deg, #009688, #00796B); }
206
+ .color-cyan { background: linear-gradient(135deg, #00BCD4, #0097A7); }
207
+ .color-emerald { background: linear-gradient(135deg, #4CAF50, #2E7D32); }
208
+
209
+ .qr-section {
210
+ background: rgba(0, 0, 0, 0.3);
211
+ border: 1px solid rgba(0, 255, 255, 0.3);
212
+ border-radius: 15px;
213
+ padding: 2rem;
214
+ text-align: center;
215
+ margin-bottom: 2rem;
216
+ }
217
+
218
+ .qr-section h3 {
219
+ color: #00FFFF;
220
+ margin-bottom: 1rem;
221
+ font-size: 1.5rem;
222
+ }
223
+
224
+ .qr-code-container {
225
+ margin: 1rem 0;
226
+ }
227
+
228
+ .qr-code-container img {
229
+ max-width: 200px;
230
+ border-radius: 10px;
231
+ border: 2px solid rgba(0, 255, 255, 0.3);
232
+ }
233
+
234
+ .api-section {
235
+ background: rgba(0, 0, 0, 0.3);
236
+ border: 1px solid rgba(0, 255, 255, 0.3);
237
+ border-radius: 15px;
238
+ padding: 2rem;
239
+ margin-bottom: 2rem;
240
+ }
241
+
242
+ .api-section h3 {
243
+ color: #00FFFF;
244
+ margin-bottom: 1rem;
245
+ font-size: 1.5rem;
246
+ }
247
+
248
+ .api-endpoints {
249
+ display: grid;
250
+ gap: 0.5rem;
251
+ font-family: 'Courier New', monospace;
252
+ font-size: 0.9rem;
253
+ }
254
+
255
+ .api-endpoint {
256
+ background: rgba(0, 0, 0, 0.4);
257
+ padding: 0.75rem;
258
+ border-radius: 8px;
259
+ border-left: 3px solid #00FFFF;
260
+ }
261
+
262
+ .footer {
263
+ background: rgba(0, 0, 0, 0.4);
264
+ padding: 2rem;
265
+ text-align: center;
266
+ border-top: 1px solid rgba(0, 255, 255, 0.3);
267
+ margin-top: 2rem;
268
+ }
269
+
270
+ .footer p {
271
+ opacity: 0.7;
272
+ margin-bottom: 0.5rem;
273
+ }
274
+
275
+ @keyframes pulse {
276
+ 0%, 100% { opacity: 1; }
277
+ 50% { opacity: 0.8; }
278
+ }
279
+
280
+ @keyframes spin {
281
+ from { transform: rotate(0deg); }
282
+ to { transform: rotate(360deg); }
283
+ }
284
+
285
+ .animate-spin {
286
+ animation: spin 1s linear infinite;
287
+ }
288
+
289
+ .error-notification {
290
+ animation: slideIn 0.3s ease-out;
291
+ }
292
+
293
+ @keyframes slideIn {
294
+ from {
295
+ transform: translateX(100%);
296
+ opacity: 0;
297
+ }
298
+ to {
299
+ transform: translateX(0);
300
+ opacity: 1;
301
+ }
302
+ }
303
+
304
+ .portal-card {
305
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
306
+ }
307
+
308
+ .portal-card:active {
309
+ transform: translateY(-2px) scale(0.98);
310
+ }
311
+
312
+ @media (max-width: 768px) {
313
+ .header h1 {
314
+ font-size: 1.8rem;
315
+ }
316
+
317
+ .portals-grid {
318
+ grid-template-columns: 1fr;
319
+ gap: 1rem;
320
+ }
321
+
322
+ .status-bar {
323
+ flex-direction: column;
324
+ gap: 1rem;
325
+ }
326
+ }
327
+ </style>
328
+ </head>
329
+ <body>
330
+ <header class="header">
331
+ <h1>AEGIS BIO DIGITAL LAB 10</h1>
332
+ <div class="subtitle">AUGMENTED REALITY PORTAL DASHBOARD</div>
333
+
334
+ <div class="status-bar">
335
+ <div class="status-item">
336
+ <i data-lucide="globe"></i>
337
+ <span>10 Active Portals</span>
338
+ </div>
339
+ <div class="status-item" id="connection-status">
340
+ <i data-lucide="wifi" id="status-icon"></i>
341
+ <span id="status-text">Checking Connection...</span>
342
+ </div>
343
+ <div class="status-item">
344
+ <i data-lucide="clock"></i>
345
+ <span id="current-time"></span>
346
+ </div>
347
+ </div>
348
+ </header>
349
+
350
+ <main class="main-content">
351
+ <section class="hero-section">
352
+ <h2>🚀 Portal Access Dashboard</h2>
353
+ <p>
354
+ Welcome to the AEGIS Bio Digital Lab 10 AR Portal.
355
+ Access all system components through this unified interface.
356
+ Each portal provides specialized functionality for biological research and analysis.
357
+ </p>
358
+ </section>
359
+
360
+ <section class="portals-grid" id="portals-container">
361
+ <!-- Portals will be dynamically loaded here -->
362
+ </section>
363
+
364
+ <section class="api-section">
365
+ <h3>🔧 API Endpoints</h3>
366
+ <div class="api-endpoints">
367
+ <div class="api-endpoint">POST /api/update-urls - Update portal URLs</div>
368
+ <div class="api-endpoint">GET /api/status - System status</div>
369
+ <div class="api-endpoint">GET /health - Health check</div>
370
+ </div>
371
+ </section>
372
+ </main>
373
+
374
+ <footer class="footer">
375
+ <p>🔬 Advanced Biological Research Platform</p>
376
+ <p>🚀 Powered by AEGIS Technology</p>
377
+ <p>&copy; 2025 AEGIS Systems - All Rights Reserved</p>
378
+ </footer>
379
+
380
+ <script>
381
+ // Server-side data will be injected here if available
382
+ window.portalsData = null;
383
+ </script>
384
+
385
+ <script>
386
+ // Initialize Lucide icons
387
+ lucide.createIcons();
388
+
389
+ // Portal data with icons
390
+ const defaultPortals = [
391
+ { name: 'Portal Control', url: '#', icon: 'settings', color: 'blue', description: 'Main control center' },
392
+ { name: 'SciBERT Analysis', url: '#', icon: 'bar-chart-3', color: 'green', description: 'Scientific analysis tools' },
393
+ { name: 'Conductor', url: '#', icon: 'cpu', color: 'purple', description: 'System orchestration' },
394
+ { name: 'Economic Modal', url: '#', icon: 'dollar-sign', color: 'yellow', description: 'Economic modeling' },
395
+ { name: 'War Modal', url: '#', icon: 'shield', color: 'red', description: 'Strategic analysis' },
396
+ { name: 'Disease Modal', url: '#', icon: 'bug', color: 'orange', description: 'Disease tracking' },
397
+ { name: 'DeepSeek', url: '#', icon: 'brain', color: 'indigo', description: 'AI deep analysis' },
398
+ { name: 'Image Modal', url: '#', icon: 'image', color: 'pink', description: 'Image processing' },
399
+ { name: 'Drug Development', url: '#', icon: 'pill', color: 'teal', description: 'Pharmaceutical research' },
400
+ { name: 'Visual Kinetic', url: '#', icon: 'eye', color: 'cyan', description: 'Visual analytics' }
401
+ ];
402
+
403
+ // Update time
404
+ function updateTime() {
405
+ const now = new Date();
406
+ document.getElementById('current-time').textContent = now.toLocaleString();
407
+ }
408
+
409
+ // Load portals from API or use defaults
410
+ async function loadPortals() {
411
+ const container = document.getElementById('portals-container');
412
+
413
+ // Show loading state
414
+ container.innerHTML = `
415
+ <div style="grid-column: 1 / -1; text-align: center; padding: 2rem;">
416
+ <i data-lucide="loader-2" class="animate-spin" style="width: 48px; height: 48px; margin-bottom: 1rem;"></i>
417
+ <p>Loading portals...</p>
418
+ </div>
419
+ `;
420
+ lucide.createIcons();
421
+
422
+ let portalsData = null;
423
+
424
+ // Try to load from API first
425
+ try {
426
+ const response = await fetch('/api/portals');
427
+ if (response.ok) {
428
+ const data = await response.json();
429
+ portalsData = data.portals;
430
+ }
431
+ } catch (error) {
432
+ console.warn('Failed to load portals from API:', error);
433
+ }
434
+
435
+ // Check if portals data is available from server-side injection
436
+ if (!portalsData && typeof window.portalsData !== 'undefined') {
437
+ portalsData = window.portalsData;
438
+ }
439
+
440
+ // Fallback to default portals if no data available
441
+ const portalsToUse = portalsData || defaultPortals;
442
+
443
+ container.innerHTML = portalsToUse.map(portal => `
444
+ <div class="portal-card" onclick="openPortal('${portal.url}', '${portal.name}')">
445
+ <div class="portal-icon color-${portal.color}">
446
+ <i data-lucide="${portal.icon}"></i>
447
+ </div>
448
+ <h3>${portal.name}</h3>
449
+ <p>${portal.description}</p>
450
+ <a href="${portal.url}" class="launch-btn" onclick="event.stopPropagation(); openPortal('${portal.url}', '${portal.name}')">
451
+ <span>Launch Portal</span>
452
+ <i data-lucide="external-link"></i>
453
+ </a>
454
+ </div>
455
+ `).join('');
456
+
457
+ lucide.createIcons();
458
+ }
459
+
460
+ // Open portal
461
+ function openPortal(url) {
462
+ if (url && url !== '#') {
463
+ window.open(url, '_blank', 'noopener,noreferrer');
464
+ }
465
+ }
466
+
467
+ // Check system status
468
+ async function checkStatus() {
469
+ try {
470
+ const response = await fetch('/api/status');
471
+ const data = await response.json();
472
+
473
+ const statusIcon = document.getElementById('status-icon');
474
+ const statusText = document.getElementById('status-text');
475
+ const connectionStatus = document.getElementById('connection-status');
476
+
477
+ if (data.status === 'online') {
478
+ statusIcon.setAttribute('data-lucide', 'wifi');
479
+ statusText.textContent = 'System Online';
480
+ connectionStatus.className = 'status-item status-online';
481
+ } else {
482
+ statusIcon.setAttribute('data-lucide', 'wifi-off');
483
+ statusText.textContent = 'System Offline';
484
+ connectionStatus.className = 'status-item status-offline';
485
+ }
486
+
487
+ lucide.createIcons();
488
+ } catch (error) {
489
+ const statusIcon = document.getElementById('status-icon');
490
+ const statusText = document.getElementById('status-text');
491
+ const connectionStatus = document.getElementById('connection-status');
492
+
493
+ statusIcon.setAttribute('data-lucide', 'wifi-off');
494
+ statusText.textContent = 'Connection Error';
495
+ connectionStatus.className = 'status-item status-offline';
496
+
497
+ lucide.createIcons();
498
+ }
499
+ }
500
+
501
+ // Update portal URLs
502
+ async function updatePortalUrls(newUrls) {
503
+ try {
504
+ const response = await fetch('/api/update-urls', {
505
+ method: 'POST',
506
+ headers: {
507
+ 'Content-Type': 'application/json',
508
+ },
509
+ body: JSON.stringify({ urls: newUrls })
510
+ });
511
+
512
+ const data = await response.json();
513
+
514
+ if (data.success) {
515
+ loadPortals();
516
+ return true;
517
+ } else {
518
+ console.error('Failed to update URLs:', data.error);
519
+ return false;
520
+ }
521
+ } catch (error) {
522
+ console.error('Error updating URLs:', error);
523
+ return false;
524
+ }
525
+ }
526
+
527
+ // Initialize dashboard
528
+ function initDashboard() {
529
+ updateTime();
530
+ loadPortals();
531
+ checkStatus();
532
+
533
+ // Update time every second
534
+ setInterval(updateTime, 1000);
535
+
536
+ // Check status every 30 seconds
537
+ setInterval(checkStatus, 30000);
538
+ }
539
+
540
+ // Handle keyboard shortcuts
541
+ document.addEventListener('keydown', function(event) {
542
+ // Ctrl/Cmd + R to refresh portals
543
+ if ((event.ctrlKey || event.metaKey) && event.key === 'r') {
544
+ event.preventDefault();
545
+ loadPortals();
546
+ checkStatus();
547
+ }
548
+ });
549
+
550
+ // Handle window focus/blur for status updates
551
+ window.addEventListener('focus', function() {
552
+ checkStatus();
553
+ });
554
+
555
+ // Initialize when DOM is loaded
556
+ document.addEventListener('DOMContentLoaded', initDashboard);
557
+
558
+ // Handle portal card animations
559
+ document.addEventListener('mouseover', function(event) {
560
+ if (event.target.closest('.portal-card')) {
561
+ event.target.closest('.portal-card').style.transform = 'translateY(-5px) scale(1.02)';
562
+ }
563
+ });
564
+
565
+ document.addEventListener('mouseout', function(event) {
566
+ if (event.target.closest('.portal-card')) {
567
+ event.target.closest('.portal-card').style.transform = 'translateY(0) scale(1)';
568
+ }
569
+ });
570
+
571
+ // Add loading states
572
+ function showLoading(elementId) {
573
+ const element = document.getElementById(elementId);
574
+ if (element) {
575
+ element.innerHTML = '<i data-lucide="loader-2" class="animate-spin"></i> Loading...';
576
+ lucide.createIcons();
577
+ }
578
+ }
579
+
580
+ function hideLoading(elementId, originalContent) {
581
+ const element = document.getElementById(elementId);
582
+ if (element) {
583
+ element.innerHTML = originalContent;
584
+ lucide.createIcons();
585
+ }
586
+ }
587
+
588
+ // Add error handling for portal launches
589
+ function handlePortalError(portalName, error) {
590
+ console.error(`Error launching ${portalName}:`, error);
591
+
592
+ // Show user-friendly error message
593
+ const errorDiv = document.createElement('div');
594
+ errorDiv.className = 'error-notification';
595
+ errorDiv.style.cssText = `
596
+ position: fixed;
597
+ top: 20px;
598
+ right: 20px;
599
+ background: rgba(244, 67, 54, 0.9);
600
+ color: white;
601
+ padding: 1rem;
602
+ border-radius: 8px;
603
+ z-index: 1000;
604
+ max-width: 300px;
605
+ backdrop-filter: blur(10px);
606
+ `;
607
+ errorDiv.innerHTML = `
608
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
609
+ <i data-lucide="alert-circle"></i>
610
+ <span>Failed to launch ${portalName}</span>
611
+ </div>
612
+ `;
613
+
614
+ document.body.appendChild(errorDiv);
615
+ lucide.createIcons();
616
+
617
+ // Remove error message after 5 seconds
618
+ setTimeout(() => {
619
+ if (errorDiv.parentNode) {
620
+ errorDiv.parentNode.removeChild(errorDiv);
621
+ }
622
+ }, 5000);
623
+ }
624
+
625
+ // Enhanced portal opening with error handling
626
+ function openPortalSafe(url, name) {
627
+ try {
628
+ if (url && url !== '#') {
629
+ const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
630
+
631
+ // Check if popup was blocked
632
+ if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
633
+ throw new Error('Popup blocked or failed to open');
634
+ }
635
+ } else {
636
+ throw new Error('Invalid URL');
637
+ }
638
+ } catch (error) {
639
+ handlePortalError(name, error);
640
+ }
641
+ }
642
+
643
+ // Update the openPortal function to use the safer version
644
+ function openPortal(url, name = 'Portal') {
645
+ openPortalSafe(url, name);
646
+ }
647
+
648
+ // Add service worker registration for offline support
649
+ if ('serviceWorker' in navigator) {
650
+ window.addEventListener('load', function() {
651
+ navigator.serviceWorker.register('/sw.js')
652
+ .then(function(registration) {
653
+ console.log('ServiceWorker registration successful');
654
+ })
655
+ .catch(function(error) {
656
+ console.log('ServiceWorker registration failed');
657
+ });
658
+ });
659
+ }
660
+ </script>
661
+ </body>
662
+ </html>
templates/ar_portal.html ADDED
Binary file (6 Bytes). View file
 
templates/ar_portal_index.html ADDED
File without changes
templates/index.html ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Bio Human Atlas Viewer | Gaston Software Solutions Tec</title>
7
+ <meta name="description" content="Interactive 3D human anatomy atlas with virus visualization and drug development insights">
8
+ <meta name="keywords" content="anatomy, 3D visualization, medical, virus, drug development, atlas">
9
+ <meta name="author" content="Gaston Software Solutions Tec, Uganda">
10
+
11
+ <!-- Open Graph / Facebook -->
12
+ <meta property="og:type" content="website">
13
+ <meta property="og:url" content="https://huggingface.co/spaces/gsstec/bio_human">
14
+ <meta property="og:title" content="Bio Human Atlas Viewer">
15
+ <meta property="og:description" content="Interactive 3D human anatomy atlas with virus visualization">
16
+ <meta property="og:image" content="/static/preview.png">
17
+
18
+ <!-- Twitter -->
19
+ <meta property="twitter:card" content="summary_large_image">
20
+ <meta property="twitter:url" content="https://huggingface.co/spaces/gsstec/bio_human">
21
+ <meta property="twitter:title" content="Bio Human Atlas Viewer">
22
+ <meta property="twitter:description" content="Interactive 3D human anatomy atlas with virus visualization">
23
+ <meta property="twitter:image" content="/static/preview.png">
24
+
25
+ <style>
26
+ * {
27
+ margin: 0;
28
+ padding: 0;
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ body {
33
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
34
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
35
+ color: white;
36
+ min-height: 100vh;
37
+ display: flex;
38
+ flex-direction: column;
39
+ }
40
+
41
+ .header {
42
+ background: rgba(0, 0, 0, 0.3);
43
+ padding: 1rem 2rem;
44
+ backdrop-filter: blur(10px);
45
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
46
+ }
47
+
48
+ .header h1 {
49
+ font-size: 2.5rem;
50
+ margin-bottom: 0.5rem;
51
+ background: linear-gradient(45deg, #4CAF50, #2196F3);
52
+ -webkit-background-clip: text;
53
+ -webkit-text-fill-color: transparent;
54
+ background-clip: text;
55
+ }
56
+
57
+ .header .subtitle {
58
+ font-size: 1.1rem;
59
+ opacity: 0.9;
60
+ margin-bottom: 0.5rem;
61
+ }
62
+
63
+ .header .organization {
64
+ font-size: 0.9rem;
65
+ opacity: 0.7;
66
+ font-style: italic;
67
+ }
68
+
69
+ .main-content {
70
+ flex: 1;
71
+ display: flex;
72
+ flex-direction: column;
73
+ align-items: center;
74
+ justify-content: center;
75
+ padding: 2rem;
76
+ text-align: center;
77
+ }
78
+
79
+ .hero-section {
80
+ max-width: 800px;
81
+ margin-bottom: 3rem;
82
+ }
83
+
84
+ .hero-section h2 {
85
+ font-size: 2rem;
86
+ margin-bottom: 1rem;
87
+ color: #E3F2FD;
88
+ }
89
+
90
+ .hero-section p {
91
+ font-size: 1.2rem;
92
+ line-height: 1.6;
93
+ opacity: 0.9;
94
+ margin-bottom: 2rem;
95
+ }
96
+
97
+ .features {
98
+ display: grid;
99
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
100
+ gap: 2rem;
101
+ margin-bottom: 3rem;
102
+ max-width: 1000px;
103
+ }
104
+
105
+ .feature-card {
106
+ background: rgba(255, 255, 255, 0.1);
107
+ padding: 2rem;
108
+ border-radius: 15px;
109
+ backdrop-filter: blur(10px);
110
+ border: 1px solid rgba(255, 255, 255, 0.2);
111
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
112
+ }
113
+
114
+ .feature-card:hover {
115
+ transform: translateY(-5px);
116
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
117
+ }
118
+
119
+ .feature-card h3 {
120
+ font-size: 1.3rem;
121
+ margin-bottom: 1rem;
122
+ color: #4CAF50;
123
+ }
124
+
125
+ .feature-card p {
126
+ opacity: 0.9;
127
+ line-height: 1.5;
128
+ }
129
+
130
+ .cta-section {
131
+ margin-top: 2rem;
132
+ }
133
+
134
+ .btn {
135
+ display: inline-block;
136
+ padding: 1rem 2rem;
137
+ margin: 0.5rem;
138
+ background: linear-gradient(45deg, #4CAF50, #2196F3);
139
+ color: white;
140
+ text-decoration: none;
141
+ border-radius: 50px;
142
+ font-weight: bold;
143
+ font-size: 1.1rem;
144
+ transition: all 0.3s ease;
145
+ border: none;
146
+ cursor: pointer;
147
+ }
148
+
149
+ .btn:hover {
150
+ transform: translateY(-2px);
151
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
152
+ }
153
+
154
+ .btn-secondary {
155
+ background: rgba(255, 255, 255, 0.2);
156
+ border: 2px solid rgba(255, 255, 255, 0.3);
157
+ }
158
+
159
+ .api-section {
160
+ background: rgba(0, 0, 0, 0.2);
161
+ padding: 2rem;
162
+ border-radius: 15px;
163
+ margin-top: 2rem;
164
+ max-width: 800px;
165
+ }
166
+
167
+ .api-section h3 {
168
+ color: #4CAF50;
169
+ margin-bottom: 1rem;
170
+ }
171
+
172
+ .api-endpoints {
173
+ text-align: left;
174
+ font-family: 'Courier New', monospace;
175
+ font-size: 0.9rem;
176
+ opacity: 0.8;
177
+ }
178
+
179
+ .api-endpoints div {
180
+ margin: 0.5rem 0;
181
+ padding: 0.5rem;
182
+ background: rgba(0, 0, 0, 0.3);
183
+ border-radius: 5px;
184
+ }
185
+
186
+ .footer {
187
+ background: rgba(0, 0, 0, 0.3);
188
+ padding: 1rem 2rem;
189
+ text-align: center;
190
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
191
+ }
192
+
193
+ .footer p {
194
+ opacity: 0.7;
195
+ font-size: 0.9rem;
196
+ }
197
+
198
+ @media (max-width: 768px) {
199
+ .header h1 {
200
+ font-size: 2rem;
201
+ }
202
+
203
+ .hero-section h2 {
204
+ font-size: 1.5rem;
205
+ }
206
+
207
+ .hero-section p {
208
+ font-size: 1rem;
209
+ }
210
+
211
+ .features {
212
+ grid-template-columns: 1fr;
213
+ }
214
+ }
215
+ </style>
216
+ </head>
217
+ <body>
218
+ <header class="header">
219
+ <h1>Bio Human Atlas Viewer</h1>
220
+ <div class="subtitle">Interactive 3D Human Anatomy Visualization</div>
221
+ <div class="organization">Gaston Software Solutions Tec, Uganda</div>
222
+ </header>
223
+
224
+ <main class="main-content">
225
+ <section class="hero-section">
226
+ <h2>Explore Human Anatomy in 3D</h2>
227
+ <p>
228
+ Advanced interactive 3D anatomy atlas featuring virus visualization,
229
+ drug development insights, and comprehensive biological analysis.
230
+ Built with cutting-edge web technologies for medical education and research.
231
+ </p>
232
+ </section>
233
+
234
+ <section class="features">
235
+ <div class="feature-card">
236
+ <h3>🧬 3D Anatomy Visualization</h3>
237
+ <p>Interactive 3D models of human anatomy with detailed organ systems, tissues, and cellular structures using WebGL technology.</p>
238
+ </div>
239
+
240
+ <div class="feature-card">
241
+ <h3>🔬 Educational Platform</h3>
242
+ <p>Comprehensive learning tool for medical students, researchers, and healthcare professionals with detailed anatomical information.</p>
243
+ </div>
244
+
245
+ <div class="feature-card">
246
+ <h3>🌐 Web-Based Access</h3>
247
+ <p>No installation required - access the full 3D anatomy atlas directly through your web browser on any device.</p>
248
+ </div>
249
+
250
+ <div class="feature-card">
251
+ <h3>� Cross-Platform</h3>
252
+ <p>Responsive design that works seamlessly on desktop, tablet, and mobile devices with touch and mouse controls.</p>
253
+ </div>
254
+ </section>
255
+
256
+ <section class="cta-section">
257
+ <a href="/viewer" class="btn">Launch 3D Atlas Viewer</a>
258
+ <a href="/api/health" class="btn btn-secondary">API Status</a>
259
+ </section>
260
+
261
+ <section class="api-section">
262
+ <h3>Available API Endpoints</h3>
263
+ <div class="api-endpoints">
264
+ <div>GET /api/health - Service health check</div>
265
+ <div>GET /api/atlas/structure - Atlas configuration</div>
266
+ <div>GET /api/bio-digital-lab - Bio digital lab data</div>
267
+ <div>GET /api/disease-analysis - Disease analysis data</div>
268
+ <div>GET /api/drug-development - Drug development results</div>
269
+ <div>GET /api/comprehensive-virus-data - Complete virus dataset</div>
270
+ <div>GET /api/atlas/virus-mapping - Virus-anatomy mapping</div>
271
+ <div>GET /api/sample-data - Sample anatomical data</div>
272
+ <div>GET /api/atlas/demo-structures - Demo 3D structures</div>
273
+ </div>
274
+ </section>
275
+ </main>
276
+
277
+ <footer class="footer">
278
+ <p>&copy; 2026 Gaston Software Solutions Tec, Uganda. All rights reserved.</p>
279
+ <p>Bio Human Atlas Viewer - Advanced 3D Anatomy Visualization Platform</p>
280
+ </footer>
281
+
282
+ <script>
283
+ // Add some interactive effects
284
+ document.addEventListener('DOMContentLoaded', function() {
285
+ // Animate feature cards on scroll
286
+ const observerOptions = {
287
+ threshold: 0.1,
288
+ rootMargin: '0px 0px -50px 0px'
289
+ };
290
+
291
+ const observer = new IntersectionObserver(function(entries) {
292
+ entries.forEach(entry => {
293
+ if (entry.isIntersecting) {
294
+ entry.target.style.opacity = '1';
295
+ entry.target.style.transform = 'translateY(0)';
296
+ }
297
+ });
298
+ }, observerOptions);
299
+
300
+ // Observe feature cards
301
+ document.querySelectorAll('.feature-card').forEach(card => {
302
+ card.style.opacity = '0';
303
+ card.style.transform = 'translateY(20px)';
304
+ card.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
305
+ observer.observe(card);
306
+ });
307
+
308
+ // Add click tracking for analytics
309
+ document.querySelectorAll('.btn').forEach(btn => {
310
+ btn.addEventListener('click', function(e) {
311
+ console.log('Button clicked:', this.textContent);
312
+ });
313
+ });
314
+ });
315
+ </script>
316
+ </body>
317
+ </html>
templates/index_aegis.html ADDED
@@ -0,0 +1,988 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AEGIS BIO LAB 10 CONDUCTOR - Grid Interface</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #667eea 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ background: white;
26
+ border-radius: 20px;
27
+ box-shadow: 0 20px 40px rgba(0,0,0,0.15);
28
+ width: 100%;
29
+ max-width: 1600px;
30
+ height: 90vh;
31
+ display: grid;
32
+ grid-template-columns: 1fr 1fr;
33
+ grid-template-rows: 1fr 1fr;
34
+ gap: 20px;
35
+ padding: 25px;
36
+ }
37
+
38
+ /* Grid squares */
39
+ .grid-square {
40
+ background: #f8f9fa;
41
+ border-radius: 15px;
42
+ border: 2px solid #e9ecef;
43
+ display: flex;
44
+ flex-direction: column;
45
+ overflow: hidden;
46
+ position: relative;
47
+ }
48
+
49
+ /* Header for each square */
50
+ .square-header {
51
+ background: linear-gradient(135deg, #1e3c72, #2a5298);
52
+ color: white;
53
+ padding: 15px 20px;
54
+ border-radius: 13px 13px 0 0;
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 10px;
58
+ flex-shrink: 0;
59
+ z-index: 2;
60
+ }
61
+
62
+ .square-header h3 {
63
+ font-size: 1.2em;
64
+ font-weight: 600;
65
+ margin: 0;
66
+ }
67
+
68
+ /* Scrollable content area */
69
+ .square-content {
70
+ flex: 1;
71
+ overflow-y: auto;
72
+ overflow-x: hidden;
73
+ padding: 20px;
74
+ }
75
+
76
+ /* Custom scrollbar for all squares */
77
+ .square-content::-webkit-scrollbar {
78
+ width: 8px;
79
+ }
80
+
81
+ .square-content::-webkit-scrollbar-track {
82
+ background: #f1f1f1;
83
+ border-radius: 4px;
84
+ }
85
+
86
+ .square-content::-webkit-scrollbar-thumb {
87
+ background: #c1c1c1;
88
+ border-radius: 4px;
89
+ }
90
+
91
+ .square-content::-webkit-scrollbar-thumb:hover {
92
+ background: #a8a8a8;
93
+ }
94
+
95
+ /* Square 1: Status & Controls */
96
+ #square-1 .square-content {
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: 20px;
100
+ }
101
+
102
+ .status-panel {
103
+ background: white;
104
+ border-radius: 12px;
105
+ padding: 20px;
106
+ border-left: 4px solid #007bff;
107
+ }
108
+
109
+ .status-panel.ready {
110
+ border-left-color: #28a745;
111
+ }
112
+
113
+ .status-panel.loading {
114
+ border-left-color: #ffc107;
115
+ }
116
+
117
+ .status-panel.error {
118
+ border-left-color: #dc3545;
119
+ }
120
+
121
+ .status-text {
122
+ font-size: 1.1em;
123
+ font-weight: 600;
124
+ margin-bottom: 10px;
125
+ }
126
+
127
+ .status-details {
128
+ font-size: 0.9em;
129
+ color: #666;
130
+ line-height: 1.5;
131
+ }
132
+
133
+ .control-group {
134
+ background: white;
135
+ border-radius: 12px;
136
+ padding: 15px;
137
+ margin-bottom: 10px;
138
+ }
139
+
140
+ .control-group label {
141
+ display: block;
142
+ margin-bottom: 8px;
143
+ color: #555;
144
+ font-weight: 600;
145
+ font-size: 0.9em;
146
+ }
147
+
148
+ .control-group select,
149
+ .control-group input[type="range"] {
150
+ width: 100%;
151
+ margin-bottom: 5px;
152
+ }
153
+
154
+ .slider-container {
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 15px;
158
+ }
159
+
160
+ .slider-value {
161
+ min-width: 40px;
162
+ text-align: center;
163
+ font-weight: 600;
164
+ color: #1e3c72;
165
+ }
166
+
167
+ .action-buttons {
168
+ display: flex;
169
+ gap: 10px;
170
+ margin-top: 10px;
171
+ }
172
+
173
+ .action-btn {
174
+ flex: 1;
175
+ padding: 12px;
176
+ border: none;
177
+ border-radius: 10px;
178
+ font-weight: 600;
179
+ cursor: pointer;
180
+ transition: all 0.3s;
181
+ }
182
+
183
+ .primary-btn {
184
+ background: linear-gradient(135deg, #1e3c72, #2a5298);
185
+ color: white;
186
+ }
187
+
188
+ .danger-btn {
189
+ background: linear-gradient(135deg, #dc3545, #c82333);
190
+ color: white;
191
+ }
192
+
193
+ .action-btn:hover:not(:disabled) {
194
+ transform: translateY(-2px);
195
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
196
+ }
197
+
198
+ /* Square 2: Chat Interface */
199
+ .chat-messages {
200
+ flex: 1;
201
+ overflow-y: auto;
202
+ margin-bottom: 15px;
203
+ }
204
+
205
+ .message {
206
+ margin-bottom: 15px;
207
+ display: flex;
208
+ }
209
+
210
+ .message.user {
211
+ justify-content: flex-end;
212
+ }
213
+
214
+ .message-content {
215
+ max-width: 85%;
216
+ padding: 12px 16px;
217
+ border-radius: 15px;
218
+ word-wrap: break-word;
219
+ line-height: 1.5;
220
+ }
221
+
222
+ .message.user .message-content {
223
+ background: linear-gradient(135deg, #1e3c72, #2a5298);
224
+ color: white;
225
+ border-bottom-right-radius: 5px;
226
+ }
227
+
228
+ .message.assistant .message-content {
229
+ background: white;
230
+ color: #333;
231
+ border: 1px solid #e9ecef;
232
+ border-bottom-left-radius: 5px;
233
+ }
234
+
235
+ .chat-input-container {
236
+ background: white;
237
+ padding: 15px;
238
+ border-radius: 12px;
239
+ border: 2px solid #e9ecef;
240
+ }
241
+
242
+ .chat-input-container textarea {
243
+ width: 100%;
244
+ border: none;
245
+ outline: none;
246
+ resize: none;
247
+ font-family: inherit;
248
+ font-size: 14px;
249
+ line-height: 1.5;
250
+ min-height: 60px;
251
+ }
252
+
253
+ /* Square 3: Threat Analysis */
254
+ .threat-scores-grid {
255
+ display: grid;
256
+ grid-template-columns: 1fr 1fr;
257
+ gap: 15px;
258
+ margin-bottom: 20px;
259
+ }
260
+
261
+ .threat-score-card {
262
+ background: white;
263
+ padding: 15px;
264
+ border-radius: 10px;
265
+ border: 1px solid #e9ecef;
266
+ }
267
+
268
+ .threat-score-header {
269
+ display: flex;
270
+ justify-content: space-between;
271
+ align-items: center;
272
+ margin-bottom: 10px;
273
+ }
274
+
275
+ .threat-name {
276
+ font-weight: 600;
277
+ color: #555;
278
+ }
279
+
280
+ .threat-value {
281
+ font-weight: 700;
282
+ color: #1e3c72;
283
+ font-size: 1.2em;
284
+ }
285
+
286
+ .analysis-controls {
287
+ background: white;
288
+ padding: 20px;
289
+ border-radius: 12px;
290
+ margin-bottom: 20px;
291
+ }
292
+
293
+ .year-input {
294
+ display: flex;
295
+ align-items: center;
296
+ gap: 10px;
297
+ margin-bottom: 15px;
298
+ }
299
+
300
+ .year-input label {
301
+ font-weight: 600;
302
+ color: #555;
303
+ min-width: 100px;
304
+ }
305
+
306
+ .year-input input {
307
+ flex: 1;
308
+ padding: 8px 12px;
309
+ border: 2px solid #e9ecef;
310
+ border-radius: 8px;
311
+ font-size: 14px;
312
+ }
313
+
314
+ .run-analysis-btn {
315
+ width: 100%;
316
+ padding: 15px;
317
+ background: linear-gradient(135deg, #28a745, #20c997);
318
+ color: white;
319
+ border: none;
320
+ border-radius: 12px;
321
+ font-size: 16px;
322
+ font-weight: 600;
323
+ cursor: pointer;
324
+ transition: all 0.3s;
325
+ }
326
+
327
+ .run-analysis-btn:hover:not(:disabled) {
328
+ transform: translateY(-2px);
329
+ box-shadow: 0 5px 15px rgba(40, 167, 69, 0.3);
330
+ }
331
+
332
+ /* Square 4: Analysis Results */
333
+ .metrics-grid {
334
+ display: grid;
335
+ grid-template-columns: 1fr 1fr;
336
+ gap: 15px;
337
+ margin-bottom: 20px;
338
+ }
339
+
340
+ .metric-card {
341
+ background: white;
342
+ padding: 15px;
343
+ border-radius: 10px;
344
+ border-left: 4px solid #1e3c72;
345
+ text-align: center;
346
+ }
347
+
348
+ .metric-value {
349
+ font-size: 1.8em;
350
+ font-weight: 700;
351
+ color: #1e3c72;
352
+ margin-bottom: 5px;
353
+ }
354
+
355
+ .metric-label {
356
+ font-size: 0.9em;
357
+ color: #666;
358
+ font-weight: 600;
359
+ }
360
+
361
+ .analysis-details {
362
+ background: white;
363
+ padding: 20px;
364
+ border-radius: 12px;
365
+ margin-top: 20px;
366
+ }
367
+
368
+ .analysis-section {
369
+ margin-bottom: 25px;
370
+ }
371
+
372
+ .analysis-section h4 {
373
+ color: #1e3c72;
374
+ margin-bottom: 10px;
375
+ font-size: 1.1em;
376
+ border-bottom: 2px solid #e9ecef;
377
+ padding-bottom: 5px;
378
+ }
379
+
380
+ .analysis-text {
381
+ white-space: pre-wrap;
382
+ line-height: 1.6;
383
+ font-size: 14px;
384
+ color: #444;
385
+ }
386
+
387
+ .region-list {
388
+ display: grid;
389
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
390
+ gap: 10px;
391
+ margin-top: 10px;
392
+ }
393
+
394
+ .region-item {
395
+ background: #f1f8ff;
396
+ padding: 8px 12px;
397
+ border-radius: 6px;
398
+ font-size: 0.85em;
399
+ border-left: 3px solid #1e3c72;
400
+ }
401
+
402
+ .loading-spinner {
403
+ display: inline-block;
404
+ width: 20px;
405
+ height: 20px;
406
+ border: 3px solid #f3f3f3;
407
+ border-top: 3px solid #1e3c72;
408
+ border-radius: 50%;
409
+ animation: spin 1s linear infinite;
410
+ margin-right: 10px;
411
+ }
412
+
413
+ @keyframes spin {
414
+ 0% { transform: rotate(0deg); }
415
+ 100% { transform: rotate(360deg); }
416
+ }
417
+
418
+ /* Placeholder states */
419
+ .placeholder {
420
+ display: flex;
421
+ flex-direction: column;
422
+ align-items: center;
423
+ justify-content: center;
424
+ height: 100%;
425
+ color: #666;
426
+ text-align: center;
427
+ padding: 40px;
428
+ }
429
+
430
+ .placeholder-icon {
431
+ font-size: 3em;
432
+ margin-bottom: 20px;
433
+ opacity: 0.5;
434
+ }
435
+
436
+ /* Responsive design */
437
+ @media (max-width: 1200px) {
438
+ .container {
439
+ grid-template-columns: 1fr;
440
+ grid-template-rows: auto;
441
+ height: auto;
442
+ min-height: 90vh;
443
+ }
444
+
445
+ .grid-square {
446
+ min-height: 300px;
447
+ }
448
+ }
449
+
450
+ @media (max-width: 768px) {
451
+ .threat-scores-grid,
452
+ .metrics-grid {
453
+ grid-template-columns: 1fr;
454
+ }
455
+
456
+ .region-list {
457
+ grid-template-columns: 1fr;
458
+ }
459
+ }
460
+ </style>
461
+ </head>
462
+ <body>
463
+ <div class="container">
464
+ <!-- Square 1: Status & Controls -->
465
+ <div id="square-1" class="grid-square">
466
+ <div class="square-header">
467
+ <div class="loading-spinner"></div>
468
+ <h3>Status & Configuration</h3>
469
+ </div>
470
+ <div class="square-content">
471
+ <div id="status-panel" class="status-panel loading">
472
+ <div class="status-text" id="status-text">Loading AEGIS CONDUCTOR...</div>
473
+ <div class="status-details" id="status-details">Initializing multi-domain expert system</div>
474
+ </div>
475
+
476
+ <div class="control-group">
477
+ <label for="analysis-type">Analysis Domain</label>
478
+ <select id="analysis-type">
479
+ <option value="general">🌐 General Analysis</option>
480
+ <option value="economic">🏦 Economics</option>
481
+ <option value="technology">🔬 Technology</option>
482
+ <option value="security">🛡️ Security</option>
483
+ <option value="health">🏥 Health & Bio</option>
484
+ <option value="environment">🌍 Environment</option>
485
+ <option value="strategic">📋 Strategic Planning</option>
486
+ <option value="threat">⚠️ Threat Assessment</option>
487
+ <option value="aegis_conductor">🧬 AEGIS Conductor</option>
488
+ </select>
489
+ </div>
490
+
491
+ <div class="control-group">
492
+ <label for="temperature">Response Creativity</label>
493
+ <div class="slider-container">
494
+ <input type="range" id="temperature" min="0.1" max="1.0" step="0.1" value="0.7">
495
+ <div class="slider-value" id="temperature-value">0.7</div>
496
+ </div>
497
+ </div>
498
+
499
+ <div class="control-group">
500
+ <label for="max-tokens">Response Length</label>
501
+ <div class="slider-container">
502
+ <input type="range" id="max-tokens" min="128" max="512" step="64" value="256">
503
+ <div class="slider-value" id="max-tokens-value">256</div>
504
+ </div>
505
+ </div>
506
+
507
+ <div class="action-buttons">
508
+ <button id="clear-btn" class="action-btn danger-btn">Clear Chat</button>
509
+ <button id="refresh-btn" class="action-btn primary-btn">Refresh Status</button>
510
+ </div>
511
+
512
+ <div class="control-group">
513
+ <h4>System Info</h4>
514
+ <div id="system-info">
515
+ <p>Model: <span id="model-name">Loading...</span></p>
516
+ <p>Version: <span id="model-version">Loading...</span></p>
517
+ <p>Regions: <span id="model-regions">Loading...</span></p>
518
+ <p>Optimization: <span id="model-optimization">8-bit quantization</span></p>
519
+ </div>
520
+ </div>
521
+ </div>
522
+ </div>
523
+
524
+ <!-- Square 2: Chat Interface -->
525
+ <div id="square-2" class="grid-square">
526
+ <div class="square-header">
527
+ <span>💬</span>
528
+ <h3>Multi-Domain Chat</h3>
529
+ </div>
530
+ <div class="square-content">
531
+ <div class="chat-messages" id="chat-messages">
532
+ <div class="message assistant">
533
+ <div class="message-content">
534
+ Welcome to AEGIS BIO LAB 10 CONDUCTOR! I'm your multi-domain expert system capable of analyzing any topic across:
535
+ <br><br>
536
+ • Economics & Finance<br>
537
+ • Technology & AI<br>
538
+ • Security & Defense<br>
539
+ • Health & Biotechnology<br>
540
+ • Environment & Climate<br>
541
+ • Strategic Planning
542
+ <br><br>
543
+ What would you like to analyze today?
544
+ </div>
545
+ </div>
546
+ </div>
547
+
548
+ <div class="chat-input-container">
549
+ <textarea
550
+ id="message-input"
551
+ placeholder="Ask about any topic... (Press Shift+Enter for new line)"
552
+ rows="3"
553
+ disabled
554
+ ></textarea>
555
+ <div class="action-buttons" style="margin-top: 10px;">
556
+ <button id="send-btn" class="action-btn primary-btn" disabled>Send Message</button>
557
+ </div>
558
+ </div>
559
+ </div>
560
+ </div>
561
+
562
+ <!-- Square 3: Threat Analysis -->
563
+ <div id="square-3" class="grid-square">
564
+ <div class="square-header">
565
+ <span>🎯</span>
566
+ <h3>Threat Assessment</h3>
567
+ </div>
568
+ <div class="square-content">
569
+ <div class="threat-scores-grid">
570
+ <div class="threat-score-card">
571
+ <div class="threat-score-header">
572
+ <span class="threat-name">AI Threats</span>
573
+ <span class="threat-value" id="ai-value">7.0</span>
574
+ </div>
575
+ <input type="range" id="ai-score" min="0" max="10" step="0.1" value="7.0">
576
+ </div>
577
+
578
+ <div class="threat-score-card">
579
+ <div class="threat-score-header">
580
+ <span class="threat-name">Cyber Threats</span>
581
+ <span class="threat-value" id="cyber-value">6.5</span>
582
+ </div>
583
+ <input type="range" id="cyber-score" min="0" max="10" step="0.1" value="6.5">
584
+ </div>
585
+
586
+ <div class="threat-score-card">
587
+ <div class="threat-score-header">
588
+ <span class="threat-name">Bio Threats</span>
589
+ <span class="threat-value" id="bio-value">8.0</span>
590
+ </div>
591
+ <input type="range" id="bio-score" min="0" max="10" step="0.1" value="8.0">
592
+ </div>
593
+
594
+ <div class="threat-score-card">
595
+ <div class="threat-score-header">
596
+ <span class="threat-name">Nuclear Threats</span>
597
+ <span class="threat-value" id="nuclear-value">4.0</span>
598
+ </div>
599
+ <input type="range" id="nuclear-score" min="0" max="10" step="0.1" value="4.0">
600
+ </div>
601
+
602
+ <div class="threat-score-card">
603
+ <div class="threat-score-header">
604
+ <span class="threat-name">Climate Threats</span>
605
+ <span class="threat-value" id="climate-value">7.5</span>
606
+ </div>
607
+ <input type="range" id="climate-score" min="0" max="10" step="0.1" value="7.5">
608
+ </div>
609
+
610
+ <div class="threat-score-card">
611
+ <div class="threat-score-header">
612
+ <span class="threat-name">Space Threats</span>
613
+ <span class="threat-value" id="space-value">5.0</span>
614
+ </div>
615
+ <input type="range" id="space-score" min="0" max="10" step="0.1" value="5.0">
616
+ </div>
617
+ </div>
618
+
619
+ <div class="analysis-controls">
620
+ <div class="year-input">
621
+ <label for="analysis-year">Analysis Year:</label>
622
+ <input type="number" id="analysis-year" min="2024" max="2030" value="2024">
623
+ </div>
624
+
625
+ <button id="run-analysis-btn" class="run-analysis-btn" disabled>
626
+ 🚀 Run Comprehensive Analysis
627
+ </button>
628
+ </div>
629
+
630
+ <div class="control-group">
631
+ <h4>Global Coverage</h4>
632
+ <div class="region-list">
633
+ <div class="region-item">North America</div>
634
+ <div class="region-item">Europe</div>
635
+ <div class="region-item">Asia</div>
636
+ <div class="region-item">Africa</div>
637
+ <div class="region-item">South America</div>
638
+ <div class="region-item">Middle East</div>
639
+ <div class="region-item">Oceania</div>
640
+ <div class="region-item">Arctic Region</div>
641
+ </div>
642
+ </div>
643
+ </div>
644
+ </div>
645
+
646
+ <!-- Square 4: Analysis Results -->
647
+ <div id="square-4" class="grid-square">
648
+ <div class="square-header">
649
+ <span>📊</span>
650
+ <h3>Analysis Results</h3>
651
+ </div>
652
+ <div class="square-content">
653
+ <div id="analysis-placeholder" class="placeholder">
654
+ <div class="placeholder-icon">📈</div>
655
+ <h3>Analysis Results</h3>
656
+ <p>Configure threat scores and run analysis to see comprehensive results here.</p>
657
+ <p>Results will include:</p>
658
+ <ul style="text-align: left; margin-top: 10px;">
659
+ <li>Market shock index</li>
660
+ <li>Economic impact classification</li>
661
+ <li>Threat level assessment</li>
662
+ <li>Regional vulnerabilities</li>
663
+ <li>Contagion metrics</li>
664
+ <li>Detailed analysis report</li>
665
+ </ul>
666
+ </div>
667
+
668
+ <div id="analysis-results" style="display: none;">
669
+ <div class="metrics-grid" id="metrics-display"></div>
670
+
671
+ <div class="analysis-details">
672
+ <div class="analysis-section">
673
+ <h4>Comprehensive Analysis</h4>
674
+ <div class="analysis-text" id="detailed-analysis"></div>
675
+ </div>
676
+
677
+ <div class="analysis-section" id="regional-section" style="display: none;">
678
+ <h4>Regional Vulnerabilities</h4>
679
+ <div class="region-list" id="regional-vulnerabilities"></div>
680
+ </div>
681
+
682
+ <div class="analysis-section" id="contagion-section" style="display: none;">
683
+ <h4>Contagion Metrics</h4>
684
+ <div id="contagion-metrics"></div>
685
+ </div>
686
+ </div>
687
+ </div>
688
+ </div>
689
+ </div>
690
+ </div>
691
+
692
+ <script>
693
+ let chatHistory = [];
694
+ let isGenerating = false;
695
+
696
+ // DOM Elements
697
+ const statusPanel = document.getElementById('status-panel');
698
+ const statusText = document.getElementById('status-text');
699
+ const statusDetails = document.getElementById('status-details');
700
+ const chatMessages = document.getElementById('chat-messages');
701
+ const messageInput = document.getElementById('message-input');
702
+ const sendBtn = document.getElementById('send-btn');
703
+ const clearBtn = document.getElementById('clear-btn');
704
+ const refreshBtn = document.getElementById('refresh-btn');
705
+ const runAnalysisBtn = document.getElementById('run-analysis-btn');
706
+
707
+ // System info elements
708
+ const modelName = document.getElementById('model-name');
709
+ const modelVersion = document.getElementById('model-version');
710
+ const modelRegions = document.getElementById('model-regions');
711
+
712
+ // Control elements
713
+ const analysisTypeSelect = document.getElementById('analysis-type');
714
+ const temperatureSlider = document.getElementById('temperature');
715
+ const temperatureValue = document.getElementById('temperature-value');
716
+ const maxTokensSlider = document.getElementById('max-tokens');
717
+ const maxTokensValue = document.getElementById('max-tokens-value');
718
+
719
+ // Threat score elements
720
+ const threatScores = {
721
+ ai: { slider: document.getElementById('ai-score'), value: document.getElementById('ai-value') },
722
+ cyber: { slider: document.getElementById('cyber-score'), value: document.getElementById('cyber-value') },
723
+ bio: { slider: document.getElementById('bio-score'), value: document.getElementById('bio-value') },
724
+ nuclear: { slider: document.getElementById('nuclear-score'), value: document.getElementById('nuclear-value') },
725
+ climate: { slider: document.getElementById('climate-score'), value: document.getElementById('climate-value') },
726
+ space: { slider: document.getElementById('space-score'), value: document.getElementById('space-value') }
727
+ };
728
+
729
+ // Update slider values
730
+ temperatureSlider.addEventListener('input', () => {
731
+ temperatureValue.textContent = temperatureSlider.value;
732
+ });
733
+
734
+ maxTokensSlider.addEventListener('input', () => {
735
+ maxTokensValue.textContent = maxTokensSlider.value;
736
+ });
737
+
738
+ // Update threat score values
739
+ Object.keys(threatScores).forEach(key => {
740
+ threatScores[key].slider.addEventListener('input', () => {
741
+ threatScores[key].value.textContent = threatScores[key].slider.value;
742
+ });
743
+ });
744
+
745
+ // Check model status
746
+ function checkStatus() {
747
+ fetch('/status')
748
+ .then(response => response.json())
749
+ .then(data => {
750
+ if (data.loaded) {
751
+ statusPanel.className = 'status-panel ready';
752
+ statusText.textContent = 'AEGIS CONDUCTOR Ready';
753
+ statusDetails.textContent = `Model ${data.model} v${data.version} loaded successfully`;
754
+
755
+ modelName.textContent = data.model;
756
+ modelVersion.textContent = data.version;
757
+ modelRegions.textContent = data.regions;
758
+
759
+ messageInput.disabled = false;
760
+ sendBtn.disabled = false;
761
+ runAnalysisBtn.disabled = false;
762
+ } else if (data.status.includes('Error')) {
763
+ statusPanel.className = 'status-panel error';
764
+ statusText.textContent = 'System Error';
765
+ statusDetails.textContent = data.status;
766
+ } else {
767
+ statusPanel.className = 'status-panel loading';
768
+ statusText.textContent = 'Loading System';
769
+ statusDetails.textContent = data.status;
770
+ setTimeout(checkStatus, 2000);
771
+ }
772
+ })
773
+ .catch(error => {
774
+ console.error('Status check failed:', error);
775
+ statusPanel.className = 'status-panel error';
776
+ statusText.textContent = 'Connection Error';
777
+ statusDetails.textContent = 'Cannot connect to AEGIS server';
778
+ setTimeout(checkStatus, 5000);
779
+ });
780
+ }
781
+
782
+ // Add message to chat
783
+ function addMessage(content, isUser = false) {
784
+ const messageDiv = document.createElement('div');
785
+ messageDiv.className = `message ${isUser ? 'user' : 'assistant'}`;
786
+
787
+ const contentDiv = document.createElement('div');
788
+ contentDiv.className = 'message-content';
789
+
790
+ if (isUser) {
791
+ contentDiv.textContent = content;
792
+ } else {
793
+ contentDiv.innerHTML = content.replace(/\n/g, '<br>');
794
+ }
795
+
796
+ messageDiv.appendChild(contentDiv);
797
+ chatMessages.appendChild(messageDiv);
798
+ chatMessages.scrollTop = chatMessages.scrollHeight;
799
+ }
800
+
801
+ // Send message
802
+ function sendMessage() {
803
+ const message = messageInput.value.trim();
804
+ if (!message || isGenerating) return;
805
+
806
+ isGenerating = true;
807
+ sendBtn.disabled = true;
808
+ sendBtn.textContent = 'Analyzing...';
809
+
810
+ addMessage(message, true);
811
+ messageInput.value = '';
812
+
813
+ fetch('/chat', {
814
+ method: 'POST',
815
+ headers: { 'Content-Type': 'application/json' },
816
+ body: JSON.stringify({
817
+ message: message,
818
+ history: chatHistory,
819
+ temperature: parseFloat(temperatureSlider.value),
820
+ max_tokens: parseInt(maxTokensSlider.value),
821
+ analysis_type: analysisTypeSelect.value
822
+ })
823
+ })
824
+ .then(response => response.json())
825
+ .then(data => {
826
+ if (data.error) {
827
+ addMessage(`Error: ${data.error}`);
828
+ } else {
829
+ addMessage(data.response);
830
+ chatHistory.push({
831
+ user: message,
832
+ assistant: data.response
833
+ });
834
+ }
835
+ })
836
+ .catch(error => {
837
+ console.error('Chat error:', error);
838
+ addMessage('Sorry, there was an error processing your message. Please try again.');
839
+ })
840
+ .finally(() => {
841
+ isGenerating = false;
842
+ sendBtn.disabled = false;
843
+ sendBtn.textContent = 'Send Message';
844
+ });
845
+ }
846
+
847
+ // Run AEGIS analysis
848
+ function runAegisAnalysis() {
849
+ if (isGenerating) return;
850
+
851
+ isGenerating = true;
852
+ runAnalysisBtn.disabled = true;
853
+ runAnalysisBtn.textContent = 'Analyzing...';
854
+
855
+ document.getElementById('analysis-placeholder').style.display = 'none';
856
+ document.getElementById('analysis-results').style.display = 'block';
857
+
858
+ const analysisData = {
859
+ ai_score: parseFloat(threatScores.ai.slider.value),
860
+ cyber_score: parseFloat(threatScores.cyber.slider.value),
861
+ bio_score: parseFloat(threatScores.bio.slider.value),
862
+ nuclear_score: parseFloat(threatScores.nuclear.slider.value),
863
+ climate_score: parseFloat(threatScores.climate.slider.value),
864
+ space_score: parseFloat(threatScores.space.slider.value),
865
+ year: parseInt(document.getElementById('analysis-year').value)
866
+ };
867
+
868
+ fetch('/aegis_analysis', {
869
+ method: 'POST',
870
+ headers: { 'Content-Type': 'application/json' },
871
+ body: JSON.stringify(analysisData)
872
+ })
873
+ .then(response => response.json())
874
+ .then(data => {
875
+ displayAnalysisResults(data);
876
+ })
877
+ .catch(error => {
878
+ console.error('Analysis error:', error);
879
+ document.getElementById('detailed-analysis').innerHTML =
880
+ '<p style="color: #dc3545;">Analysis failed. Please try again.</p>';
881
+ })
882
+ .finally(() => {
883
+ isGenerating = false;
884
+ runAnalysisBtn.disabled = false;
885
+ runAnalysisBtn.textContent = '🚀 Run Comprehensive Analysis';
886
+ });
887
+ }
888
+
889
+ // Display analysis results
890
+ function displayAnalysisResults(data) {
891
+ const metricsDisplay = document.getElementById('metrics-display');
892
+ const detailedAnalysis = document.getElementById('detailed-analysis');
893
+ const regionalSection = document.getElementById('regional-section');
894
+ const regionalVulnerabilities = document.getElementById('regional-vulnerabilities');
895
+ const contagionSection = document.getElementById('contagion-section');
896
+ const contagionMetrics = document.getElementById('contagion-metrics');
897
+
898
+ // Display metrics
899
+ metricsDisplay.innerHTML = `
900
+ <div class="metric-card">
901
+ <div class="metric-value">${data.market_shock_index ? data.market_shock_index.toFixed(3) : 'N/A'}</div>
902
+ <div class="metric-label">Market Shock Index</div>
903
+ </div>
904
+ <div class="metric-card">
905
+ <div class="metric-value">${data.impact_classification || 'N/A'}</div>
906
+ <div class="metric-label">Impact Level</div>
907
+ </div>
908
+ <div class="metric-card">
909
+ <div class="metric-value">${data.threat_level || 'N/A'}</div>
910
+ <div class="metric-label">Threat Level</div>
911
+ </div>
912
+ <div class="metric-card">
913
+ <div class="metric-value">${Object.keys(data.regional_vulnerabilities || {}).length}</div>
914
+ <div class="metric-label">Regions</div>
915
+ </div>
916
+ `;
917
+
918
+ // Detailed analysis
919
+ detailedAnalysis.textContent = data.reasoning_analysis || 'No analysis available';
920
+
921
+ // Regional vulnerabilities
922
+ if (data.regional_vulnerabilities && Object.keys(data.regional_vulnerabilities).length > 0) {
923
+ regionalSection.style.display = 'block';
924
+ regionalVulnerabilities.innerHTML = '';
925
+
926
+ Object.entries(data.regional_vulnerabilities).forEach(([region, score]) => {
927
+ const regionDiv = document.createElement('div');
928
+ regionDiv.className = 'region-item';
929
+ regionDiv.innerHTML = `<strong>${region}:</strong> ${typeof score === 'number' ? score.toFixed(2) : score}/10`;
930
+ regionalVulnerabilities.appendChild(regionDiv);
931
+ });
932
+ }
933
+
934
+ // Contagion metrics
935
+ if (data.contagion_metrics && Object.keys(data.contagion_metrics).length > 0) {
936
+ contagionSection.style.display = 'block';
937
+ contagionMetrics.innerHTML = '';
938
+
939
+ Object.entries(data.contagion_metrics).forEach(([metric, score]) => {
940
+ const metricDiv = document.createElement('div');
941
+ metricDiv.style.marginBottom = '8px';
942
+ metricDiv.innerHTML = `
943
+ <strong>${metric.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}:</strong>
944
+ <span style="float: right; font-weight: 600; color: #1e3c72;">
945
+ ${typeof score === 'number' ? score.toFixed(3) : score}
946
+ </span>
947
+ `;
948
+ contagionMetrics.appendChild(metricDiv);
949
+ });
950
+ }
951
+
952
+ // Scroll to top of results
953
+ document.getElementById('square-4').querySelector('.square-content').scrollTop = 0;
954
+ }
955
+
956
+ // Clear chat
957
+ function clearChat() {
958
+ chatHistory = [];
959
+ chatMessages.innerHTML = `
960
+ <div class="message assistant">
961
+ <div class="message-content">
962
+ Chat cleared! AEGIS BIO LAB 10 CONDUCTOR ready for new analysis.
963
+ </div>
964
+ </div>
965
+ `;
966
+
967
+ fetch('/clear', { method: 'POST' })
968
+ .catch(error => console.error('Clear error:', error));
969
+ }
970
+
971
+ // Event listeners
972
+ sendBtn.addEventListener('click', sendMessage);
973
+ clearBtn.addEventListener('click', clearChat);
974
+ refreshBtn.addEventListener('click', checkStatus);
975
+ runAnalysisBtn.addEventListener('click', runAegisAnalysis);
976
+
977
+ messageInput.addEventListener('keypress', (e) => {
978
+ if (e.key === 'Enter' && !e.shiftKey) {
979
+ e.preventDefault();
980
+ sendMessage();
981
+ }
982
+ });
983
+
984
+ // Initialize
985
+ checkStatus();
986
+ </script>
987
+ </body>
988
+ </html>