Simford.Dong Claude commited on
Commit
89d226e
·
1 Parent(s): 4ee7a5e

Replace custom wrapper with full OpenClaw project

Browse files

- Remove custom market research wrapper (src/, public/, server.js)
- Install full OpenClaw from npm (openclaw@latest)
- Configure gateway to run on port 7860 (HF default)
- Enable Control UI and WebChat access
- Set up auto-configuration script for openclaw.json
- Update README with full OpenClaw documentation

Features now available:
- Control UI (web dashboard)
- WebChat (browser-based chat)
- Multi-channel support (WhatsApp, Telegram, Slack, Discord, etc.)
- Multi-model support (Anthropic, OpenAI, Gemini, DeepSeek, etc.)
- Tools (Browser, Canvas, Nodes, Cron, etc.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

.gitattributes DELETED
@@ -1,35 +0,0 @@
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 CHANGED
@@ -1,17 +1,45 @@
1
- FROM node:20-slim
2
 
3
- WORKDIR /app
 
4
 
5
- # copy wrapper app
6
- COPY package.json ./
7
- RUN npm install
8
 
9
- # copy source code
10
- COPY src/ ./src/
11
- COPY server.js .
12
 
13
- # copy web UI
14
- COPY public/ ./public/
 
15
 
 
16
  EXPOSE 7860
17
- CMD ["node", "server.js"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:22-slim
2
 
3
+ # Install system dependencies
4
+ RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
5
 
6
+ # Install OpenClaw globally
7
+ RUN npm install -g openclaw@latest
 
8
 
9
+ # Create config directory
10
+ RUN mkdir -p /root/.openclaw
 
11
 
12
+ # Set up environment for Hugging Face Spaces
13
+ ENV PORT=7860
14
+ ENV GATEWAY_CONTROLUI_ALLOWINSECUREAUTH=true
15
 
16
+ # Expose the port (Hugging Face default)
17
  EXPOSE 7860
18
+
19
+ # Create a startup script that generates config and starts OpenClaw
20
+ RUN echo '#!/bin/bash\n\
21
+ # Generate minimal config if not exists\n\
22
+ if [ ! -f /root/.openclaw/openclaw.json ]; then\n\
23
+ cat > /root/.openclaw/openclaw.json << EOF\n\
24
+ {\n\
25
+ "gateway": {\n\
26
+ "bind": "0.0.0.0",\n\
27
+ "port": 7860,\n\
28
+ "controlUi": {\n\
29
+ "allowInsecureAuth": true\n\
30
+ }\n\
31
+ },\n\
32
+ "agents": {\n\
33
+ "defaults": {\n\
34
+ "model": "anthropic/claude-sonnet-4-20250514"\n\
35
+ }\n\
36
+ }\n\
37
+ }\n\
38
+ EOF\n\
39
+ fi\n\
40
+ \n\
41
+ # Start OpenClaw gateway\n\
42
+ exec openclaw gateway --port 7860 --verbose\n\
43
+ ' > /usr/local/bin/start-openclaw && chmod +x /usr/local/bin/start-openclaw
44
+
45
+ CMD ["/usr/local/bin/start-openclaw"]
README.md CHANGED
@@ -1,12 +1,112 @@
1
  ---
2
- title: OpenClaw Agent
3
- emoji: 👁
4
  colorFrom: pink
5
  colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
- short_description: OpenClaw
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: OpenClaw AI Assistant
3
+ emoji: 🦞
4
  colorFrom: pink
5
  colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
+ short_description: Your own personal AI assistant
10
  ---
11
 
12
+ # OpenClaw AI Assistant
13
+
14
+ > Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞
15
+
16
+ This Hugging Face Space runs the full **OpenClaw** gateway with Control UI and WebChat support.
17
+
18
+ ## Features
19
+
20
+ - 🎛️ **Control UI** - Web-based dashboard for managing your assistant
21
+ - 💬 **WebChat** - Browser-based chat interface
22
+ - 📡 **WebSocket Gateway** - Real-time communication
23
+ - 🔌 **Multi-Channel Support** - WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, and more
24
+ - 🤖 **Multi-Model Support** - Anthropic, OpenAI, Gemini, DeepSeek, OpenRouter, etc.
25
+ - 🛠️ **Tool Support** - Browser automation, Canvas, Nodes, Cron, and more
26
+
27
+ ## Quick Start
28
+
29
+ ### 1. Access the Control UI
30
+
31
+ Visit the Space URL to access the Control UI:
32
+ ```
33
+ https://sim4imgbed-openclaw.hf.space/
34
+ ```
35
+
36
+ ### 2. Configure API Keys
37
+
38
+ Set environment variables in the Space settings:
39
+
40
+ **For Anthropic Claude:**
41
+ ```
42
+ ANTHROPIC_AUTH_TOKEN=your-sk-ant-key
43
+ # or OAuth: ANTHROPIC_AUTH_REFRESH_TOKEN=your-refresh-token
44
+ ```
45
+
46
+ **For OpenAI:**
47
+ ```
48
+ OPENAI_KEY=your-sk-key
49
+ ```
50
+
51
+ **For Gemini:**
52
+ ```
53
+ GEMINI_KEY=your-api-key
54
+ ```
55
+
56
+ **For DeepSeek:**
57
+ ```
58
+ DEEPSEEK_KEY=your-api-key
59
+ ```
60
+
61
+ ### 3. Set Auth Token (Optional)
62
+
63
+ For secure access, set a token:
64
+ ```
65
+ GATEWAY_TOKEN=your-secret-token
66
+ ```
67
+
68
+ Then access with: `/?token=your-secret-token`
69
+
70
+ ## Channels Setup
71
+
72
+ OpenClaw supports multiple messaging channels. Configure them in the Control UI or via environment variables:
73
+
74
+ | Channel | Environment Variable |
75
+ |---------|---------------------|
76
+ | Discord | `DISCORD_TOKEN` |
77
+ | Slack | `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` |
78
+ | Telegram | `TELEGRAM_BOT_TOKEN` |
79
+ | WhatsApp | Configure via Control UI (pairing required) |
80
+ | Google Chat | Configure via Control UI |
81
+ | Signal | Configure via Control UI |
82
+
83
+ ## Configuration
84
+
85
+ The gateway uses `~/.openclaw/openclaw.json` for configuration. Key settings:
86
+
87
+ ```json
88
+ {
89
+ "gateway": {
90
+ "bind": "0.0.0.0",
91
+ "port": 7860,
92
+ "controlUi": {
93
+ "allowInsecureAuth": true
94
+ }
95
+ },
96
+ "agents": {
97
+ "defaults": {
98
+ "model": "anthropic/claude-sonnet-4-20250514"
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Documentation
105
+
106
+ - [Official Docs](https://docs.openclaw.ai/)
107
+ - [GitHub](https://github.com/openclaw/openclaw)
108
+ - [Discord](https://discord.gg/openclaw)
109
+
110
+ ## License
111
+
112
+ MIT
package.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "name": "openclaw",
3
- "private": true,
4
- "type": "module",
5
- "scripts": {
6
- "start": "node server.js"
7
- },
8
- "dependencies": {
9
- "express": "^4.19.2"
10
- }
11
- }
 
 
 
 
 
 
 
 
 
 
 
 
public/index.html DELETED
@@ -1,648 +0,0 @@
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>OpenClaw Market Research Agent</title>
7
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
8
- <style>
9
- * {
10
- margin: 0;
11
- padding: 0;
12
- box-sizing: border-box;
13
- }
14
-
15
- body {
16
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
17
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
18
- min-height: 100vh;
19
- padding: 20px;
20
- }
21
-
22
- .container {
23
- max-width: 1200px;
24
- margin: 0 auto;
25
- }
26
-
27
- .header {
28
- text-align: center;
29
- color: white;
30
- margin-bottom: 30px;
31
- }
32
-
33
- .header h1 {
34
- font-size: 2.5rem;
35
- margin-bottom: 10px;
36
- }
37
-
38
- .header p {
39
- font-size: 1.1rem;
40
- opacity: 0.9;
41
- }
42
-
43
- .card {
44
- background: white;
45
- border-radius: 16px;
46
- padding: 24px;
47
- margin-bottom: 20px;
48
- box-shadow: 0 10px 40px rgba(0,0,0,0.1);
49
- }
50
-
51
- .input-section {
52
- display: flex;
53
- gap: 12px;
54
- align-items: center;
55
- flex-wrap: wrap;
56
- }
57
-
58
- .input-group {
59
- flex: 1;
60
- min-width: 200px;
61
- }
62
-
63
- .input-group label {
64
- display: block;
65
- font-size: 0.9rem;
66
- color: #666;
67
- margin-bottom: 6px;
68
- }
69
-
70
- .input-group input,
71
- .input-group select {
72
- width: 100%;
73
- padding: 12px 16px;
74
- border: 2px solid #e0e0e0;
75
- border-radius: 10px;
76
- font-size: 1rem;
77
- transition: border-color 0.2s;
78
- }
79
-
80
- .input-group input:focus,
81
- .input-group select:focus {
82
- outline: none;
83
- border-color: #667eea;
84
- }
85
-
86
- .btn {
87
- padding: 12px 24px;
88
- border: none;
89
- border-radius: 10px;
90
- font-size: 1rem;
91
- font-weight: 600;
92
- cursor: pointer;
93
- transition: all 0.2s;
94
- }
95
-
96
- .btn-primary {
97
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
98
- color: white;
99
- }
100
-
101
- .btn-primary:hover {
102
- transform: translateY(-2px);
103
- box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
104
- }
105
-
106
- .btn-primary:disabled {
107
- opacity: 0.6;
108
- cursor: not-allowed;
109
- transform: none;
110
- }
111
-
112
- .loading {
113
- display: none;
114
- text-align: center;
115
- padding: 40px;
116
- }
117
-
118
- .loading.active {
119
- display: block;
120
- }
121
-
122
- .spinner {
123
- width: 50px;
124
- height: 50px;
125
- border: 4px solid #f3f3f3;
126
- border-top: 4px solid #667eea;
127
- border-radius: 50%;
128
- animation: spin 1s linear infinite;
129
- margin: 0 auto 20px;
130
- }
131
-
132
- @keyframes spin {
133
- 0% { transform: rotate(0deg); }
134
- 100% { transform: rotate(360deg); }
135
- }
136
-
137
- .results {
138
- display: none;
139
- }
140
-
141
- .results.active {
142
- display: block;
143
- }
144
-
145
- .results-header {
146
- display: flex;
147
- justify-content: space-between;
148
- align-items: center;
149
- margin-bottom: 20px;
150
- padding-bottom: 20px;
151
- border-bottom: 1px solid #e0e0e0;
152
- }
153
-
154
- .results-title {
155
- font-size: 1.5rem;
156
- color: #333;
157
- }
158
-
159
- .meta-info {
160
- display: flex;
161
- gap: 20px;
162
- font-size: 0.9rem;
163
- color: #666;
164
- }
165
-
166
- .meta-item {
167
- display: flex;
168
- align-items: center;
169
- gap: 6px;
170
- }
171
-
172
- .meta-badge {
173
- background: #e8f5e9;
174
- color: #2e7d32;
175
- padding: 4px 12px;
176
- border-radius: 20px;
177
- font-size: 0.85rem;
178
- font-weight: 600;
179
- }
180
-
181
- .summary-grid {
182
- display: grid;
183
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
184
- gap: 16px;
185
- margin-bottom: 24px;
186
- }
187
-
188
- .summary-card {
189
- background: linear-gradient(135deg, #f5f7fa 0%, #e4e8eb 100%);
190
- border-radius: 12px;
191
- padding: 20px;
192
- text-align: center;
193
- }
194
-
195
- .summary-label {
196
- font-size: 0.85rem;
197
- color: #666;
198
- margin-bottom: 8px;
199
- }
200
-
201
- .summary-value {
202
- font-size: 1.8rem;
203
- font-weight: 700;
204
- color: #333;
205
- }
206
-
207
- .summary-value.highlight {
208
- color: #667eea;
209
- }
210
-
211
- .chart-section {
212
- margin-bottom: 24px;
213
- }
214
-
215
- .chart-title {
216
- font-size: 1.1rem;
217
- color: #333;
218
- margin-bottom: 16px;
219
- }
220
-
221
- .chart-container {
222
- height: 300px;
223
- position: relative;
224
- }
225
-
226
- .data-table {
227
- width: 100%;
228
- border-collapse: collapse;
229
- margin-top: 16px;
230
- }
231
-
232
- .data-table th,
233
- .data-table td {
234
- padding: 12px;
235
- text-align: left;
236
- border-bottom: 1px solid #e0e0e0;
237
- }
238
-
239
- .data-table th {
240
- background: #f5f5f5;
241
- font-weight: 600;
242
- color: #333;
243
- }
244
-
245
- .data-table tr:hover {
246
- background: #f9f9f9;
247
- }
248
-
249
- .section-title {
250
- font-size: 1.2rem;
251
- color: #333;
252
- margin-bottom: 16px;
253
- display: flex;
254
- align-items: center;
255
- gap: 10px;
256
- }
257
-
258
- .section-title::before {
259
- content: '';
260
- width: 4px;
261
- height: 24px;
262
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
263
- border-radius: 2px;
264
- }
265
-
266
- .insights-grid {
267
- display: grid;
268
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
269
- gap: 16px;
270
- }
271
-
272
- .insight-card {
273
- background: #f9f9f9;
274
- border-radius: 12px;
275
- padding: 16px;
276
- }
277
-
278
- .insight-card h4 {
279
- color: #333;
280
- margin-bottom: 12px;
281
- font-size: 1rem;
282
- }
283
-
284
- .insight-list {
285
- list-style: none;
286
- }
287
-
288
- .insight-list li {
289
- padding: 6px 0;
290
- color: #666;
291
- font-size: 0.95rem;
292
- }
293
-
294
- .insight-list li::before {
295
- content: '•';
296
- color: #667eea;
297
- font-weight: bold;
298
- margin-right: 8px;
299
- }
300
-
301
- .error {
302
- display: none;
303
- background: #ffebee;
304
- color: #c62828;
305
- padding: 16px;
306
- border-radius: 10px;
307
- margin-top: 20px;
308
- }
309
-
310
- .error.active {
311
- display: block;
312
- }
313
-
314
- @media (max-width: 768px) {
315
- .header h1 {
316
- font-size: 1.8rem;
317
- }
318
-
319
- .input-section {
320
- flex-direction: column;
321
- }
322
-
323
- .input-group {
324
- width: 100%;
325
- }
326
-
327
- .btn {
328
- width: 100%;
329
- }
330
- }
331
- </style>
332
- </head>
333
- <body>
334
- <div class="container">
335
- <div class="header">
336
- <h1>OpenClaw Market Research</h1>
337
- <p>AI-powered market analysis and forecasting</p>
338
- </div>
339
-
340
- <div class="card">
341
- <div class="input-section">
342
- <div class="input-group">
343
- <label for="keyword">Market Keyword</label>
344
- <input type="text" id="keyword" placeholder="e.g., AI Healthcare, Electric Vehicles, Cloud Computing" value="AI Healthcare">
345
- </div>
346
- <div class="input-group">
347
- <label for="provider">AI Provider</label>
348
- <select id="provider">
349
- <option value="auto">Auto Select</option>
350
- <option value="gemini">Gemini</option>
351
- <option value="openai">OpenAI</option>
352
- <option value="claude">Claude</option>
353
- <option value="deepseek">DeepSeek</option>
354
- <option value="openrouter">OpenRouter</option>
355
- </select>
356
- </div>
357
- <button class="btn btn-primary" id="searchBtn" onclick="runResearch()">
358
- Generate Report
359
- </button>
360
- </div>
361
- </div>
362
-
363
- <div class="loading" id="loading">
364
- <div class="spinner"></div>
365
- <p>Analyzing market data and generating forecast...</p>
366
- </div>
367
-
368
- <div class="error" id="error"></div>
369
-
370
- <div class="results" id="results">
371
- <div class="card">
372
- <div class="results-header">
373
- <h2 class="results-title" id="marketTitle">Market Research Report</h2>
374
- <div class="meta-info">
375
- <span class="meta-badge" id="providerBadge">Gemini</span>
376
- <span class="meta-item" id="timestamp"></span>
377
- </div>
378
- </div>
379
-
380
- <div class="summary-grid">
381
- <div class="summary-card">
382
- <div class="summary-label">2023 Market Size</div>
383
- <div class="summary-value" id="past2023">$0B</div>
384
- </div>
385
- <div class="summary-card">
386
- <div class="summary-label">2025 Market Size</div>
387
- <div class="summary-value highlight" id="current2025">$0B</div>
388
- </div>
389
- <div class="summary-card">
390
- <div class="summary-label">2033 Forecast</div>
391
- <div class="summary-value" id="forecast2033">$0B</div>
392
- </div>
393
- <div class="summary-card">
394
- <div class="summary-label">CAGR</div>
395
- <div class="summary-value highlight" id="cagr">0%</div>
396
- </div>
397
- </div>
398
-
399
- <div class="chart-section">
400
- <h3 class="chart-title">Market Forecast (2023-2033)</h3>
401
- <div class="chart-container">
402
- <canvas id="forecastChart"></canvas>
403
- </div>
404
- </div>
405
-
406
- <div class="chart-section">
407
- <h3 class="chart-title">Regional Market Distribution</h3>
408
- <div class="chart-container">
409
- <canvas id="regionalChart"></canvas>
410
- </div>
411
- </div>
412
-
413
- <h3 class="section-title">Regional Analysis</h3>
414
- <table class="data-table" id="regionalTable">
415
- <thead>
416
- <tr>
417
- <th>Region</th>
418
- <th>Market Share</th>
419
- <th>CAGR</th>
420
- </tr>
421
- </thead>
422
- <tbody id="regionalBody"></tbody>
423
- </table>
424
-
425
- <h3 class="section-title" style="margin-top: 24px;">Competitive Landscape</h3>
426
- <table class="data-table">
427
- <thead>
428
- <tr>
429
- <th>Company</th>
430
- <th>Market Share</th>
431
- </tr>
432
- </thead>
433
- <tbody id="competitiveBody"></tbody>
434
- </table>
435
-
436
- <h3 class="section-title" style="margin-top: 24px;">Key Insights</h3>
437
- <div class="insights-grid">
438
- <div class="insight-card">
439
- <h4>Opportunities</h4>
440
- <ul class="insight-list" id="opportunitiesList"></ul>
441
- </div>
442
- <div class="insight-card">
443
- <h4>Challenges</h4>
444
- <ul class="insight-list" id="challengesList"></ul>
445
- </div>
446
- <div class="insight-card">
447
- <h4>Market Drivers</h4>
448
- <ul class="insight-list" id="driversList"></ul>
449
- </div>
450
- </div>
451
- </div>
452
- </div>
453
- </div>
454
-
455
- <script>
456
- let forecastChart = null;
457
- let regionalChart = null;
458
-
459
- async function runResearch() {
460
- const keyword = document.getElementById('keyword').value.trim();
461
- const provider = document.getElementById('provider').value;
462
-
463
- if (!keyword) {
464
- showError('Please enter a market keyword');
465
- return;
466
- }
467
-
468
- const loading = document.getElementById('loading');
469
- const results = document.getElementById('results');
470
- const error = document.getElementById('error');
471
- const btn = document.getElementById('searchBtn');
472
-
473
- loading.classList.add('active');
474
- results.classList.remove('active');
475
- error.classList.remove('active');
476
- btn.disabled = true;
477
-
478
- try {
479
- const response = await fetch('/run', {
480
- method: 'POST',
481
- headers: {
482
- 'Content-Type': 'application/json'
483
- },
484
- body: JSON.stringify({
485
- task: 'market_research',
486
- provider: provider === 'auto' ? undefined : provider,
487
- keyword: keyword
488
- })
489
- });
490
-
491
- const data = await response.json();
492
-
493
- if (!response.ok || data.error) {
494
- throw new Error(data.error || data.details || 'Request failed');
495
- }
496
-
497
- displayResults(data);
498
- results.classList.add('active');
499
- } catch (err) {
500
- showError(err.message);
501
- } finally {
502
- loading.classList.remove('active');
503
- btn.disabled = false;
504
- }
505
- }
506
-
507
- function displayResults(data) {
508
- const dashboard = data.dashboard_view;
509
-
510
- // Header
511
- document.getElementById('marketTitle').textContent = dashboard.marketTitle;
512
- document.getElementById('providerBadge').textContent = data.meta.provider_used || 'Auto';
513
- document.getElementById('timestamp').textContent = new Date(data.meta.timestamp).toLocaleString();
514
-
515
- // Summary
516
- document.getElementById('past2023').textContent = `$${dashboard.marketSummary.past2023}B`;
517
- document.getElementById('current2025').textContent = `$${dashboard.marketSummary.current2025}B`;
518
- document.getElementById('forecast2033').textContent = `$${dashboard.marketSummary.forecast2033}B`;
519
- document.getElementById('cagr').textContent = `${dashboard.marketSummary.cagr}%`;
520
-
521
- // Forecast Chart
522
- updateForecastChart(dashboard.forecast);
523
-
524
- // Regional Chart
525
- updateRegionalChart(dashboard.regional);
526
-
527
- // Regional Table
528
- const regionalBody = document.getElementById('regionalBody');
529
- regionalBody.innerHTML = dashboard.regional.map(r => `
530
- <tr>
531
- <td>${r.region}</td>
532
- <td>${r.share}%</td>
533
- <td>${r.cagr}%</td>
534
- </tr>
535
- `).join('');
536
-
537
- // Competitive Table
538
- const competitiveBody = document.getElementById('competitiveBody');
539
- competitiveBody.innerHTML = dashboard.competitive.map(c => `
540
- <tr>
541
- <td>${c.company}</td>
542
- <td>${c.share}%</td>
543
- </tr>
544
- `).join('');
545
-
546
- // Insights
547
- document.getElementById('opportunitiesList').innerHTML = dashboard.insights.keyOpportunities.map(o => `<li>${o}</li>`).join('');
548
- document.getElementById('challengesList').innerHTML = dashboard.insights.majorChallenges.map(c => `<li>${c}</li>`).join('');
549
- document.getElementById('driversList').innerHTML = dashboard.drivers.map(d => `<li>${d.driver} (Impact: ${d.impact})</li>`).join('');
550
- }
551
-
552
- function updateForecastChart(forecast) {
553
- const ctx = document.getElementById('forecastChart').getContext('2d');
554
-
555
- if (forecastChart) {
556
- forecastChart.destroy();
557
- }
558
-
559
- forecastChart = new Chart(ctx, {
560
- type: 'line',
561
- data: {
562
- labels: forecast.map(f => f.year),
563
- datasets: [{
564
- label: 'Market Size (Billion USD)',
565
- data: forecast.map(f => f.value),
566
- borderColor: '#667eea',
567
- backgroundColor: 'rgba(102, 126, 234, 0.1)',
568
- fill: true,
569
- tension: 0.4,
570
- pointBackgroundColor: '#667eea',
571
- pointBorderColor: '#fff',
572
- pointBorderWidth: 2,
573
- pointRadius: 6
574
- }]
575
- },
576
- options: {
577
- responsive: true,
578
- maintainAspectRatio: false,
579
- plugins: {
580
- legend: {
581
- display: false
582
- }
583
- },
584
- scales: {
585
- y: {
586
- beginAtZero: true,
587
- grid: {
588
- color: 'rgba(0,0,0,0.05)'
589
- }
590
- },
591
- x: {
592
- grid: {
593
- display: false
594
- }
595
- }
596
- }
597
- }
598
- });
599
- }
600
-
601
- function updateRegionalChart(regional) {
602
- const ctx = document.getElementById('regionalChart').getContext('2d');
603
-
604
- if (regionalChart) {
605
- regionalChart.destroy();
606
- }
607
-
608
- regionalChart = new Chart(ctx, {
609
- type: 'doughnut',
610
- data: {
611
- labels: regional.map(r => r.region),
612
- datasets: [{
613
- data: regional.map(r => r.share),
614
- backgroundColor: [
615
- '#667eea',
616
- '#764ba2',
617
- '#f093fb',
618
- '#4facfe',
619
- '#43e97b'
620
- ],
621
- borderWidth: 0
622
- }]
623
- },
624
- options: {
625
- responsive: true,
626
- maintainAspectRatio: false,
627
- plugins: {
628
- legend: {
629
- position: 'right'
630
- }
631
- }
632
- }
633
- });
634
- }
635
-
636
- function showError(message) {
637
- const error = document.getElementById('error');
638
- error.textContent = `Error: ${message}`;
639
- error.classList.add('active');
640
- }
641
-
642
- // Run on Enter key
643
- document.getElementById('keyword').addEventListener('keypress', (e) => {
644
- if (e.key === 'Enter') runResearch();
645
- });
646
- </script>
647
- </body>
648
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server.js DELETED
@@ -1,222 +0,0 @@
1
- import express from "express";
2
- import { spawn } from "child_process";
3
- import { fileURLToPath } from "url";
4
- import { dirname, join } from "path";
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = dirname(__filename);
8
-
9
- const app = express();
10
-
11
- // Serve static files from public directory
12
- app.use(express.static(join(__dirname, "public")));
13
- app.use(express.json());
14
-
15
- /* ===============================
16
- 1. BASIC AUTH
17
- ================================ */
18
-
19
- const GATE_KEY = process.env.OPENCLAW_GATE_KEY;
20
-
21
- app.use((req, res, next) => {
22
- if (!GATE_KEY) return next();
23
- if (req.headers["x-openclaw-key"] !== GATE_KEY) {
24
- return res.status(401).json({ error: "Unauthorized" });
25
- }
26
- next();
27
- });
28
-
29
- /* ===============================
30
- 2. RATE LIMITING (IP-based)
31
- ================================ */
32
-
33
- const RATE_LIMIT = 30; // requests
34
- const WINDOW_MS = 60_000;
35
- const ipHits = new Map();
36
-
37
- app.use((req, res, next) => {
38
- const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
39
- const now = Date.now();
40
-
41
- const record = ipHits.get(ip) || { count: 0, ts: now };
42
- if (now - record.ts > WINDOW_MS) {
43
- record.count = 0;
44
- record.ts = now;
45
- }
46
-
47
- record.count++;
48
- ipHits.set(ip, record);
49
-
50
- if (record.count > RATE_LIMIT) {
51
- return res.status(429).json({ error: "Rate limit exceeded" });
52
- }
53
- next();
54
- });
55
-
56
- /* ===============================
57
- 3. LOAD KEY POOLS
58
- ================================ */
59
-
60
- function loadPool(prefix) {
61
- return Object.keys(process.env)
62
- .filter(k => k.startsWith(prefix))
63
- .map(k => process.env[k])
64
- .filter(Boolean);
65
- }
66
-
67
- const PROVIDERS = {
68
- openai: loadPool("OPENAI_API_KEY_"),
69
- deepseek: loadPool("DEEPSEEK_API_KEY_"),
70
- gemini: loadPool("GEMINI_API_KEY_"),
71
- openrouter: loadPool("OPENROUTER_API_KEY_"),
72
- dashscope: loadPool("DASHSCOPE_API_KEY_"),
73
- claude: loadPool("CLAUDE_API_KEY_")
74
- };
75
-
76
- function randomKey(pool) {
77
- return pool[Math.floor(Math.random() * pool.length)];
78
- }
79
-
80
- /* ===============================
81
- 4. TASK ROUTING
82
- ================================ */
83
-
84
- const TASKS = new Set([
85
- "market_research",
86
- "summarize",
87
- "classify"
88
- ]);
89
-
90
- /* ===============================
91
- 5. RUN AGENT
92
- ================================ */
93
- app.use((req, res, next) => {
94
- if (req.method === "POST" && !req.is("application/json")) {
95
- return res.status(400).json({ error: "JSON body required" });
96
- }
97
- next();
98
- });
99
-
100
- app.post("/run", async (req, res) => {
101
- const { task, provider, model, ...payload } = req.body;
102
-
103
- if (!TASKS.has(task)) {
104
- return res.status(400).json({ error: "Unknown task" });
105
- }
106
-
107
- const providersToTry = provider
108
- ? [provider, ...Object.keys(PROVIDERS).filter(p => p !== provider)]
109
- : Object.keys(PROVIDERS);
110
-
111
- let lastError;
112
-
113
- for (const p of providersToTry) {
114
- const pool = PROVIDERS[p];
115
- if (!pool || pool.length === 0) continue;
116
-
117
- for (let i = 0; i < pool.length; i++) {
118
- const apiKey = randomKey(pool);
119
-
120
- const env = {
121
- ...process.env,
122
- OPENCLAW_PROVIDER: p,
123
- OPENCLAW_MODEL: model || "",
124
- OPENCLAW_API_KEY: apiKey,
125
- OPENCLAW_TASK: task
126
- };
127
-
128
- try {
129
- const result = await runOpenClaw(env, payload);
130
- return res.json(result);
131
- } catch (err) {
132
- lastError = err;
133
- if (!isRateLimit(err)) break;
134
- }
135
- }
136
- }
137
-
138
- res.status(500).json({
139
- error: "All providers failed",
140
- details: lastError?.message
141
- });
142
- });
143
-
144
- /* ===============================
145
- 6. EXECUTION + JSON PARSE
146
- ================================ */
147
-
148
- function runOpenClaw(env, payload) {
149
- return new Promise((resolve, reject) => {
150
- const proc = spawn("node", ["src/index.js"], {
151
- cwd: "/app",
152
- env
153
- });
154
-
155
- let out = "";
156
- let err = "";
157
-
158
- const timeout = setTimeout(() => {
159
- proc.kill("SIGKILL");
160
- reject(new Error("Agent timeout"));
161
- }, 120000); // 2 minutes
162
-
163
- proc.stdout.on("data", d => out += d.toString());
164
- proc.stderr.on("data", d => err += d.toString());
165
-
166
- proc.on("close", code => {
167
- clearTimeout(timeout);
168
-
169
- if (code !== 0) {
170
- return reject(new Error(err || "Execution failed"));
171
- }
172
-
173
- try {
174
- const json = JSON.parse(out);
175
- resolve(json);
176
- } catch {
177
- reject(new Error("Invalid JSON output from agent"));
178
- }
179
- });
180
-
181
- proc.stdin.write(JSON.stringify(payload));
182
- proc.stdin.end();
183
- });
184
- }
185
-
186
-
187
- function isRateLimit(err) {
188
- const msg = err.message?.toLowerCase() || "";
189
- return (
190
- msg.includes("429") ||
191
- msg.includes("rate") ||
192
- msg.includes("quota") ||
193
- msg.includes("limit")
194
- );
195
- }
196
-
197
-
198
- /* ===============================
199
- 7. HEALTH CHECK
200
- ================================ */
201
-
202
- app.get("/health", (req, res) => {
203
- res.json({ status: "ok", service: "openclaw-agent" });
204
- });
205
-
206
- app.get("/", (req, res) => {
207
- res.json({
208
- service: "OpenClaw Agent Gateway",
209
- endpoints: {
210
- health: "GET /health",
211
- run: "POST /run"
212
- }
213
- });
214
- });
215
-
216
- /* ===============================
217
- 8. START
218
- ================================ */
219
-
220
- app.listen(7860, () => {
221
- console.log("🚀 OpenClaw Agent Gateway running");
222
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/index.js DELETED
@@ -1,33 +0,0 @@
1
- import { marketResearchAgent } from "./marketResearchAgent.js";
2
-
3
- function readStdin() {
4
- return new Promise(resolve => {
5
- let data = "";
6
- process.stdin.on("data", chunk => data += chunk);
7
- process.stdin.on("end", () => resolve(data));
8
- });
9
- }
10
-
11
- (async () => {
12
- try {
13
- const input = JSON.parse(await readStdin());
14
-
15
- const result = await marketResearchAgent({
16
- input,
17
- provider: process.env.OPENCLAW_PROVIDER,
18
- model: process.env.OPENCLAW_MODEL
19
- });
20
-
21
- // CRITICAL: JSON ONLY
22
- console.log(JSON.stringify(result));
23
- } catch (e) {
24
- console.log(JSON.stringify({
25
- meta: {
26
- status: "failed",
27
- error: e.message
28
- },
29
- dashboard_view: {},
30
- report_view: {}
31
- }));
32
- }
33
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/llm/callLLM.js DELETED
@@ -1,72 +0,0 @@
1
- import { getProviderConfig } from "./providers.js";
2
-
3
- export async function callLLM({ provider, model, prompt }) {
4
- const cfg = getProviderConfig(provider, model);
5
-
6
- let body;
7
-
8
- switch (cfg.name) {
9
- case "gemini":
10
- body = {
11
- contents: [{ parts: [{ text: prompt }] }]
12
- };
13
- break;
14
-
15
- case "dashscope":
16
- body = {
17
- model: cfg.model,
18
- input: { prompt }
19
- };
20
- break;
21
-
22
- case "claude":
23
- body = {
24
- model: cfg.model,
25
- max_tokens: 4096,
26
- messages: [
27
- { role: "user", content: prompt }
28
- ],
29
- system: "You are a market research analyst."
30
- };
31
- break;
32
-
33
- default:
34
- body = {
35
- model: cfg.model,
36
- messages: [
37
- { role: "system", content: "You are a market research analyst." },
38
- { role: "user", content: prompt }
39
- ],
40
- temperature: 0.4
41
- };
42
- }
43
-
44
- const res = await fetch(cfg.baseUrl, {
45
- method: "POST",
46
- headers: cfg.headers,
47
- body: JSON.stringify(body)
48
- });
49
-
50
- const text = await res.text();
51
-
52
- if (!res.ok) {
53
- throw new Error(`LLM error ${res.status}: ${text}`);
54
- }
55
-
56
- const json = JSON.parse(text);
57
-
58
- // Normalize output
59
- if (cfg.name === "gemini") {
60
- return json.candidates?.[0]?.content?.parts?.[0]?.text || "";
61
- }
62
-
63
- if (cfg.name === "dashscope") {
64
- return json.output?.text || "";
65
- }
66
-
67
- if (cfg.name === "claude") {
68
- return json.content?.[0]?.text || "";
69
- }
70
-
71
- return json.choices?.[0]?.message?.content || "";
72
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/llm/providers.js DELETED
@@ -1,94 +0,0 @@
1
- export function getProviderConfig(provider, model) {
2
- const apiKey = process.env.OPENCLAW_API_KEY;
3
-
4
- if (!apiKey) {
5
- throw new Error("OPENCLAW_API_KEY not set");
6
- }
7
-
8
- // Support custom base URLs via environment variables
9
- // Format: OPENCLAW_<PROVIDER>_BASE_URL
10
- const customBaseUrl = process.env[`OPENCLAW_${provider.toUpperCase()}_BASE_URL`];
11
-
12
- switch (provider) {
13
- case "openai":
14
- return {
15
- name: "openai",
16
- baseUrl: customBaseUrl || "https://api.openai.com/v1/chat/completions",
17
- model: model || "gpt-4.1-mini",
18
- headers: {
19
- "Authorization": `Bearer ${apiKey}`,
20
- "Content-Type": "application/json"
21
- }
22
- };
23
-
24
- case "deepseek":
25
- return {
26
- name: "deepseek",
27
- baseUrl: customBaseUrl || "https://api.deepseek.com/chat/completions",
28
- model: model || "deepseek-chat",
29
- headers: {
30
- "Authorization": `Bearer ${apiKey}`,
31
- "Content-Type": "application/json"
32
- }
33
- };
34
-
35
- case "gemini":
36
- if (customBaseUrl) {
37
- return {
38
- name: "gemini",
39
- baseUrl: `${customBaseUrl}/${model || "gemini-1.5-pro"}:generateContent?key=${apiKey}`,
40
- model,
41
- headers: {
42
- "Content-Type": "application/json"
43
- }
44
- };
45
- }
46
- return {
47
- name: "gemini",
48
- baseUrl: `https://generativelanguage.googleapis.com/v1beta/models/${model || "gemini-1.5-pro"}:generateContent?key=${apiKey}`,
49
- model,
50
- headers: {
51
- "Content-Type": "application/json"
52
- }
53
- };
54
-
55
- case "openrouter":
56
- return {
57
- name: "openrouter",
58
- baseUrl: customBaseUrl || "https://openrouter.ai/api/v1/chat/completions",
59
- model: model || "openai/gpt-4o-mini",
60
- headers: {
61
- "Authorization": `Bearer ${apiKey}`,
62
- "Content-Type": "application/json",
63
- "HTTP-Referer": "https://sim4imgbed-openclaw.hf.space",
64
- "X-Title": "OpenClaw Market Research Agent"
65
- }
66
- };
67
-
68
- case "dashscope":
69
- return {
70
- name: "dashscope",
71
- baseUrl: customBaseUrl || "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation",
72
- model: model || "qwen-plus",
73
- headers: {
74
- "Authorization": `Bearer ${apiKey}`,
75
- "Content-Type": "application/json"
76
- }
77
- };
78
-
79
- case "claude":
80
- return {
81
- name: "claude",
82
- baseUrl: customBaseUrl || "https://api.anthropic.com/v1/messages",
83
- model: model || "claude-sonnet-4-20250514",
84
- headers: {
85
- "x-api-key": apiKey,
86
- "anthropic-version": "2023-06-01",
87
- "Content-Type": "application/json"
88
- }
89
- };
90
-
91
- default:
92
- throw new Error(`Unsupported provider: ${provider}`);
93
- }
94
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/marketResearchAgent.js DELETED
@@ -1,266 +0,0 @@
1
- import crypto from "crypto";
2
-
3
- /* =========================================================
4
- PUBLIC ENTRY
5
- ========================================================= */
6
-
7
- export async function marketResearchAgent({ input, provider, model }) {
8
- if (!input?.keyword) {
9
- throw new Error("keyword is required");
10
- }
11
-
12
- const keyword = normalizeKeyword(input.keyword);
13
- const normalizedTitle = `Global ${keyword} Market and Forecast 2026-2033`;
14
-
15
- /* =======================================================
16
- 1. GENERATE CORE MARKET ESTIMATES (LLM or heuristic)
17
- ======================================================= */
18
-
19
- // NOTE:
20
- // Replace this block with a real LLM call later.
21
- // This placeholder is deterministic + schema-safe.
22
- const baseMarket2023 = randomFloat(0.5, 5.0);
23
- const cagr = randomFloat(8, 22);
24
- const market2025 = round(baseMarket2023 * Math.pow(1 + cagr / 100, 2));
25
- const market2033 = round(baseMarket2023 * Math.pow(1 + cagr / 100, 10));
26
-
27
- /* =======================================================
28
- 2. BUILD DASHBOARD VIEW
29
- ======================================================= */
30
-
31
- const dashboard_view = {
32
- marketTitle: normalizedTitle,
33
-
34
- marketSummary: {
35
- past2023: baseMarket2023,
36
- current2025: market2025,
37
- forecast2033: market2033,
38
- cagr
39
- },
40
-
41
- forecast: [
42
- { year: "2023", value: baseMarket2023 },
43
- { year: "2025", value: market2025 },
44
- { year: "2033", value: market2033 }
45
- ],
46
-
47
- regional: buildRegions(),
48
-
49
- competitive: buildTopPlayers(),
50
-
51
- segments: buildSegments(),
52
-
53
- drivers: [
54
- { driver: "Technology adoption", impact: 92 },
55
- { driver: "Rising healthcare demand", impact: 88 },
56
- { driver: "Digital transformation", impact: 85 }
57
- ],
58
-
59
- insights: {
60
- largestSegment2025: "Primary Segment",
61
- fastestGrowingSegment: "AI-Enabled Solutions",
62
- keyOpportunities: [
63
- "Emerging markets",
64
- "AI integration",
65
- "Cost-efficient platforms"
66
- ],
67
- majorChallenges: [
68
- "Regulatory approvals",
69
- "High initial investment",
70
- "Skilled workforce shortage"
71
- ]
72
- },
73
-
74
- citation: "iHealthcareAnalyst, Inc. Internal Data Analysis",
75
- citationUrl: "https://www.ihealthcareanalyst.com"
76
- };
77
-
78
- /* =======================================================
79
- 3. BUILD REPORT VIEW
80
- ======================================================= */
81
-
82
- const report_view = {
83
- marketTitle: keyword,
84
-
85
- marketSizing: {
86
- pastYear_2023: baseMarket2023,
87
- currentYear_2025: market2025,
88
- forecastYear_2033: market2033,
89
- global_cagr_Forecast: cagr
90
- },
91
-
92
- executiveOverview:
93
- `The ${keyword} market is experiencing accelerated growth driven by technological innovation, increasing healthcare demand, and expanding adoption across developed and emerging economies.`,
94
-
95
- marketSegments: [
96
- {
97
- segmentName: "Primary Segment",
98
- subSegments: [
99
- {
100
- subSegmentName: "Core Products",
101
- segmet_marketShare_2023: 40,
102
- segment_marketShare_2025: 45,
103
- segment_marketShare_2033: 50,
104
- segmentName_cagr_Forecast: round(cagr + 2)
105
- }
106
- ]
107
- }
108
- ],
109
-
110
- marketDrivers: [
111
- "Growing demand for advanced healthcare solutions",
112
- "Increased investment in digital health",
113
- "Favorable government initiatives"
114
- ],
115
-
116
- emergingTrends: [
117
- "AI-driven diagnostics",
118
- "Cloud-based healthcare platforms",
119
- "Personalized medicine"
120
- ],
121
-
122
- competitiveLandscape: [
123
- {
124
- company: "Company A",
125
- player_markerShare_2025: 18,
126
- positioning: "Market leader with diversified portfolio"
127
- },
128
- {
129
- company: "Company B",
130
- player_markerShare_2025: 14,
131
- positioning: "Strong innovation focus"
132
- }
133
- ],
134
-
135
- regulatoryEnvironment:
136
- "The market is regulated by stringent healthcare and data protection regulations, with increasing emphasis on patient safety and clinical validation.",
137
-
138
- geographicAnalysis:
139
- "North America leads the market due to early adoption, while Asia-Pacific is expected to witness the fastest growth driven by expanding healthcare infrastructure.",
140
-
141
- futureOutlook:
142
- "The market is projected to grow steadily through 2033, supported by innovation, demographic trends, and increased healthcare spending.",
143
-
144
- strategicRecommendations: [
145
- "Invest in AI-enabled platforms",
146
- "Expand presence in emerging markets",
147
- "Strengthen regulatory compliance capabilities"
148
- ]
149
- };
150
-
151
- /* =======================================================
152
- 4. META + FINAL ASSEMBLY
153
- ======================================================= */
154
-
155
- const result = {
156
- meta: {
157
- job_id: input.job_id || `job_${crypto.randomUUID()}`,
158
- keyword,
159
- normalized_title: normalizedTitle,
160
- status: "completed",
161
- timestamp: new Date().toISOString(),
162
- provider_used: provider || "auto",
163
- model_used: model || "auto",
164
- confidence: "medium"
165
- },
166
- dashboard_view,
167
- report_view
168
- };
169
-
170
- /* =======================================================
171
- 5. SCHEMA ENFORCEMENT (STEP A5)
172
- ======================================================= */
173
-
174
- validateDashboard(result.dashboard_view);
175
- validateReport(result.report_view);
176
-
177
- return result;
178
- }
179
-
180
- /* =========================================================
181
- SCHEMA VALIDATION
182
- ========================================================= */
183
-
184
- function validateDashboard(d) {
185
- if (!d.marketTitle) throw new Error("dashboard_view.marketTitle missing");
186
- if (!d.marketSummary) throw new Error("dashboard_view.marketSummary missing");
187
-
188
- const ms = d.marketSummary;
189
- requireNumber(ms.past2023, "past2023");
190
- requireNumber(ms.current2025, "current2025");
191
- requireNumber(ms.forecast2033, "forecast2033");
192
- requireNumber(ms.cagr, "cagr");
193
-
194
- if (!Array.isArray(d.forecast)) throw new Error("forecast must be array");
195
- if (!Array.isArray(d.segments)) throw new Error("segments must be array");
196
- }
197
-
198
- function validateReport(r) {
199
- if (!r.marketTitle) throw new Error("report_view.marketTitle missing");
200
- if (!r.marketSizing) throw new Error("marketSizing missing");
201
-
202
- const ms = r.marketSizing;
203
- requireNumber(ms.pastYear_2023, "pastYear_2023");
204
- requireNumber(ms.currentYear_2025, "currentYear_2025");
205
- requireNumber(ms.forecastYear_2033, "forecastYear_2033");
206
- requireNumber(ms.global_cagr_Forecast, "global_cagr_Forecast");
207
-
208
- if (!Array.isArray(r.marketSegments)) {
209
- throw new Error("marketSegments must be array");
210
- }
211
- }
212
-
213
- /* =========================================================
214
- HELPERS
215
- ========================================================= */
216
-
217
- function normalizeKeyword(k) {
218
- return k.replace(/market/gi, "").trim();
219
- }
220
-
221
- function round(n) {
222
- return Math.round(n * 100) / 100;
223
- }
224
-
225
- function randomFloat(min, max) {
226
- return round(min + Math.random() * (max - min));
227
- }
228
-
229
- function requireNumber(v, name) {
230
- if (typeof v !== "number" || Number.isNaN(v)) {
231
- throw new Error(`${name} must be a number`);
232
- }
233
- }
234
-
235
- function buildRegions() {
236
- return [
237
- { region: "North America", share: 42, cagr: 14.2 },
238
- { region: "Europe", share: 28, cagr: 12.8 },
239
- { region: "Asia Pacific", share: 22, cagr: 18.6 },
240
- { region: "Latin America", share: 5, cagr: 11.3 },
241
- { region: "Middle East & Africa", share: 3, cagr: 10.9 }
242
- ];
243
- }
244
-
245
- function buildTopPlayers() {
246
- return [
247
- { company: "Company A", share: 18 },
248
- { company: "Company B", share: 15 },
249
- { company: "Company C", share: 12 },
250
- { company: "Company D", share: 10 },
251
- { company: "Company E", share: 8 }
252
- ];
253
- }
254
-
255
- function buildSegments() {
256
- return [
257
- {
258
- segment: "Primary Segment",
259
- subSegments: [
260
- { name: "Core Products", share2025: 45, cagr: 16.5 },
261
- { name: "AI Solutions", share2025: 35, cagr: 19.2 },
262
- { name: "Services", share2025: 20, cagr: 13.1 }
263
- ]
264
- }
265
- ];
266
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/schema/dashboard.schema.js DELETED
@@ -1,39 +0,0 @@
1
- export function validateDashboard(d) {
2
- if (!d) throw new Error("dashboard_view missing");
3
-
4
- if (!d.marketTitle) {
5
- throw new Error("dashboard_view.marketTitle missing");
6
- }
7
-
8
- if (!d.marketSummary) {
9
- throw new Error("dashboard_view.marketSummary missing");
10
- }
11
-
12
- const ms = d.marketSummary;
13
- requireNumber(ms.past2023, "past2023");
14
- requireNumber(ms.current2025, "current2025");
15
- requireNumber(ms.forecast2033, "forecast2033");
16
- requireNumber(ms.cagr, "cagr");
17
-
18
- if (!Array.isArray(d.forecast)) {
19
- throw new Error("dashboard_view.forecast must be array");
20
- }
21
-
22
- if (!Array.isArray(d.regional)) {
23
- throw new Error("dashboard_view.regional must be array");
24
- }
25
-
26
- if (!Array.isArray(d.segments)) {
27
- throw new Error("dashboard_view.segments must be array");
28
- }
29
-
30
- if (!Array.isArray(d.competitive)) {
31
- throw new Error("dashboard_view.competitive must be array");
32
- }
33
- }
34
-
35
- function requireNumber(value, name) {
36
- if (typeof value !== "number" || Number.isNaN(value)) {
37
- throw new Error(`dashboard_view.marketSummary.${name} must be number`);
38
- }
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/schema/report.schema.js DELETED
@@ -1,35 +0,0 @@
1
- export function validateReport(r) {
2
- if (!r) throw new Error("report_view missing");
3
-
4
- if (!r.marketTitle) {
5
- throw new Error("report_view.marketTitle missing");
6
- }
7
-
8
- if (!r.marketSizing) {
9
- throw new Error("report_view.marketSizing missing");
10
- }
11
-
12
- const ms = r.marketSizing;
13
- requireNumber(ms.pastYear_2023, "pastYear_2023");
14
- requireNumber(ms.currentYear_2025, "currentYear_2025");
15
- requireNumber(ms.forecastYear_2033, "forecastYear_2033");
16
- requireNumber(ms.global_cagr_Forecast, "global_cagr_Forecast");
17
-
18
- if (!Array.isArray(r.marketSegments)) {
19
- throw new Error("report_view.marketSegments must be array");
20
- }
21
-
22
- if (!Array.isArray(r.marketDrivers)) {
23
- throw new Error("report_view.marketDrivers must be array");
24
- }
25
-
26
- if (!Array.isArray(r.strategicRecommendations)) {
27
- throw new Error("report_view.strategicRecommendations must be array");
28
- }
29
- }
30
-
31
- function requireNumber(value, name) {
32
- if (typeof value !== "number" || Number.isNaN(value)) {
33
- throw new Error(`report_view.marketSizing.${name} must be number`);
34
- }
35
- }