Pepguy commited on
Commit
7517a94
·
verified ·
1 Parent(s): d1fe88e

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +252 -72
app.js CHANGED
@@ -1,84 +1,264 @@
1
- const express = require('express');
2
- const cors = require('cors');
3
- // require('dotenv').config();
 
4
 
5
- const { hydrateMemory, searchTools } = require('./db');
6
- const { runSmartScraper } = require('./scraper');
7
- const { formatResponse } = require('./translator');
8
 
9
  const app = express();
 
 
10
  app.use(express.json());
11
- app.use(cors()); // Allow requests from any frontend
12
-
13
- // 1. The Runtime Search API (Public)
14
- // POST /v1/search
15
- app.post('/v1/search', (req, res) => {
16
- const { query, format, limit = 5 } = req.body;
17
-
18
- if (!query) {
19
- return res.status(400).json({ error: "Query is required" });
20
  }
 
21
 
22
- // A. Fast In-Memory Search
23
- // We fetch a bit more than needed to filter for quality later
24
- let results = searchTools(query);
25
-
26
- // Sort by "Stars" (Rudimentary Reputation V1)
27
- results.sort((a, b) => (b.stats?.stars || 0) - (a.stats?.stars || 0));
28
-
29
- // Slice to limit
30
- const topResults = results.slice(0, limit);
31
-
32
- // B. Apply Translation
33
- const formattedTools = topResults.map(tool => formatResponse(tool, format));
34
-
35
- // C. Log Analytics (Async - don't await)
36
- // TODO: Increment 'views' count in Firestore for these tool IDs
37
-
38
- res.json({
39
- meta: {
40
- query,
41
- count: formattedTools.length,
42
- target_format: format || 'agentq-json'
43
- },
44
- tools: formattedTools
45
- });
46
  });
47
 
48
- // 2. The Fire-and-Forget Scraper Trigger (Admin Only)
49
- // POST /admin/trigger-scrape
50
- app.post('/admin/trigger-scrape', (req, res) => {
51
-
52
- /* const secret = req.headers['x-admin-secret'];
53
-
54
- if (secret !==
55
- // process.env.
56
- ADMIN_SECRET) {
57
- return res.status(401).json({ error: "Unauthorized" });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  }
59
- */
60
- console.log("🔥 Manual Scrape Triggered");
61
-
62
- // EXECUTE WITHOUT AWAITING
63
- // This allows the response to return immediately
64
- runSmartScraper()
65
- .then(() => console.log("✅ Background Job Finished"))
66
- .catch(err => console.error("❌ Background Job Failed", err));
67
-
68
- res.json({
69
- status: "accepted",
70
- message: "Scraping job started in background."
71
- });
72
  });
73
 
74
- // Start Server
75
- const PORT = process.env.PORT || 7860;
76
- app.listen(PORT, async () => {
77
- console.log(`🚀 AgentQ Core running on port ${PORT}`);
78
-
79
- // Initial Hydration on Boot
80
- await hydrateMemory();
81
-
82
- // Optional: Run scraper on boot if DB is empty
83
- // runSmartScraper();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  });
 
1
+ import express from 'express';
2
+ import dotenv from 'dotenv';
3
+ import { BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";
4
+ import { AzureOpenAI } from "openai";
5
 
6
+ dotenv.config();
 
 
7
 
8
  const app = express();
9
+ const PORT = process.env.PORT || 7860;
10
+
11
  app.use(express.json());
12
+
13
+ // --- 1. INITIALIZE AI CLIENTS ---
14
+
15
+ // AWS Bedrock Client (For Claude)
16
+ const bedrockClient = new BedrockRuntimeClient({
17
+ region: process.env.AWS_REGION || "us-east-1",
18
+ credentials: {
19
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
20
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
21
  }
22
+ });
23
 
24
+ // Azure OpenAI Client (For GPT-5 Mini)
25
+ const azureClient = new AzureOpenAI({
26
+ endpoint: process.env.AZURE_OPENAI_ENDPOINT,
27
+ apiKey: process.env.AZURE_OPENAI_API_KEY,
28
+ apiVersion: "2026-01-01-preview", // Current 2026 API version
29
+ deployment: process.env.AZURE_OPENAI_DEPLOYMENT || "gpt-5-mini"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  });
31
 
32
+ // --- 2. AI ROUTING LOGIC ---
33
+
34
+ async function callClaudeOnBedrock(prompt) {
35
+ try {
36
+ const command = new ConverseCommand({
37
+ // Using the standard Bedrock model ID format for Sonnet
38
+ modelId: "anthropic.claude-4-6-sonnet-v1:0",
39
+ messages:
40
+ }
41
+ ],
42
+ inferenceConfig: {
43
+ maxTokens: 4096,
44
+ temperature: 0.2
45
+ }
46
+ });
47
+
48
+ const response = await bedrockClient.send(command);
49
+ return response.output.message.content.text;
50
+ } catch (error) {
51
+ console.error("AWS Bedrock Error:", error);
52
+ throw new Error(`Bedrock Error: ${error.message}`);
53
+ }
54
+ }
55
+
56
+ async function callGptOnAzure(prompt) {
57
+ try {
58
+ const response = await azureClient.chat.completions.create({
59
+ messages:,
60
+ temperature: 0.2,
61
+ max_tokens: 4096
62
+ });
63
+
64
+ return response.choices.message.content;
65
+ } catch (error) {
66
+ console.error("Azure OpenAI Error:", error);
67
+ throw new Error(`Azure Error: ${error.message}`);
68
+ }
69
+ }
70
+
71
+ // --- 3. API ENDPOINTS ---
72
+
73
+ app.post('/api/generate', async (req, res) => {
74
+ const { model, prompt } = req.body;
75
+
76
+ if (!prompt) {
77
+ return res.status(400).json({ error: "Prompt is required." });
78
+ }
79
+
80
+ try {
81
+ let aiResponse = "";
82
+ const startTime = Date.now();
83
+
84
+ if (model === "claude-sonnet") {
85
+ aiResponse = await callClaudeOnBedrock(prompt);
86
+ } else if (model === "gpt-mini") {
87
+ aiResponse = await callGptOnAzure(prompt);
88
+ } else {
89
+ return res.status(400).json({ error: "Invalid model selected." });
90
+ }
91
+
92
+ const latency = Date.now() - startTime;
93
+
94
+ res.json({
95
+ success: true,
96
+ model: model,
97
+ response: aiResponse,
98
+ latencyMs: latency
99
+ });
100
+
101
+ } catch (error) {
102
+ res.status(500).json({ success: false, error: error.message });
103
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  });
105
 
106
+ // --- 4. EMBEDDED HTML FRONTEND ---
107
+
108
+ app.get('/', (req, res) => {
109
+ const html = `
110
+ <!DOCTYPE html>
111
+ <html lang="en">
112
+ <head>
113
+ <meta charset="UTF-8">
114
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
115
+ <title>Hollowpad Battle Arena</title>
116
+ <style>
117
+ body {
118
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
119
+ background-color: #121212;
120
+ color: #e0e0e0;
121
+ margin: 0;
122
+ padding: 40px;
123
+ display: flex;
124
+ flex-direction: column;
125
+ align-items: center;
126
+ }
127
+ .container {
128
+ width: 100%;
129
+ max-width: 800px;
130
+ background: #1e1e1e;
131
+ padding: 30px;
132
+ border-radius: 12px;
133
+ box-shadow: 0 8px 16px rgba(0,0,0,0.5);
134
+ }
135
+ h1 { text-align: center; color: #4facfe; }
136
+ label { font-weight: bold; margin-top: 15px; display: block; }
137
+ select, textarea {
138
+ width: 100%;
139
+ padding: 12px;
140
+ margin-top: 8px;
141
+ background: #2a2a2a;
142
+ border: 1px solid #444;
143
+ color: #fff;
144
+ border-radius: 6px;
145
+ font-size: 16px;
146
+ box-sizing: border-box;
147
+ }
148
+ textarea { height: 150px; resize: vertical; font-family: monospace; }
149
+ button {
150
+ width: 100%;
151
+ padding: 15px;
152
+ margin-top: 20px;
153
+ background: linear-gradient(to right, #4facfe 0%, #00f2fe 100%);
154
+ border: none;
155
+ color: #000;
156
+ font-weight: bold;
157
+ font-size: 16px;
158
+ border-radius: 6px;
159
+ cursor: pointer;
160
+ transition: opacity 0.2s;
161
+ }
162
+ button:hover { opacity: 0.8; }
163
+ button:disabled { background: #555; cursor: not-allowed; }
164
+ .output-box {
165
+ margin-top: 30px;
166
+ background: #000;
167
+ padding: 20px;
168
+ border-radius: 6px;
169
+ border: 1px solid #333;
170
+ min-height: 100px;
171
+ white-space: pre-wrap;
172
+ font-family: 'Courier New', Courier, monospace;
173
+ }
174
+ .stats {
175
+ font-size: 12px;
176
+ color: #888;
177
+ margin-top: 10px;
178
+ text-align: right;
179
+ }
180
+ .loader {
181
+ display: none;
182
+ text-align: center;
183
+ margin-top: 20px;
184
+ color: #4facfe;
185
+ }
186
+ </style>
187
+ </head>
188
+ <body>
189
+ <div class="container">
190
+ <h1>Hollowpad: Model Test Arena</h1>
191
+
192
+ <label for="modelSelect">Select AI Model:</label>
193
+ <select id="modelSelect">
194
+ <option value="gpt-mini">GPT-5 Mini (Azure - Worker)</option>
195
+ <option value="claude-sonnet">Claude 4.6 Sonnet (AWS - Pro)</option>
196
+ </select>
197
+
198
+ <label for="promptInput">Enter Prompt:</label>
199
+ <textarea id="promptInput" placeholder="Write a Roblox DataStore script..."></textarea>
200
+
201
+ <button id="generateBtn" onclick="generate()">Run Test</button>
202
+ <div id="loader" class="loader">Generating response... Please wait.</div>
203
+
204
+ <label>Output:</label>
205
+ <div id="outputBox" class="output-box">Response will appear here...</div>
206
+ <div id="stats" class="stats"></div>
207
+ </div>
208
+
209
+ <script>
210
+ async function generate() {
211
+ const model = document.getElementById('modelSelect').value;
212
+ const prompt = document.getElementById('promptInput').value;
213
+ const btn = document.getElementById('generateBtn');
214
+ const loader = document.getElementById('loader');
215
+ const outputBox = document.getElementById('outputBox');
216
+ const stats = document.getElementById('stats');
217
+
218
+ if (!prompt.trim()) {
219
+ alert('Please enter a prompt.');
220
+ return;
221
+ }
222
+
223
+ // UI Loading State
224
+ btn.disabled = true;
225
+ loader.style.display = 'block';
226
+ outputBox.innerText = '';
227
+ stats.innerText = '';
228
+
229
+ try {
230
+ const response = await fetch('/api/generate', {
231
+ method: 'POST',
232
+ headers: { 'Content-Type': 'application/json' },
233
+ body: JSON.stringify({ model, prompt })
234
+ });
235
+
236
+ const data = await response.json();
237
+
238
+ if (data.success) {
239
+ outputBox.innerText = data.response;
240
+ stats.innerText = \`Latency: \${(data.latencyMs / 1000).toFixed(2)}s\`;
241
+ } else {
242
+ outputBox.innerText = "Error: " + data.error;
243
+ }
244
+ } catch (err) {
245
+ outputBox.innerText = "Network Error: " + err.message;
246
+ } finally {
247
+ // Reset UI
248
+ btn.disabled = false;
249
+ loader.style.display = 'none';
250
+ }
251
+ }
252
+ </script>
253
+ </body>
254
+ </html>
255
+ `;
256
+ res.send(html);
257
+ });
258
+
259
+ // --- 5. START SERVER ---
260
+ app.listen(PORT, () => {
261
+ console.log(`🚀 Hollowpad Test Arena running at http://localhost:${PORT}`);
262
+ console.log(`Checking AWS Region: ${process.env.AWS_REGION}`);
263
+ console.log(`Checking Azure Endpoint: ${process.env.AZURE_OPENAI_ENDPOINT ? "Set" : "Missing"}`);
264
  });