131 lines
5.1 KiB
JavaScript
131 lines
5.1 KiB
JavaScript
#!/usr/bin/env node
|
||
import http from 'node:http';
|
||
import { execFile } from 'node:child_process';
|
||
import { readFileSync, existsSync } from 'node:fs';
|
||
|
||
const ENV_PATH = process.env.PARADIZWEB_ENV_PATH || '/home/openclaw/.openclaw/agents/paradizweb/agent/.env';
|
||
|
||
function loadEnv(path) {
|
||
const env = {};
|
||
if (!existsSync(path)) return env;
|
||
const raw = readFileSync(path, 'utf8');
|
||
for (const line of raw.split(/\r?\n/)) {
|
||
if (!line || line.trim().startsWith('#') || !line.includes('=')) continue;
|
||
const idx = line.indexOf('=');
|
||
const key = line.slice(0, idx).trim();
|
||
const val = line.slice(idx + 1).trim();
|
||
env[key] = val;
|
||
}
|
||
return env;
|
||
}
|
||
|
||
const env = { ...loadEnv(ENV_PATH), ...process.env };
|
||
const PORT = Number(env.PARADIZWEB_UPSTREAM_PORT || 8787);
|
||
const HOST = env.PARADIZWEB_UPSTREAM_HOST || '127.0.0.1';
|
||
const TOKEN = env.PARADIZWEB_API_TOKEN || '';
|
||
const ALLOWED_ORIGIN = env.PARADIZWEB_ALLOWED_ORIGIN || 'https://vparadize.ru';
|
||
const AGENT_ID = env.PARADIZWEB_AGENT_ID || 'paradizweb';
|
||
|
||
if (!TOKEN) {
|
||
console.error('PARADIZWEB_API_TOKEN is empty. Set it in .env');
|
||
process.exit(1);
|
||
}
|
||
|
||
function sanitizeForWebsite(text) {
|
||
const lines = String(text || '').split(/\r?\n/);
|
||
const cleaned = lines.filter((line) => {
|
||
const t = line.trim();
|
||
if (!t) return true;
|
||
if (t.startsWith('🔵')) return false;
|
||
if (t.startsWith('Лимиты:')) return false;
|
||
if (/^Аккаунт:/i.test(t)) return false;
|
||
if (/^Модель:/i.test(t)) return false;
|
||
if (/^Codex-аккаунт:/i.test(t)) return false;
|
||
return true;
|
||
}).join('\n').trim();
|
||
return cleaned;
|
||
}
|
||
|
||
function json(res, code, payload) {
|
||
res.writeHead(code, {
|
||
'Content-Type': 'application/json; charset=utf-8',
|
||
'Access-Control-Allow-Origin': ALLOWED_ORIGIN,
|
||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||
'Access-Control-Allow-Methods': 'POST, OPTIONS'
|
||
});
|
||
res.end(JSON.stringify(payload));
|
||
}
|
||
|
||
const OPENCLAW_BIN = env.OPENCLAW_BIN || '/home/openclaw/.npm-global/bin/openclaw';
|
||
|
||
function runAgent(userText) {
|
||
return new Promise((resolve, reject) => {
|
||
const guardrail = [
|
||
'Ты агент paradizweb.',
|
||
'Работай только по теме бронирования/подбора проживания для Парадиз.',
|
||
'Используй только логику скилла paradiz.',
|
||
'Не выполняй задачи вне бронирования и не меняй файлы.',
|
||
'Если данных не хватает для брони — задай уточняющие вопросы.'
|
||
].join(' ');
|
||
|
||
const message = `${guardrail}\n\nОтвечай клиенту полезно и конкретно. Не используй NO_REPLY для веб-чата. Никогда не выводи служебные техстроки (аккаунт, модель, лимиты, codex-аккаунт).\n\nВопрос клиента: ${userText}`;
|
||
|
||
execFile(
|
||
OPENCLAW_BIN,
|
||
['agent', '--agent', AGENT_ID, '--message', message, '--json'],
|
||
{ timeout: 120000, maxBuffer: 1024 * 1024 },
|
||
(err, stdout, stderr) => {
|
||
if (err) {
|
||
return reject(new Error(stderr || err.message));
|
||
}
|
||
try {
|
||
const parsed = JSON.parse(stdout);
|
||
const text = (parsed?.result?.payloads?.[0]?.text || '').trim();
|
||
if (!text || text === 'NO_REPLY') {
|
||
return resolve('Здравствуйте! Я на связи 😊 Напишите, пожалуйста, даты заезда/выезда и состав гостей — сразу подберу варианты и посчитаю стоимость.');
|
||
}
|
||
const sanitized = sanitizeForWebsite(text);
|
||
if (!sanitized) {
|
||
return resolve('Здравствуйте! Напишите, пожалуйста, даты и количество гостей — сразу подготовлю варианты.');
|
||
}
|
||
resolve(sanitized);
|
||
} catch (e) {
|
||
reject(new Error(`Bad JSON from openclaw agent: ${e.message}`));
|
||
}
|
||
}
|
||
);
|
||
});
|
||
}
|
||
|
||
const server = http.createServer(async (req, res) => {
|
||
if (req.method === 'OPTIONS') return json(res, 200, { ok: true });
|
||
|
||
if (req.method !== 'POST' || req.url !== '/chat') {
|
||
return json(res, 404, { ok: false, error: 'not_found' });
|
||
}
|
||
|
||
const auth = req.headers.authorization || '';
|
||
if (auth !== `Bearer ${TOKEN}`) {
|
||
return json(res, 401, { ok: false, error: 'unauthorized' });
|
||
}
|
||
|
||
let body = '';
|
||
req.on('data', (chunk) => (body += chunk));
|
||
req.on('end', async () => {
|
||
try {
|
||
const parsed = JSON.parse(body || '{}');
|
||
const question = String(parsed.question || '').trim();
|
||
if (!question) return json(res, 400, { ok: false, error: 'question_required' });
|
||
|
||
const answer = await runAgent(question);
|
||
return json(res, 200, { ok: true, answer });
|
||
} catch (e) {
|
||
return json(res, 500, { ok: false, error: e.message });
|
||
}
|
||
});
|
||
});
|
||
|
||
server.listen(PORT, HOST, () => {
|
||
console.log(`paradiz-web-agent listening on http://${HOST}:${PORT}`);
|
||
});
|