isididiidid commited on
Commit
0554d4d
·
verified ·
1 Parent(s): ba798b5

Update src/lightweight-client.js

Browse files
Files changed (1) hide show
  1. src/lightweight-client.js +41 -385
src/lightweight-client.js CHANGED
@@ -1,398 +1,54 @@
1
- import express from 'express';
2
- import dotenv from 'dotenv';
3
- import { randomUUID } from 'crypto';
4
- import { fileURLToPath } from 'url';
5
- import { dirname, join } from 'path';
6
- import chalk from 'chalk';
7
- import {
8
- ChatMessage, ChatCompletionRequest, Choice, ChoiceDelta, ChatCompletionChunk
9
- } from './models.js';
10
- import {
11
- initialize,
12
- streamNotionResponse,
13
- buildNotionRequest,
14
- INITIALIZED_SUCCESSFULLY
15
- } from './lightweight-client.js';
16
- import { cookieManager } from './CookieManager.js';
17
-
18
- // 获取当前文件的目录路径
19
- const __filename = fileURLToPath(import.meta.url);
20
- const __dirname = dirname(__filename);
21
-
22
- // 加载环境变量
23
- dotenv.config({ path: join(dirname(__dirname), '.env') });
24
-
25
- // 活跃流管理器 - 用于跟踪和管理当前活跃的请求流
26
- const activeStreams = new Map();
27
-
28
- // 日志配置
29
- const logger = {
30
- info: (message) => console.log(chalk.blue(`[info] ${message}`)),
31
- error: (message) => console.error(chalk.red(`[error] ${message}`)),
32
- warning: (message) => console.warn(chalk.yellow(`[warn] ${message}`)),
33
- success: (message) => console.log(chalk.green(`[success] ${message}`)),
34
- request: (method, path, status, time) => {
35
- const statusColor = status >= 500 ? chalk.red :
36
- status >= 400 ? chalk.yellow :
37
- status >= 300 ? chalk.cyan :
38
- status >= 200 ? chalk.green : chalk.white;
39
- console.log(`${chalk.magenta(`[${method}]`)} - ${path} ${statusColor(status)} ${chalk.gray(`${time}ms`)}`);
40
- }
41
- };
42
-
43
- // 认证配置
44
- const EXPECTED_TOKEN = process.env.PROXY_AUTH_TOKEN || "default_token";
45
-
46
- // 代理配置
47
- const PROXY_URL = process.env.PROXY_URL;
48
-
49
- // 代理验证函数
50
- async function validateProxy() {
51
- if (!PROXY_URL) {
52
- logger.info('未配置代理,将使用直连访问');
53
- return true;
54
- }
55
-
56
- try {
57
- const { default: fetch } = await import('node-fetch');
58
- const { HttpsProxyAgent } = await import('https-proxy-agent');
59
-
60
- const testResponse = await fetch('https://httpbin.org/ip', {
61
- method: 'GET',
62
- agent: new HttpsProxyAgent(PROXY_URL),
63
- timeout: 15000
64
- });
65
-
66
- if (testResponse.ok) {
67
- const ipInfo = await testResponse.json();
68
- // 隐藏密码的安全日志输出
69
- const safeProxyUrl = PROXY_URL.replace(/:([^:@]+)@/, ':****@');
70
- logger.success(`✅ 代理验证成功 - IP: ${ipInfo.origin} - 代理: ${safeProxyUrl}`);
71
- return true;
72
- } else {
73
- logger.error(`❌ 代理验证失败 - 状态码: ${testResponse.status}`);
74
- return false;
75
- }
76
- } catch (error) {
77
- logger.error(`❌ 代理验证异常: ${error.message}`);
78
- return false;
79
- }
80
- }
81
-
82
- // 创建Express应用
83
- const app = express();
84
- app.use(express.json({ limit: '50mb' }));
85
- app.use(express.urlencoded({ extended: true, limit: '50mb' }));
86
 
87
- // 请求日志中间件
88
- app.use((req, res, next) => {
89
- const start = Date.now();
90
-
91
- // 保存原始的 end 方法
92
- const originalEnd = res.end;
93
-
94
- // 重写 end 方法以记录请求完成时间
95
- res.end = function(...args) {
96
- const duration = Date.now() - start;
97
- logger.request(req.method, req.path, res.statusCode, duration);
98
- return originalEnd.apply(this, args);
99
- };
100
-
101
- next();
102
- });
103
 
104
- // 认证中间件
105
- function authenticate(req, res, next) {
106
- const authHeader = req.headers.authorization;
 
107
 
108
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
109
- return res.status(401).json({
110
- error: {
111
- message: "Authentication required. Please provide a valid Bearer token.",
112
- type: "authentication_error"
113
- }
114
- });
115
  }
116
 
117
- const token = authHeader.split(' ')[1];
118
-
119
- if (token !== EXPECTED_TOKEN) {
120
- return res.status(401).json({
121
- error: {
122
- message: "Invalid authentication credentials",
123
- type: "authentication_error"
124
- }
125
- });
126
- }
127
-
128
- next();
129
- }
130
-
131
- // 流管理函数
132
- function manageStream(clientId, stream) {
133
- // 如果该客户端已有活跃流,先关闭它
134
- if (activeStreams.has(clientId)) {
135
  try {
136
- const oldStream = activeStreams.get(clientId);
137
- logger.info(`关闭客户端 ${clientId} 的旧流`);
138
- oldStream.end();
139
- } catch (error) {
140
- logger.error(`关闭旧流时出错: ${error.message}`);
141
- }
142
- }
143
-
144
- // 注册新流
145
- activeStreams.set(clientId, stream);
146
-
147
- // 当���结束时从管理器中移除
148
- stream.on('end', () => {
149
- if (activeStreams.get(clientId) === stream) {
150
- activeStreams.delete(clientId);
151
- //logger.info(`客户端 ${clientId} 的流已结束并移除`);
152
- }
153
- });
154
-
155
- stream.on('error', (error) => {
156
- logger.error(`流错误: ${error.message}`);
157
- if (activeStreams.get(clientId) === stream) {
158
- activeStreams.delete(clientId);
159
- }
160
- });
161
-
162
- return stream;
163
- }
164
-
165
- // API路由
166
-
167
- // 获取模型列表
168
- app.get('/v1/models', authenticate, (req, res) => {
169
- // 返回可用模型列表
170
- const modelList = {
171
- data: [
172
- { id: "openai-gpt-4.1" },
173
- { id: "anthropic-opus-4" },
174
- { id: "anthropic-sonnet-4" },
175
- { id: "anthropic-sonnet-3.x-stable" },
176
- { id: "google-gemini-2.5-pro"}, //vertex-gemini-2.5-pro
177
- { id: "google-gemini-2.5-flash"}, //vertex-gemini-2.5-flash
178
- ]
179
- };
180
-
181
- res.json(modelList);
182
- });
183
-
184
- // 聊天完成端点
185
- app.post('/v1/chat/completions', authenticate, async (req, res) => {
186
- try {
187
- // 生成或获取客户端ID
188
- const clientId = req.headers['x-client-id'] || randomUUID();
189
-
190
- // 检查是否成功初始化
191
- if (!INITIALIZED_SUCCESSFULLY) {
192
- return res.status(500).json({
193
- error: {
194
- message: "系统未成功初始化。请检查您的NOTION_COOKIE是否有效。",
195
- type: "server_error"
196
- }
197
- });
198
- }
199
-
200
- // 检查是否有可用的cookie
201
- if (cookieManager.getValidCount() === 0) {
202
- return res.status(500).json({
203
- error: {
204
- message: "没有可用的有效cookie。请检查您的NOTION_COOKIE配置。",
205
- type: "server_error"
206
- }
207
- });
208
- }
209
-
210
- // 验证请求数据
211
- const requestData = req.body;
212
-
213
- if (!requestData.messages || !Array.isArray(requestData.messages) || requestData.messages.length === 0) {
214
- return res.status(400).json({
215
- error: {
216
- message: "Invalid request: 'messages' field must be a non-empty array.",
217
- type: "invalid_request_error"
218
- }
219
- });
220
- }
221
-
222
- // 构建Notion请求
223
- const notionRequestBody = buildNotionRequest(requestData);
224
-
225
- // 处理流式响应
226
- if (requestData.stream) {
227
- res.setHeader('Content-Type', 'text/event-stream');
228
- res.setHeader('Cache-Control', 'no-cache');
229
- res.setHeader('Connection', 'keep-alive');
230
-
231
- logger.info(`开始流式响应`);
232
- const stream = await streamNotionResponse(notionRequestBody);
233
-
234
- // 管理流
235
- manageStream(clientId, stream);
236
 
237
- stream.pipe(res);
 
 
238
 
239
- // 客户端断开连接
240
- req.on('close', () => {
241
- logger.info(`客户端 ${clientId} 断开连接`);
242
- if (activeStreams.has(clientId)) {
243
- try {
244
- activeStreams.get(clientId).end();
245
- activeStreams.delete(clientId);
246
- } catch (error) {
247
- logger.error(`关闭流时出错: ${error.message}`);
248
- }
249
  }
250
- });
251
- } else {
252
- // 非流式响应
253
- // 创建一个内部流来收集完整响应
254
- logger.info(`开始非流式响应`);
255
- const chunks = [];
256
- const stream = await streamNotionResponse(notionRequestBody);
257
-
258
- // 管理流
259
- manageStream(clientId, stream);
260
 
261
- return new Promise((resolve, reject) => {
262
- stream.on('data', (chunk) => {
263
- const chunkStr = chunk.toString();
264
- if (chunkStr.startsWith('data: ') && !chunkStr.includes('[DONE]')) {
265
- try {
266
- const dataJson = chunkStr.substring(6).trim();
267
- if (dataJson) {
268
- const chunkData = JSON.parse(dataJson);
269
- if (chunkData.choices && chunkData.choices[0].delta && chunkData.choices[0].delta.content) {
270
- chunks.push(chunkData.choices[0].delta.content);
271
- }
272
- }
273
- } catch (error) {
274
- logger.error(`解析非流式响应块时出错: ${error}`);
275
- }
276
- }
277
- });
278
-
279
- stream.on('end', () => {
280
- const fullResponse = {
281
- id: `chatcmpl-${randomUUID()}`,
282
- object: "chat.completion",
283
- created: Math.floor(Date.now() / 1000),
284
- model: requestData.model,
285
- choices: [
286
- {
287
- index: 0,
288
- message: {
289
- role: "assistant",
290
- content: chunks.join('')
291
- },
292
- finish_reason: "stop"
293
- }
294
- ],
295
- usage: {
296
- prompt_tokens: null,
297
- completion_tokens: null,
298
- total_tokens: null
299
- }
300
- };
301
-
302
- res.json(fullResponse);
303
- resolve();
304
- });
305
-
306
- stream.on('error', (error) => {
307
- logger.error(`非流式响应出错: ${error}`);
308
- reject(error);
309
- });
310
-
311
- // 处理客户端断开连接
312
- req.on('close', () => {
313
- logger.info(`客户端 ${clientId} 断开连接(非流式)`);
314
- if (activeStreams.has(clientId)) {
315
- try {
316
- activeStreams.get(clientId).end();
317
- activeStreams.delete(clientId);
318
- } catch (error) {
319
- logger.error(`关闭流时出错: ${error.message}`);
320
- }
321
- }
322
- });
323
- });
324
- }
325
- } catch (error) {
326
- logger.error(`聊天完成端点错误: ${error}`);
327
- res.status(500).json({
328
- error: {
329
- message: `Internal server error: ${error.message}`,
330
- type: "server_error"
331
  }
332
- });
333
- }
334
- });
335
-
336
- // 健康检查端点
337
- app.get('/health', (req, res) => {
338
- res.json({
339
- status: 'ok',
340
- timestamp: new Date().toISOString(),
341
- initialized: INITIALIZED_SUCCESSFULLY,
342
- valid_cookies: cookieManager.getValidCount(),
343
- active_streams: activeStreams.size,
344
- proxy_configured: !!PROXY_URL
345
- });
346
- });
347
-
348
- // Cookie状态查询端点
349
- app.get('/cookies/status', authenticate, (req, res) => {
350
- res.json({
351
- total_cookies: cookieManager.getValidCount(),
352
- cookies: cookieManager.getStatus()
353
- });
354
- });
355
-
356
- // 代理状态查询端点
357
- app.get('/proxy/status', authenticate, (req, res) => {
358
- const safeProxyUrl = PROXY_URL ? PROXY_URL.replace(/:([^:@]+)@/, ':****@') : null;
359
- res.json({
360
- proxy_configured: !!PROXY_URL,
361
- proxy_url: safeProxyUrl,
362
- proxy_type: PROXY_URL ? 'fixed' : 'none'
363
- });
364
- });
365
-
366
- // 启动服务器
367
- const PORT = process.env.PORT || 7860;
368
-
369
- // 初始化并启动服务器
370
- initialize().then(async () => {
371
- // 验证代理配置
372
- await validateProxy();
373
-
374
- // 将监听地址改为 '0.0.0.0'
375
- app.listen(PORT, '0.0.0.0', () => {
376
- logger.info(`服务已在 0.0.0.0:${PORT} 上启动`);
377
- logger.info(`访问地址: http://0.0.0.0:${PORT}`);
378
-
379
- // 显示代理状态
380
- if (PROXY_URL) {
381
- const safeProxyUrl = PROXY_URL.replace(/:([^:@]+)@/, ':****@');
382
- logger.info(`代理配置: ${safeProxyUrl}`);
383
- } else {
384
- logger.info(`代理配置: 未配置代理,使用直连`);
385
- }
386
-
387
- if (INITIALIZED_SUCCESSFULLY) {
388
- logger.success(`系统初始化状态: ✅`);
389
- logger.success(`可用cookie数量: ${cookieManager.getValidCount()}`);
390
- } else {
391
- logger.warning(`系统初始化状态: ❌`);
392
- logger.warning(`警告: 系统未成功初始化,API调用将无法正常工作`);
393
- logger.warning(`请检查NOTION_COOKIE配置是否有效`);
394
  }
395
- });
396
- }).catch((error) => {
397
- logger.error(`初始化失败: ${error}`);
398
  });
 
1
+ // 启动服务器
2
+ const PORT = process.env.PORT || 7860;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ // 简化的启动流程
5
+ console.log('🚀 开始启动服务器...');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
+ // 先启动服务器,再异步初始化
8
+ app.listen(PORT, '0.0.0.0', () => {
9
+ logger.success(`🚀 服务器已在 0.0.0.0:${PORT} 启动成功`);
10
+ logger.info(`📡 访问地址: http://0.0.0.0:${PORT}`);
11
 
12
+ // 显示代理配置
13
+ if (PROXY_URL) {
14
+ const safeProxyUrl = PROXY_URL.replace(/:([^:@]+)@/, ':****@');
15
+ logger.info(`🌐 代理配置: ${safeProxyUrl}`);
16
+ } else {
17
+ logger.info(`🌐 代理配置: 直连模式`);
 
18
  }
19
 
20
+ // 异步初始化,避免阻塞启动
21
+ setTimeout(async () => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  try {
23
+ logger.info('🔧 开始系统初始化...');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ // 初始化系统
26
+ const initResult = await initialize();
27
+ INITIALIZED_SUCCESSFULLY = initResult !== false;
28
 
29
+ // 验证代(如果配置了)
30
+ if (PROXY_URL) {
31
+ try {
32
+ await validateProxy();
33
+ } catch (error) {
34
+ logger.warning(`代理验证失败: ${error.message}`);
 
 
 
 
35
  }
36
+ }
 
 
 
 
 
 
 
 
 
37
 
38
+ // 显示最终状态
39
+ if (INITIALIZED_SUCCESSFULLY) {
40
+ logger.success(`✅ 系统初始化完成`);
41
+ logger.success(`🍪 可用Cookie数量: ${cookieManager.getValidCount()}`);
42
+ } else {
43
+ logger.warning(`❌ 系统初始化失败`);
44
+ logger.warning(`⚠️ 请检查NOTION_COOKIE配置`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  }
46
+
47
+ logger.success(`🎯 所有服务已就绪!`);
48
+
49
+ } catch (error) {
50
+ logger.error(`❌ 初始化过程出错: ${error.message}`);
51
+ INITIALIZED_SUCCESSFULLY = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
53
+ }, 1000); // 1秒后开始初始化
 
 
54
  });