dbei1 / main.ts
hzruo's picture
Update main.ts
0e09aac verified
raw
history blame contribute delete
20.4 kB
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { Md5 } from "https://deno.land/[email protected]/hash/md5.ts";
const API_DOMAIN = 'https://ai-api.dangbei.net';
const USER_AGENTS = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'
];
const VALID_API_KEY = Deno.env.get('VALID_API_KEY');
const MAX_CONVERSATIONS_PER_DEVICE = 10; // 每个设备最多创建的会话数
class ChatManage {
private currentDeviceId: string | null = null;
private currentConversationId: string | null = null;
private conversationCount = 0;
private currentUserAgent: string;
constructor() {
this.currentUserAgent = this.getRandomUserAgent();
}
private getRandomUserAgent(): string {
return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
}
getOrCreateIds(forceNew = false) {
let newDeviceId = this.currentDeviceId;
let newConversationId = this.currentConversationId;
if (forceNew || !newDeviceId || this.conversationCount >= MAX_CONVERSATIONS_PER_DEVICE) {
newDeviceId = this.generateDeviceId();
newConversationId = null;
this.conversationCount = 0;
// 在生成新设备ID时更新 User-Agent
this.currentUserAgent = this.getRandomUserAgent();
}
this.currentDeviceId = newDeviceId;
this.currentConversationId = newConversationId;
return {
deviceId: newDeviceId,
conversationId: newConversationId,
userAgent: this.currentUserAgent
};
}
updateConversationId(conversationId: string) {
this.currentConversationId = conversationId;
this.conversationCount++;
}
generateDeviceId() {
const uuid = crypto.randomUUID();
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
const nanoid = Array.from(crypto.getRandomValues(new Uint8Array(20)))
.map(b => urlAlphabet[b % urlAlphabet.length])
.join('');
return `${uuid.replace(/-/g, '')}_${nanoid}`;
}
}
class Pipe {
private dataPrefix = 'data:';
private chatManage = new ChatManage();
private searchModels: Record<string, string> = {
'DeepSeek-R1-Search': 'deepseek',
'DeepSeek-V3-Search': 'deepseek',
'Doubao-Search': 'doubao',
'Qwen-Search': 'qwen'
};
// 创建新的会话
async _create_conversation(deviceId: string) {
const { userAgent } = this.chatManage.getOrCreateIds(false);
const payload = { botCode: "AI_SEARCH" };
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = this.nanoid(21);
const sign = await this.generateSign(timestamp, payload, nonce);
const headers = {
"Origin": "https://ai.dangbei.com",
"Referer": "https://ai.dangbei.com/",
"User-Agent": userAgent,
"deviceId": deviceId,
"nonce": nonce,
"sign": sign,
"timestamp": timestamp,
"Content-Type": "application/json"
};
try {
console.log('Creating conversation with:', {
url: `${API_DOMAIN}/ai-search/conversationApi/v1/create`,
headers,
payload
});
const response = await fetch(`${API_DOMAIN}/ai-search/conversationApi/v1/create`, {
method: 'POST',
headers,
body: JSON.stringify(payload),
});
console.log('Response status:', response.status);
const responseText = await response.text();
console.log('Response body:', responseText);
if (response.ok) {
try {
const data = JSON.parse(responseText);
if (data.success) {
console.log('Successfully created conversation:', data.data.conversationId);
return data.data.conversationId;
} else {
console.error('API returned success: false:', data);
}
} catch (e) {
console.error('Failed to parse response:', e);
}
} else {
console.error('HTTP error:', response.status, responseText);
}
} catch (e) {
console.error('Error creating conversation:', e);
}
return null;
}
// 新增方法:构建完整提示
_buildFullPrompt(messages: any[]): string {
if (!messages || messages.length === 0) {
return '';
}
let systemPrompt = '';
const history: string[] = [];
let lastUserMessage = '';
for (const msg of messages) {
if (msg.role === 'system' && !systemPrompt) {
systemPrompt = msg.content;
} else if (msg.role === 'user') {
history.push(`user: ${msg.content}`);
lastUserMessage = msg.content;
} else if (msg.role === 'assistant') {
history.push(`assistant: ${msg.content}`);
}
}
const parts: string[] = [];
if (systemPrompt) {
parts.push(`[System Prompt]\n${systemPrompt}`);
}
if (history.length > 1) {
parts.push(`[Chat History]\n${history.slice(0, -1).join('\n')}`);
}
parts.push(`[Question]\n${lastUserMessage}`);
return parts.join('\n\n');
}
async* pipe(body: any) {
const thinkingState = { thinking: -1 };
// Build full prompt
const fullPrompt = this._buildFullPrompt(body.messages);
// Check if we need to force new conversation
let forceNew = false;
const messages = body.messages;
if (messages.length === 1) {
forceNew = true;
} else if (messages.length >= 2) {
const lastTwo = messages.slice(-2);
if (lastTwo[0].role === 'user' && lastTwo[1].role === 'user') {
forceNew = true;
}
}
// Get or create device ID and conversation ID with User-Agent
const { deviceId, conversationId: storedConversationId, userAgent } = this.chatManage.getOrCreateIds(forceNew);
let conversationId = storedConversationId;
// Create new conversation if needed
if (!conversationId) {
conversationId = await this._create_conversation(deviceId);
if (!conversationId) {
yield { error: 'Failed to create conversation' };
return;
}
this.chatManage.updateConversationId(conversationId);
}
// Model name handling
let modelName;
const isSearchModel = body.model.endsWith('-Search');
if (isSearchModel) {
modelName = this.searchModels[body.model] || body.model.replace('-Search', '').toLowerCase();
} else {
const isDeepSeekModel = ['DeepSeek-R1', 'DeepSeek-V3'].includes(body.model);
modelName = isDeepSeekModel ? 'deepseek' : body.model.toLowerCase();
}
// 确定 userAction 参数
let userAction = '';
if (body.model.includes('DeepSeek-R1')) {
userAction = 'deep';
}
if (isSearchModel) {
userAction = userAction ? `${userAction},online` : 'online';
}
const payload = {
stream: true,
botCode: 'AI_SEARCH',
userAction,
model: modelName,
conversationId: conversationId,
question: fullPrompt,
};
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = this.nanoid(21);
const sign = await this.generateSign(timestamp, payload, nonce);
const headers = {
'Origin': 'https://ai.dangbei.com',
'Referer': 'https://ai.dangbei.com/',
'User-Agent': userAgent,
'deviceId': deviceId,
'nonce': nonce,
'sign': sign,
'timestamp': timestamp,
'Content-Type': 'application/json',
};
try {
const response = await fetch(`${API_DOMAIN}/ai-search/chatApi/v1/chat`, {
method: 'POST',
headers,
body: JSON.stringify(payload),
});
if (!response.ok) {
const error = await response.text();
console.error('HTTP Error:', response.status, error);
yield { error: `HTTP ${response.status}: ${error}` };
return;
}
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
let cardMessages: string[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.startsWith(this.dataPrefix)) continue;
try {
const data = JSON.parse(line.slice(this.dataPrefix.length));
if (data.type === 'answer') {
const content = data.content;
const contentType = data.content_type;
if (thinkingState.thinking === -1 && contentType === 'thinking') {
thinkingState.thinking = 0;
yield { choices: [{ delta: { content: '<think>\n\n' }, finish_reason: null }] };
} else if (thinkingState.thinking === 0 && contentType === 'text') {
thinkingState.thinking = 1;
yield { choices: [{ delta: { content: '\n' }, finish_reason: null }] };
yield { choices: [{ delta: { content: '</think>' }, finish_reason: null }] };
yield { choices: [{ delta: { content: '\n\n' }, finish_reason: null }] };
}
if (contentType === 'card') {
try {
const cardContent = JSON.parse(content);
const cardItems = cardContent.cardInfo.cardItems;
let markdownOutput = '\n\n---\n\n';
const searchKeywords = cardItems.find((item: any) => item.type === '2001');
if (searchKeywords) {
const keywords = JSON.parse(searchKeywords.content);
markdownOutput += `搜索关键字:${keywords.join('; ')}\n\n`;
}
const searchResults = cardItems.find((item: any) => item.type === '2002');
if (searchResults) {
const results = JSON.parse(searchResults.content);
markdownOutput += `共找到 ${results.length} 个搜索结果:\n\n`;
results.forEach((result: any) => {
markdownOutput += `[${result.idIndex}] [${result.name}](${result.url}) 来源:${result.siteName}\n`;
});
}
cardMessages.push(markdownOutput);
} catch (e) {
console.error('Error processing card:', e);
}
}
if (content && (contentType === 'text' || contentType === 'thinking')) {
yield { choices: [{ delta: { content }, finish_reason: null }] };
}
}
} catch (e) {
console.error('Parse error:', e, 'Line:', line);
yield { error: `JSONDecodeError: ${(e as Error).message}` };
return;
}
}
}
if (cardMessages.length > 0) {
yield { choices: [{ delta: { content: cardMessages.join('') }, finish_reason: null }] };
}
yield {
choices: [{
delta: {
meta: {
device_id: deviceId,
conversation_id: conversationId
}
},
finish_reason: null
}]
};
} catch (e) {
console.error('Error in pipe:', e);
yield { error: `${(e as Error).name}: ${(e as Error).message}` };
}
}
nanoid(size = 21) {
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
const bytes = new Uint8Array(size);
crypto.getRandomValues(bytes);
return Array.from(bytes).reverse().map(b => urlAlphabet[b & 63]).join('');
}
async generateSign(timestamp: string, payload: any, nonce: string) {
const payloadStr = JSON.stringify(payload);
const signStr = `${timestamp}${payloadStr}${nonce}`;
console.log('Sign string:', signStr);
// 使用 Deno 标准库的 MD5
const sign = new Md5()
.update(signStr)
.toString()
.toUpperCase();
console.log('Generated sign:', sign);
return sign;
}
}
const pipe = new Pipe();
// 验证 API 密钥
function verifyApiKey(request: Request) {
const authorization = request.headers.get('Authorization');
// 检查环境变量是否配置
if (!VALID_API_KEY) {
return new Response(JSON.stringify({ error: 'API key not configured' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
if (!authorization) {
return new Response(JSON.stringify({ error: 'Missing API key' }), {
status: 401,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
const apiKey = authorization.replace('Bearer ', '').trim();
if (apiKey !== VALID_API_KEY) {
return new Response(JSON.stringify({ error: 'Invalid API key' }), {
status: 401,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
return null;
}
async function handleRequest(request: Request) {
const url = new URL(request.url);
// 添加根路径处理
if (request.method === 'GET' && url.pathname === '/') {
return new Response("it's work!", {
headers: {
'Content-Type': 'text/plain',
'Access-Control-Allow-Origin': '*',
},
});
}
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
// 验证 API 密钥(除了 OPTIONS 请求)
const authError = verifyApiKey(request);
if (authError) return authError;
if (request.method === 'GET' && url.pathname === '/v1/models') {
const currentTime = Math.floor(Date.now() / 1000);
return new Response(JSON.stringify({
object: 'list',
data: [
// Original models
{
id: 'DeepSeek-R1',
object: 'model',
created: currentTime,
owned_by: 'library'
},
{
id: 'DeepSeek-V3',
object: 'model',
created: currentTime,
owned_by: 'library'
},
{
id: 'Doubao',
object: 'model',
created: currentTime,
owned_by: 'library'
},
{
id: 'Qwen',
object: 'model',
created: currentTime,
owned_by: 'library'
},
{
id: 'Glm3',
object: 'model',
created: currentTime,
owned_by: 'library'
},
{
id: 'Moonshot_v1',
object: 'model',
created: currentTime,
owned_by: 'library'
},
// Search-enabled models
{
id: 'DeepSeek-R1-Search',
object: 'model',
created: currentTime,
owned_by: 'library',
features: ['online_search']
},
{
id: 'DeepSeek-V3-Search',
object: 'model',
created: currentTime,
owned_by: 'library',
features: ['online_search']
},
{
id: 'Doubao-Search',
object: 'model',
created: currentTime,
owned_by: 'library',
features: ['online_search']
},
{
id: 'Qwen-Search',
object: 'model',
created: currentTime,
owned_by: 'library',
features: ['online_search']
},
{
id: 'Glm3-Search',
object: 'model',
created: currentTime,
owned_by: 'library',
features: ['online_search']
},
{
id: 'Moonshot_v1-Search',
object: 'model',
created: currentTime,
owned_by: 'library',
features: ['online_search']
}
]
}), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
if (request.method === 'POST' && url.pathname === '/v1/chat/completions') {
const body = await request.json();
const isStream = body.stream || false;
if (isStream) {
const stream = new ReadableStream({
async start(controller) {
try {
for await (const chunk of pipe.pipe(body)) {
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`));
}
controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
controller.close();
} catch (e) {
console.error('Error in stream:', e);
controller.error(e);
}
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
},
});
}
if (!isStream) {
let content = '';
let meta = null;
let thinking_content: string[] = [];
let is_thinking = false;
try {
for await (const chunk of pipe.pipe(body)) {
if (chunk.choices?.[0]?.delta?.content) {
const content_chunk = chunk.choices[0].delta.content;
if (content_chunk === '<think>\n\n') {
is_thinking = true;
} else if (content_chunk === '\n</think>\n\n') {
is_thinking = false;
} else if (is_thinking) {
thinking_content.push(content_chunk);
} else {
content += content_chunk;
}
}
if (chunk.choices?.[0]?.delta?.meta) {
meta = chunk.choices[0].delta.meta;
}
}
// 处理思考内容
const reasoningContent = thinking_content.join('');
return new Response(JSON.stringify({
id: crypto.randomUUID(),
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: body.model,
choices: [{
message: {
role: 'assistant',
reasoning_content: reasoningContent ? `<think>\n${reasoningContent}\n</think>` : '',
content: content.trim(),
meta: meta
},
finish_reason: 'stop'
}]
} as NonStreamResponse), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
} catch (e) {
console.error('Error processing chat request:', e);
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
}
}
return new Response('Not Found', { status: 404 });
}
serve(handleRequest, { port: 7860 });
interface Message {
role: string;
content: string;
}
interface ChatRequest {
model: string;
messages: Message[];
stream: boolean;
temperature?: number;
top_p?: number;
n?: number;
max_tokens?: number;
presence_penalty?: number;
frequency_penalty?: number;
user?: string;
}
interface DeltaContent {
content?: string;
meta?: {
device_id: string;
conversation_id: string;
};
}
interface Choice {
delta: DeltaContent;
finish_reason: string | null;
}
interface StreamResponse {
choices?: Choice[];
error?: string;
}
interface NonStreamResponse {
id: string;
object: string;
created: number;
model: string;
choices: Array<{
message: {
role: string;
reasoning_content: string;
content: string;
meta: {
device_id: string;
conversation_id: string;
};
};
finish_reason: string;
}>;
}