underrate commited on
Commit
1dcd62d
ยท
verified ยท
1 Parent(s): 8ab1b79

Upload 2 files

Browse files
Files changed (2) hide show
  1. server.js +164 -81
  2. start.sh +69 -10
server.js CHANGED
@@ -1,16 +1,136 @@
1
  const http = require('http');
2
  const fs = require('fs');
3
  const path = require('path');
 
4
 
5
  const PORT = 7860;
6
- const API_BASE = process.env.BASE_URL || '';
7
- const API_KEY = process.env.API_KEY || '';
8
 
9
- // Serve the chat UI
10
- const indexHtml = fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  const server = http.createServer(async (req, res) => {
13
- // CORS headers
14
  res.setHeader('Access-Control-Allow-Origin', '*');
15
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
16
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
@@ -23,89 +143,27 @@ const server = http.createServer(async (req, res) => {
23
 
24
  // Serve UI
25
  if (req.url === '/' || req.url === '/index.html') {
 
26
  res.writeHead(200, { 'Content-Type': 'text/html' });
27
- res.end(indexHtml);
28
  return;
29
  }
30
 
31
- // Chat API endpoint
32
  if (req.url === '/chat' && req.method === 'POST') {
33
  let body = '';
34
  req.on('data', chunk => body += chunk);
35
  req.on('end', async () => {
36
  try {
37
- const { message } = JSON.parse(body);
38
-
39
- if (!API_BASE || !API_KEY) {
40
- res.writeHead(500, { 'Content-Type': 'application/json' });
41
- res.end(JSON.stringify({ error: 'API not configured. Set BASE_URL and API_KEY secrets.' }));
42
- return;
43
- }
44
-
45
- console.log(`๐Ÿ“ค Sending to API: ${message.substring(0, 50)}...`);
46
 
47
- // Call the AI API
48
- const apiRes = await fetch(API_BASE + '/chat/completions', {
49
- method: 'POST',
50
- headers: {
51
- 'Content-Type': 'application/json',
52
- 'Authorization': `Bearer ${API_KEY}`
53
- },
54
- body: JSON.stringify({
55
- model: process.env.MODEL_ID || 'Multiple-models',
56
- messages: [
57
- { role: 'user', content: message }
58
- ]
59
- })
60
- });
61
-
62
- console.log(`๐Ÿ“ฅ API response status: ${apiRes.status}`);
63
-
64
- // Get raw text first
65
- const rawText = await apiRes.text();
66
-
67
- // Log response around the error position
68
- console.log(`๐Ÿ“ฅ Response length: ${rawText.length}`);
69
- if (rawText.length > 1500) {
70
- console.log(`๐Ÿ“ฅ Around position 1500-1700: "${rawText.substring(1500, 1700)}"`);
71
- }
72
-
73
- // Try to extract content using regex (avoid full JSON parse)
74
- const contentMatch = rawText.match(/"content"\s*:\s*"([^"]*(?:\\.[^"]*)*)"/s);
75
- if (contentMatch) {
76
- let reply = contentMatch[1];
77
- // Unescape common JSON escapes
78
- reply = reply.replace(/\\n/g, '\n')
79
- .replace(/\\"/g, '"')
80
- .replace(/\\\\/g, '\\');
81
- console.log(`โœ… Extracted reply (${reply.length} chars)`);
82
- res.writeHead(200, { 'Content-Type': 'application/json' });
83
- res.end(JSON.stringify({ reply }));
84
- return;
85
- }
86
-
87
- // Fallback: Try JSON parse
88
- let data;
89
- try {
90
- data = JSON.parse(rawText);
91
- } catch (parseErr) {
92
- console.error('โŒ JSON parse error:', parseErr.message);
93
- // Return a truncated raw response
94
- res.writeHead(200, { 'Content-Type': 'application/json' });
95
- res.end(JSON.stringify({ reply: rawText.substring(0, 500) + '...(truncated)' }));
96
- return;
97
- }
98
-
99
- const reply = data.choices?.[0]?.message?.content ||
100
- data.message ||
101
- data.response ||
102
- JSON.stringify(data);
103
-
104
- console.log(`โœ… Parsed reply (${reply.length} chars)`);
105
  res.writeHead(200, { 'Content-Type': 'application/json' });
106
- res.end(JSON.stringify({ reply }));
107
  } catch (err) {
108
- console.error('โŒ Error:', err);
109
  res.writeHead(500, { 'Content-Type': 'application/json' });
110
  res.end(JSON.stringify({ error: err.message }));
111
  }
@@ -117,8 +175,33 @@ const server = http.createServer(async (req, res) => {
117
  res.end('Not found');
118
  });
119
 
120
- server.listen(PORT, '0.0.0.0', () => {
121
- console.log(`๐ŸŒ OpenClaw Web Chat running on port ${PORT}`);
122
- console.log(`๐Ÿ”— Open your Space URL to access the chat`);
123
- console.log(`๐Ÿ“ก API URL: ${API_BASE || 'not set'}`);
124
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  const http = require('http');
2
  const fs = require('fs');
3
  const path = require('path');
4
+ const WebSocket = require('ws');
5
 
6
  const PORT = 7860;
7
+ const GATEWAY_PORT = 18789;
 
8
 
9
+ let gatewayWs = null;
10
+ const sessions = new Map();
11
 
12
+ // Connect to local OpenClaw gateway (no pairing needed for loopback)
13
+ function connectToGateway() {
14
+ return new Promise((resolve, reject) => {
15
+ console.log('๐Ÿ”Œ Connecting to OpenClaw Gateway (localhost)...');
16
+
17
+ const ws = new WebSocket(`ws://127.0.0.1:${GATEWAY_PORT}/`);
18
+
19
+ ws.on('open', () => {
20
+ console.log('โœ… Connected to OpenClaw Gateway');
21
+ gatewayWs = ws;
22
+ resolve();
23
+ });
24
+
25
+ ws.on('message', (data) => {
26
+ try {
27
+ const msg = JSON.parse(data.toString());
28
+ console.log('๐Ÿ“ฅ OpenClaw response:', JSON.stringify(msg).substring(0, 150));
29
+ handleGatewayMessage(msg);
30
+ } catch (e) {
31
+ console.log('๐Ÿ“ฅ Raw:', data.toString().substring(0, 150));
32
+ }
33
+ });
34
+
35
+ ws.on('error', (err) => {
36
+ console.error('โŒ Gateway connection error:', err.message);
37
+ gatewayWs = null;
38
+ });
39
+
40
+ ws.on('close', () => {
41
+ console.log('Gateway connection closed');
42
+ gatewayWs = null;
43
+ // Reconnect after delay
44
+ setTimeout(() => {
45
+ if (!gatewayWs) connectToGateway().catch(() => {});
46
+ }, 3000);
47
+ });
48
+ });
49
+ }
50
+
51
+ // Handle messages from OpenClaw gateway
52
+ function handleGatewayMessage(msg) {
53
+ // Extract session key
54
+ const sessionKey = msg.sessionKey;
55
+ if (!sessionKey) return;
56
+
57
+ const session = sessions.get(sessionKey);
58
+ if (session && session.pendingResolve) {
59
+ // Extract reply from various message formats
60
+ let reply = null;
61
+
62
+ if (msg.type === 'text' || msg.type === 'chunk') {
63
+ reply = msg.content || msg.text;
64
+ } else if (msg.choices && msg.choices[0]?.message?.content) {
65
+ reply = msg.choices[0].message.content;
66
+ } else if (msg.content) {
67
+ reply = msg.content;
68
+ } else if (msg.message) {
69
+ reply = msg.message;
70
+ }
71
+
72
+ if (reply) {
73
+ // Accumulate chunks
74
+ if (!session.reply) session.reply = '';
75
+ session.reply += reply;
76
+
77
+ // If done or complete message, resolve
78
+ if (msg.type === 'done' || msg.type === 'text' || msg.complete) {
79
+ session.pendingResolve({ reply: session.reply });
80
+ session.pendingResolve = null;
81
+ session.reply = '';
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ // Send message through OpenClaw
88
+ function sendToOpenClaw(message, sessionId) {
89
+ return new Promise((resolve, reject) => {
90
+ if (!gatewayWs || gatewayWs.readyState !== WebSocket.OPEN) {
91
+ reject(new Error('Gateway not connected'));
92
+ return;
93
+ }
94
+
95
+ const sessionKey = `webchat:direct:${sessionId}`;
96
+
97
+ // Track session
98
+ if (!sessions.has(sessionKey)) {
99
+ sessions.set(sessionKey, { messages: [], reply: '' });
100
+ }
101
+ const session = sessions.get(sessionKey);
102
+ session.pendingResolve = resolve;
103
+
104
+ // Send to OpenClaw gateway
105
+ const payload = {
106
+ type: 'message',
107
+ content: message,
108
+ sessionKey: sessionKey,
109
+ channel: 'webchat',
110
+ chatType: 'direct',
111
+ from: { id: sessionId, name: 'Web User' }
112
+ };
113
+
114
+ console.log('๐Ÿ“ค To OpenClaw:', message.substring(0, 50));
115
+ gatewayWs.send(JSON.stringify(payload));
116
+
117
+ // Timeout after 2 minutes
118
+ setTimeout(() => {
119
+ if (session.pendingResolve === resolve) {
120
+ session.pendingResolve = null;
121
+ if (session.reply) {
122
+ resolve({ reply: session.reply });
123
+ session.reply = '';
124
+ } else {
125
+ reject(new Error('Response timeout'));
126
+ }
127
+ }
128
+ }, 120000);
129
+ });
130
+ }
131
+
132
+ // HTTP Server - serves UI and proxies to OpenClaw
133
  const server = http.createServer(async (req, res) => {
 
134
  res.setHeader('Access-Control-Allow-Origin', '*');
135
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
136
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
 
143
 
144
  // Serve UI
145
  if (req.url === '/' || req.url === '/index.html') {
146
+ const html = fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');
147
  res.writeHead(200, { 'Content-Type': 'text/html' });
148
+ res.end(html);
149
  return;
150
  }
151
 
152
+ // Chat endpoint - goes through OpenClaw
153
  if (req.url === '/chat' && req.method === 'POST') {
154
  let body = '';
155
  req.on('data', chunk => body += chunk);
156
  req.on('end', async () => {
157
  try {
158
+ const { message, sessionId } = JSON.parse(body);
159
+ const sid = sessionId || `web-${Date.now()}`;
 
 
 
 
 
 
 
160
 
161
+ // Send through OpenClaw gateway
162
+ const response = await sendToOpenClaw(message, sid);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  res.writeHead(200, { 'Content-Type': 'application/json' });
164
+ res.end(JSON.stringify(response));
165
  } catch (err) {
166
+ console.error('โŒ Chat error:', err.message);
167
  res.writeHead(500, { 'Content-Type': 'application/json' });
168
  res.end(JSON.stringify({ error: err.message }));
169
  }
 
175
  res.end('Not found');
176
  });
177
 
178
+ // Main
179
+ async function main() {
180
+ try {
181
+ // Wait for gateway to be ready, then connect
182
+ let retries = 30;
183
+ while (retries > 0) {
184
+ try {
185
+ await connectToGateway();
186
+ break;
187
+ } catch (e) {
188
+ retries--;
189
+ await new Promise(r => setTimeout(r, 1000));
190
+ }
191
+ }
192
+
193
+ if (!gatewayWs) {
194
+ throw new Error('Could not connect to OpenClaw Gateway');
195
+ }
196
+
197
+ server.listen(PORT, '0.0.0.0', () => {
198
+ console.log(`\n๐ŸŒ OpenClaw Web Interface running on port ${PORT}`);
199
+ console.log(`๐Ÿ”— Open your Space URL to chat with Zero!\n`);
200
+ });
201
+ } catch (err) {
202
+ console.error('โŒ Startup failed:', err);
203
+ process.exit(1);
204
+ }
205
+ }
206
+
207
+ main();
start.sh CHANGED
@@ -1,8 +1,11 @@
1
  #!/bin/bash
2
 
3
- # Hugging Face Spaces startup script
4
 
5
- echo "๐Ÿฆž Starting OpenClaw Web Chat..."
 
 
 
6
 
7
  # Check required env vars
8
  if [ -z "$API_KEY" ] || [ -z "$BASE_URL" ]; then
@@ -12,16 +15,72 @@ fi
12
  # Set defaults
13
  BASE_URL=${BASE_URL:-"http://127.0.0.1:20128/v1"}
14
  MODEL_ID=${MODEL_ID:-"Multiple-models"}
 
15
 
16
- export BASE_URL
17
- export API_KEY
18
- export MODEL_ID
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- echo "โœ… Configured"
21
- echo "๐Ÿ“ก API URL: ${BASE_URL}"
22
  echo "๐Ÿค– Model: ${MODEL_ID}"
23
 
24
- # Start the simple web server
25
- echo "๐ŸŒ Starting web server on port 7860..."
 
 
 
 
 
 
 
 
26
  cd /app
27
- exec node server.js
 
 
 
 
1
  #!/bin/bash
2
 
3
+ # Hugging Face Spaces startup script for OpenClaw
4
 
5
+ echo "๐Ÿฆž Starting OpenClaw on Hugging Face Spaces..."
6
+
7
+ # Create config directory
8
+ mkdir -p /root/.openclaw/workspace
9
 
10
  # Check required env vars
11
  if [ -z "$API_KEY" ] || [ -z "$BASE_URL" ]; then
 
15
  # Set defaults
16
  BASE_URL=${BASE_URL:-"http://127.0.0.1:20128/v1"}
17
  MODEL_ID=${MODEL_ID:-"Multiple-models"}
18
+ PROVIDER_NAME=${PROVIDER_NAME:-"custom"}
19
 
20
+ # Create OpenClaw config - bind to loopback only (no pairing needed for local)
21
+ cat > /root/.openclaw/openclaw.json << EOF
22
+ {
23
+ "meta": {
24
+ "lastTouchedVersion": "2026.2.19-2"
25
+ },
26
+ "models": {
27
+ "providers": {
28
+ "${PROVIDER_NAME}": {
29
+ "baseUrl": "${BASE_URL}",
30
+ "apiKey": "${API_KEY}",
31
+ "api": "openai-completions",
32
+ "models": [
33
+ { "id": "${MODEL_ID}", "name": "${MODEL_ID}" }
34
+ ]
35
+ }
36
+ }
37
+ },
38
+ "agents": {
39
+ "defaults": {
40
+ "model": { "primary": "${PROVIDER_NAME}/${MODEL_ID}" },
41
+ "workspace": "/root/.openclaw/workspace"
42
+ }
43
+ },
44
+ "commands": { "native": "auto", "nativeSkills": "auto", "restart": false },
45
+ "hooks": {
46
+ "internal": {
47
+ "enabled": true,
48
+ "entries": {
49
+ "boot-md": { "enabled": true },
50
+ "command-logger": { "enabled": true },
51
+ "session-memory": { "enabled": true }
52
+ }
53
+ }
54
+ },
55
+ "session": { "dmScope": "per-channel-peer" },
56
+ "gateway": {
57
+ "port": 18789,
58
+ "mode": "local",
59
+ "bind": "loopback",
60
+ "auth": { "mode": "none" },
61
+ "tailscale": { "mode": "off" }
62
+ },
63
+ "skills": { "install": { "nodeManager": "npm" } },
64
+ "plugins": { "entries": {} }
65
+ }
66
+ EOF
67
 
68
+ echo "โœ… Config created (loopback mode - no pairing)"
69
+ echo "๐Ÿ“ก API: ${BASE_URL}"
70
  echo "๐Ÿค– Model: ${MODEL_ID}"
71
 
72
+ # Start OpenClaw gateway
73
+ echo "๐Ÿš€ Starting OpenClaw Gateway..."
74
+ openclaw gateway &
75
+ GATEWAY_PID=$!
76
+
77
+ # Wait for gateway
78
+ sleep 5
79
+
80
+ # Start web proxy server
81
+ echo "๐ŸŒ Starting Web Proxy..."
82
  cd /app
83
+ node server.js
84
+
85
+ # Cleanup
86
+ kill $GATEWAY_PID 2>/dev/null