Spaces:
Running
Running
const express = require("express"); | |
const { io } = require("socket.io-client"); | |
const { v4: uuidv4 } = require("uuid"); | |
const { ProxyAgent } = require("proxy-agent"); | |
// 创建代理实例 | |
const 代理 = new ProxyAgent(); | |
// 获取环境变量值并赋给中文变量名 | |
const 曲奇池 = process.env.COOKIE_POOL || ''; | |
const 用户代理 = process.env.USER_AGENT || ''; | |
const 全局代理 = process.env.ALL_PROXY || ''; | |
const 应用 = express(); | |
const 端口 = process.env.PORT || 7860; | |
// 解析曲奇池,支持单个或多个曲奇(用逗号分隔) | |
const cookiePool = 曲奇池 | |
? 曲奇池.split(',').map(cookie => cookie.trim()).filter(cookie => cookie.length > 0) | |
: []; | |
if (cookiePool.length === 0) { | |
console.error("错误:环境变量 曲奇池 未设置或为空。"); | |
process.exit(1); | |
} | |
// 创建事件流的工具函数 | |
function 创建事件(事件, 数据) { | |
if (typeof 数据 === "object") { | |
数据 = JSON.stringify(数据); | |
} | |
return `event: ${事件}\ndata: ${数据}\n\n`; | |
} | |
// 处理 POST 请求的路由 /v1/messages | |
应用.post("/v1/messages", (请求, 响应) => { | |
请求.rawBody = ""; | |
请求.setEncoding("utf8"); | |
// 收集请求的原始数据 | |
请求.on("data", function (片段) { | |
请求.rawBody += 片段; | |
}); | |
请求.on("end", async () => { | |
响应.setHeader("Content-Type", "text/event-stream;charset=utf-8"); | |
try { | |
let 请求数据 = JSON.parse(请求.rawBody); | |
if (请求数据.stream == false) { | |
// 当 stream 为 false 时,返回固定的响应 | |
响应.send( | |
JSON.stringify({ | |
id: uuidv4(), | |
content: [ | |
{ | |
text: "请开启流式传输。", | |
}, | |
{ | |
id: "string", | |
name: "string", | |
input: {}, | |
}, | |
], | |
model: "string", | |
stop_reason: "end_turn", | |
stop_sequence: "string", | |
usage: { | |
input_tokens: 0, | |
output_tokens: 0, | |
}, | |
}) | |
); | |
} else if (请求数据.stream == true) { | |
// 处理 stream 为 true 的情况 | |
let 用户消息 = [{ 问题: "", 回答: "" }]; | |
let 最后更新 = true; | |
if (请求数据.system) { | |
// 将系统消息添加到消息列表的首位 | |
请求数据.messages.unshift({ role: "system", content: 请求数据.system }); | |
} | |
console.log(请求数据.messages); | |
// 遍历消息列表,构建用户和助手的问答对 | |
请求数据.messages.forEach((消息) => { | |
if (消息.role == "system" || 消息.role == "user") { | |
if (最后更新) { | |
用户消息[用户消息.length - 1].问题 += 消息.content + "\n"; | |
} else if (用户消息[用户消息.length - 1].问题 == "") { | |
用户消息[用户消息.length - 1].问题 += 消息.content + "\n"; | |
} else { | |
用户消息.push({ 问题: 消息.content + "\n", 回答: "" }); | |
} | |
最后更新 = true; | |
} else if (消息.role == "assistant") { | |
if (!最后更新) { | |
用户消息[用户消息.length - 1].回答 += 消息.content + "\n"; | |
} else if (用户消息[用户消息.length - 1].回答 == "") { | |
用户消息[用户消息.length - 1].回答 += 消息.content + "\n"; | |
} else { | |
用户消息.push({ 问题: "", 回答: 消息.content + "\n" }); | |
} | |
最后更新 = false; | |
} | |
}); | |
// 查找最新的用户消息 | |
const 最新用户消息 = 请求数据.messages | |
.slice().reverse() | |
.find(消息 => 消息.role === "user"); | |
if (!最新用户消息) { | |
throw new Error("请求中未找到用户消息。"); | |
} | |
// 获取最新用户消息的索引 | |
const 最新用户索引 = 请求数据.messages.lastIndexOf(最新用户消息); | |
// 构建除最新用户消息外的所有消息 | |
const 先前消息列表 = 请求数据.messages.slice(0, 最新用户索引); | |
// 改进上下文拼接格式,增加提示并避免重复 | |
let 先前消息 = 先前消息列表 | |
.map((消息) => { | |
if (消息.role === "user") { | |
return `用户: ${消息.content}`; | |
} else if (消息.role === "assistant") { | |
return `助手: ${消息.content}`; | |
} else if (消息.role === "system") { | |
return `系统: ${消息.content}`; | |
} | |
return 消息.content; | |
}) | |
.join("\n\n"); | |
// 在上下文末尾添加提示,不再重复添加最新的用户消息 | |
先前消息 += `\n\n# ↓请关注用户最新的消息↓\n\n`; | |
// 追加最新的用户消息 | |
先前消息 += `用户: ${最新用户消息.content}`; | |
let 消息ID = uuidv4(); | |
// 发送消息开始的事件 | |
响应.write( | |
创建事件("message_start", { | |
type: "message_start", | |
message: { | |
id: 消息ID, | |
type: "message", | |
role: "assistant", | |
content: [], | |
model: "claude-3-opus-20240229", | |
stop_reason: null, | |
stop_sequence: null, | |
usage: { input_tokens: 8, output_tokens: 1 }, | |
}, | |
}) | |
); | |
响应.write(创建事件("content_block_start", { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } })); | |
响应.write(创建事件("ping", { type: "ping" })); | |
// 随机选择一个曲奇 | |
const 随机曲奇 = cookiePool[Math.floor(Math.random() * cookiePool.length)]; | |
// 配置 Socket.IO 客户端选项 | |
var 选项 = { | |
agent: 代理, | |
auth: { | |
jwt: "anonymous-ask-user", | |
}, | |
reconnection: false, | |
transports: ["websocket"], | |
path: "/socket.io", | |
hostname: "www.perplexity.ai", | |
secure: true, | |
port: "443", | |
extraHeaders: { | |
Cookie: 随机曲奇, // 使用随机选择的曲奇 | |
"User-Agent": 用户代理, | |
Accept: "*/*", | |
priority: "u=1, i", | |
Referer: "https://www.perplexity.ai/", | |
}, | |
}; | |
// 通过 Socket.IO 连接 Perplexity.ai 的 WebSocket | |
var socket = io("wss://www.perplexity.ai/", 选项); | |
socket.on("connect", function () { | |
console.log(" > [已连接]"); | |
socket | |
.emitWithAck("perplexity_ask", 先前消息, { | |
"version": "2.9", | |
"source": "default", | |
"attachments": [], | |
"language": "en-GB", | |
"timezone": "Europe/London", | |
"search_focus": "writing", | |
"frontend_uuid": uuidv4(), | |
"mode": "concise", | |
"is_related_query": false, | |
"is_default_related_query": false, | |
"visitor_id": uuidv4(), | |
"frontend_context_uuid": uuidv4(), | |
"prompt_source": "user", | |
"query_source": "home" | |
}) | |
.then((响应数据) => { | |
console.log(响应数据); | |
响应.write(创建事件("content_block_stop", { type: "content_block_stop", index: 0 })); | |
响应.write( | |
创建事件("message_delta", { | |
type: "message_delta", | |
delta: { stop_reason: "end_turn", stop_sequence: null }, | |
usage: { output_tokens: 12 }, | |
}) | |
); | |
响应.write(创建事件("message_stop", { type: "message_stop" })); | |
响应.end(); | |
}).catch((错误) => { | |
if(错误.message != "socket has been disconnected"){ | |
console.log(错误); | |
} | |
}); | |
}); | |
// 监听所有事件,记录日志 | |
socket.onAny((事件, ...参数) => { | |
console.log(`> [收到事件: ${事件}]`); | |
}); | |
// 监听查询进度事件,处理返回的文本块 | |
socket.on("query_progress", (数据) => { | |
if(数据.text){ | |
try { | |
var 文本 = JSON.parse(数据.text) | |
var 块 = 文本.chunks[文本.chunks.length - 1]; | |
if (块) { | |
let 块JSON = JSON.stringify({ | |
type: "content_block_delta", | |
index: 0, | |
delta: { type: "text_delta", text: 块 }, | |
}); | |
响应.write(创建事件("content_block_delta", 块JSON)); | |
} | |
} catch (错误) { | |
console.log("解析 query_progress 数据时出错:", 错误); | |
} | |
} | |
}); | |
// 监听断开连接事件 | |
socket.on("disconnect", function () { | |
console.log(" > [已断开连接]"); | |
}); | |
// 监听错误事件,返回错误信息给客户端 | |
socket.on("error", (错误) => { | |
let 块JSON = JSON.stringify({ | |
type: "content_block_delta", | |
index: 0, | |
delta: { type: "text_delta", text: "获取输出时出现错误\n请查看日志以获取更多信息" }, | |
}); | |
响应.write(创建事件("content_block_delta", 块JSON)); | |
响应.write(创建事件("content_block_stop", { type: "content_block_stop", index: 0 })); | |
响应.write( | |
创建事件("message_delta", { | |
type: "message_delta", | |
delta: { stop_reason: "end_turn", stop_sequence: null }, | |
usage: { output_tokens: 12 }, | |
}) | |
); | |
响应.write(创建事件("message_stop", { type: "message_stop" })); | |
响应.end(); | |
console.log(错误); | |
}); | |
// 监听连接错误事件,返回错误信息给客户端 | |
socket.on("connect_error", function (错误) { | |
let 块JSON = JSON.stringify({ | |
type: "content_block_delta", | |
index: 0, | |
delta: { type: "text_delta", text: "连接到 Perplexity.ai 失败\n请查看日志以获取更多信息" }, | |
}); | |
响应.write(创建事件("content_block_delta", 块JSON)); | |
响应.write(创建事件("content_block_stop", { type: "content_block_stop", index: 0 })); | |
响应.write( | |
创建事件("message_delta", { | |
type: "message_delta", | |
delta: { stop_reason: "end_turn", stop_sequence: null }, | |
usage: { output_tokens: 12 }, | |
}) | |
); | |
响应.write(创建事件("message_stop", { type: "message_stop" })); | |
响应.end(); | |
console.log(错误); | |
}); | |
// 监听客户端关闭事件,断开 Socket 连接 | |
响应.on("close", function () { | |
console.log(" > [客户端已关闭连接]"); | |
socket.disconnect(); | |
}); | |
} else { | |
throw new Error("无效的请求"); | |
} | |
} catch (错误) { | |
console.log(错误); | |
响应.write(JSON.stringify({ error: 错误.message })); | |
响应.end(); | |
return; | |
} | |
}); | |
}); | |
// 处理所有未定义的路由,返回 418 | |
应用.use((请求, 响应, 下一个) => { | |
响应.status(418).send("418 I'm a teapot"); | |
}); | |
// 启动服务器,监听指定端口 | |
应用.listen(端口, () => { | |
console.log(`Perplexity 代理服务器正在监听端口 ${端口}`); | |
}); | |