Juanoto2012 commited on
Commit
81a789f
·
verified ·
1 Parent(s): 20d3c08

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +56 -132
server.js CHANGED
@@ -1,8 +1,7 @@
1
  import express from 'express';
2
  import cors from 'cors';
3
  import rateLimit from 'express-rate-limit';
4
- import { Readable, Transform } from 'stream';
5
- import crypto from 'crypto';
6
 
7
  const app = express();
8
  const PORT = 7860;
@@ -25,174 +24,99 @@ app.use((req, res, next) => {
25
  app.use(express.json({ limit: '50mb' }));
26
  app.use(express.urlencoded({ limit: '50mb', extended: true }));
27
 
28
- // Falsificación de huella digital
29
- function getRandomUserAgent() {
30
- const agents = [
31
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
32
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
33
- "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0"
34
- ];
35
- return agents[Math.floor(Math.random() * agents.length)];
36
- }
37
-
38
- // Modelos soportados por DuckDuckGo AI
39
- const DDG_MODELS = {
40
- "gpt-4o-mini": "gpt-4o-mini",
41
- "claude-3-haiku": "claude-3-haiku-20240307",
42
- "llama-3.1-70b": "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
43
- "mixtral-8x7b": "mistralai/Mixtral-8x7B-Instruct-v0.1"
44
- };
45
-
46
- // --- RATE LIMITING ---
47
  const limiter = rateLimit({
48
  windowMs: 60 * 1000,
49
  max: 50,
50
  keyGenerator: (req) => req.ip,
51
- message: { error: { message: "Too many requests local.", code: 429 } },
52
  standardHeaders: false,
53
  legacyHeaders: false,
54
  });
55
 
56
  app.get('/health', (req, res) => {
57
- res.json({ status: "online", proxy: "DuckDuckGo AI" });
58
  });
59
 
60
- // Endpoint de modelos (Formato OpenAI)
61
  app.get('/v1/models', (req, res) => {
62
- const models = Object.keys(DDG_MODELS).map(id => ({
63
- id: id,
64
- object: "model",
65
- type: "text",
66
- owned_by: "duckduckgo"
67
- }));
68
- res.json({ object: "list", data: models });
 
 
69
  });
70
 
71
  app.post('/v1/chat/completions', limiter, async (req, res) => {
72
- const startTime = Date.now();
73
- const clientIp = req.ip || req.headers['x-forwarded-for'] || req.socket.remoteAddress;
74
-
75
- // Configuración del modelo y mensajes
76
- const requestedModel = req.body.model || "gpt-4o-mini";
77
- const duckModel = DDG_MODELS[requestedModel] || "gpt-4o-mini";
78
- const messages = req.body.messages || [];
79
- const isStream = req.body.stream === true;
80
-
81
  try {
82
- // PASO 1: Obtener el token (VQD) necesario para chatear
83
- const statusRes = await fetch("https://duckduckgo.com/duckchat/v1/status", {
84
- headers: {
85
- "x-vqd-accept": "1",
86
- "User-Agent": getRandomUserAgent(),
87
- "x-forwarded-for": clientIp
88
- }
89
- });
90
 
91
- const vqdToken = statusRes.headers.get("x-vqd-4");
92
- if (!vqdToken) {
93
- return res.status(503).json({ error: { message: "Error al negociar token con DuckDuckGo", code: 503 }});
94
- }
95
-
96
- // PASO 2: Enviar el prompt a DuckDuckGo
97
- const chatRes = await fetch("https://duckduckgo.com/duckchat/v1/chat", {
98
- method: "POST",
99
- headers: {
100
- "Content-Type": "application/json",
101
- "x-vqd-4": vqdToken,
102
- "User-Agent": getRandomUserAgent(),
103
- "Accept": "text/event-stream",
104
- "x-forwarded-for": clientIp
105
- },
106
- body: JSON.stringify({
107
- model: duckModel,
108
- messages: messages
109
- })
110
- });
111
-
112
- if (!chatRes.ok) {
113
- const errorText = await chatRes.text();
114
- return res.status(chatRes.status).json({ error: { message: `DuckDuckGo Error: ${errorText}`, code: chatRes.status }});
115
- }
116
 
117
- // PASO 3A: Manejar Petición NO STREAM (Esperar y juntar todo el texto)
118
- if (!isStream) {
119
- let fullText = "";
120
- const reader = chatRes.body.getReader();
121
- const decoder = new TextDecoder("utf-8");
122
-
123
- while (true) {
124
- const { done, value } = await reader.read();
125
- if (done) break;
126
-
127
- const chunkStr = decoder.decode(value, { stream: true });
128
- const lines = chunkStr.split('\n');
129
- for (let line of lines) {
130
- if (line.startsWith('data: ') && line !== 'data: [DONE]') {
131
- try {
132
- const data = JSON.parse(line.slice(6));
133
- if (data.message) fullText += data.message;
134
- } catch (e) { /* Ignorar chunks rotos */ }
135
- }
136
- }
137
- }
138
-
139
  return res.json({
140
- id: 'chatcmpl-' + crypto.randomUUID(),
141
  object: 'chat.completion',
142
  created: Math.floor(Date.now() / 1000),
143
- model: requestedModel,
144
- choices: [{ message: { role: 'assistant', content: fullText }, finish_reason: 'stop' }]
145
  });
146
  }
147
 
148
- // PASO 3B: Manejar Petición STREAMING (Traducir sobre la marcha DDG -> OpenAI)
149
  res.writeHead(200, {
150
  'Content-Type': 'text/event-stream',
151
  'Cache-Control': 'no-cache',
152
  'Connection': 'keep-alive'
153
  });
154
 
155
- let buffer = '';
156
- const ddgToOpenAI = new Transform({
157
- transform(chunk, encoding, callback) {
158
- buffer += chunk.toString();
159
- const lines = buffer.split('\n');
160
- buffer = lines.pop(); // Guarda el fragmento incompleto para el próximo chunk
161
-
162
- for (const line of lines) {
163
- if (line.startsWith('data: ') && line !== 'data: [DONE]') {
164
- try {
165
- const data = JSON.parse(line.slice(6));
166
- if (data.message) {
167
- const openaiChunk = {
168
- id: 'chatcmpl-' + crypto.randomUUID(),
169
- object: 'chat.completion.chunk',
170
- created: Math.floor(Date.now() / 1000),
171
- model: requestedModel,
172
- choices: [{ delta: { content: data.message } }]
173
- };
174
- this.push(`data: ${JSON.stringify(openaiChunk)}\n\n`);
175
- }
176
- } catch (e) { /* Ignorar errores de parseo parcial */ }
177
- } else if (line === 'data: [DONE]') {
178
- this.push('data: [DONE]\n\n');
179
- }
180
- }
181
- callback();
182
- }
183
  });
184
 
185
- const stream = Readable.fromWeb(chatRes.body);
186
- stream.pipe(ddgToOpenAI).pipe(res);
187
 
188
  } catch (err) {
189
- console.error("[ERROR]", err.message);
 
 
190
  if (!res.headersSent) {
191
- res.status(500).json({ error: { message: "Error interno del servidor", details: err.message, code: 500 }});
 
 
 
 
 
 
192
  }
193
  }
194
  });
195
 
196
  app.listen(PORT, '0.0.0.0', () => {
197
- console.log(`[SYS] DuckDuckGo AI Proxy Activo. Modelos: GPT-4o-mini, Claude-3-Haiku, Llama-3.1-70b. Puerto: ${PORT}`);
198
  });
 
1
  import express from 'express';
2
  import cors from 'cors';
3
  import rateLimit from 'express-rate-limit';
4
+ import { DuckDuckGoChat, Models } from 'duckduckgo-chat-interface';
 
5
 
6
  const app = express();
7
  const PORT = 7860;
 
24
  app.use(express.json({ limit: '50mb' }));
25
  app.use(express.urlencoded({ limit: '50mb', extended: true }));
26
 
27
+ // --- RATE LIMITING LOCAL ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  const limiter = rateLimit({
29
  windowMs: 60 * 1000,
30
  max: 50,
31
  keyGenerator: (req) => req.ip,
32
+ message: { error: { message: "Too many requests.", code: 429 } },
33
  standardHeaders: false,
34
  legacyHeaders: false,
35
  });
36
 
37
  app.get('/health', (req, res) => {
38
+ res.json({ status: "online", proxy: "DuckDuckGo Library", node_module: "duckduckgo-chat-interface" });
39
  });
40
 
 
41
  app.get('/v1/models', (req, res) => {
42
+ res.json({
43
+ object: "list",
44
+ data: [
45
+ { id: "gpt-4o-mini", object: "model", type: "text", owned_by: "duckduckgo" },
46
+ { id: "claude-3-haiku", object: "model", type: "text", owned_by: "duckduckgo" },
47
+ { id: "llama-3.1-70b", object: "model", type: "text", owned_by: "duckduckgo" },
48
+ { id: "mixtral-8x7b", object: "model", type: "text", owned_by: "duckduckgo" }
49
+ ]
50
+ });
51
  });
52
 
53
  app.post('/v1/chat/completions', limiter, async (req, res) => {
 
 
 
 
 
 
 
 
 
54
  try {
55
+ const { model = "gpt-4o-mini", messages, stream } = req.body;
 
 
 
 
 
 
 
56
 
57
+ // 1. Mapear tu string de modelo al Enum que usa la librería
58
+ let chatModel = Models.GPT4Mini;
59
+ if (model.includes("claude")) chatModel = Models.Claude3Haiku;
60
+ if (model.includes("llama")) chatModel = Models.Llama3_3_70B;
61
+ if (model.includes("mistral") || model.includes("mixtral")) chatModel = Models.MistralSmall;
62
+
63
+ // 2. Inicializar la sesión de DuckDuckGo
64
+ const chat = new DuckDuckGoChat(chatModel);
65
+ await chat.initialize();
66
+
67
+ // Extraemos solo el mensaje del usuario (DuckDuckGo no soporta bien historiales complejos por API sin manejo avanzado)
68
+ // Concatenamos el historial para darle contexto al modelo si hay varios mensajes.
69
+ const fullPrompt = messages.map(m => `${m.role}: ${m.content}`).join('\n\n');
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ // 3A. Modo Texto Completo (No Streaming)
72
+ if (!stream) {
73
+ const response = await chat.sendMessage(fullPrompt);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  return res.json({
75
+ id: 'chatcmpl-' + Date.now(),
76
  object: 'chat.completion',
77
  created: Math.floor(Date.now() / 1000),
78
+ model: model,
79
+ choices: [{ message: { role: 'assistant', content: response }, finish_reason: 'stop' }]
80
  });
81
  }
82
 
83
+ // 3B. Modo Streaming (Traducción en tiempo real a OpenAI)
84
  res.writeHead(200, {
85
  'Content-Type': 'text/event-stream',
86
  'Cache-Control': 'no-cache',
87
  'Connection': 'keep-alive'
88
  });
89
 
90
+ await chat.sendMessageStream(fullPrompt, (chunk) => {
91
+ const data = {
92
+ id: 'chatcmpl-' + Date.now(),
93
+ object: 'chat.completion.chunk',
94
+ created: Math.floor(Date.now() / 1000),
95
+ model: model,
96
+ choices: [{ delta: { content: chunk } }]
97
+ };
98
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  });
100
 
101
+ res.write('data: [DONE]\n\n');
102
+ res.end();
103
 
104
  } catch (err) {
105
+ console.error("[DDG LIBRARY ERROR]", err);
106
+ const statusCode = err.message.includes('503') ? 503 : 500;
107
+
108
  if (!res.headersSent) {
109
+ res.status(statusCode).json({
110
+ error: {
111
+ message: "Error de proveedor. Si recibes 503 constantemente, Cloudflare está bloqueando la IP de Hugging Face.",
112
+ details: err.message,
113
+ code: statusCode
114
+ }
115
+ });
116
  }
117
  }
118
  });
119
 
120
  app.listen(PORT, '0.0.0.0', () => {
121
+ console.log(`[SYS] DuckDuckGo Library Node.js Server Ready. Puerto: ${PORT}`);
122
  });