52 私有链接
这段内容主要介绍了两个开发者为了解决Qwen API的流式输出问题所做的尝试。一位开发者用PHP脚本获取并保存临时token,并通过轮询token进行API请求,但未完全适配OpenAI格式。另一位开发者则使用Deno重写了代码,提高了输出速度,并修复了流式输出重复的问题。他们还分享了Deno代码,并提供了API地址和使用方法,同时讨论了可能存在的API Key被屏蔽的问题以及解决方案。
这段内容主要讨论了如何使用API调用一个名为 "puter-chat-completion" 的服务,并使用 Claude 模型。最初遇到了一些问题,比如返回奇怪的结果,后来发现需要正确设置请求节点。之后,作者通过 Java 代码成功发送了请求,并获得了 200 的状态码。最后,作者指出获取 token 是目前最大的问题,并且提到可以通过反代 API 来解决地区限制,并需要抓取生成新账号的方式。
内容主要解决了在处理Qwen API响应时出现的重复问题。通过使用cursor
编写了一个worker,在透传响应时过滤掉之前的内容,只返回新增部分,从而避免重复。代码中配置了Qwen API的URL、最大重试次数和延迟时间,并实现了处理流式响应的逻辑,确保只发送新的增量内容。此外,还处理了模型列表请求和授权验证等细节。
// Qwen API 配置
const QWEN_API_URL = 'https://chat.qwenlm.ai/api/chat/completions';
const QWEN_MODELS_URL = 'https://chat.qwenlm.ai/api/models';
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000; // 1秒
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchWithRetry(url, options, retries = MAX_RETRIES) {
let lastError;
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
// 克隆响应以便检查
const responseClone = response.clone();
const responseText = await responseClone.text();
const contentType = response.headers.get('content-type') || '';
// 如果返回 HTML 错误页面或 500 错误,进行重试
if (contentType.includes('text/html') || response.status === 500) {
lastError = {
status: response.status,
contentType,
responseText: responseText.slice(0, 1000),
headers: Object.fromEntries(response.headers.entries())
};
// 如果不是最后一次重试,则继续
if (i < retries - 1) {
await sleep(RETRY_DELAY * (i + 1)); // 指数退避
continue;
}
}
// 返回原始响应的新副本
return new Response(responseText, {
status: response.status,
headers: {
'Content-Type': contentType || 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
} catch (error) {
lastError = error;
if (i < retries - 1) {
await sleep(RETRY_DELAY * (i + 1));
continue;
}
}
}
// 所有重试都失败了
throw new Error(JSON.stringify({
error: true,
message: 'All retry attempts failed',
lastError,
retries
}));
}
async function processLine(line, writer, previousContent) {
const encoder = new TextEncoder();
try {
const data = JSON.parse(line.slice(6));
if (data.choices && data.choices[0] && data.choices[0].delta && data.choices[0].delta.content) {
const currentContent = data.choices[0].delta.content;
// 计算新的增量内容
let newContent = currentContent;
if (currentContent.startsWith(previousContent) && previousContent.length > 0) {
newContent = currentContent.slice(previousContent.length);
}
// 创建新的响应对象
const newData = {
...data,
choices: [{
...data.choices[0],
delta: {
...data.choices[0].delta,
content: newContent
}
}]
};
// 发送新的响应
await writer.write(encoder.encode(`data: ${JSON.stringify(newData)}\n\n`));
return currentContent;
} else {
// 如果没有内容,直接转发原始数据
await writer.write(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
return previousContent;
}
} catch (e) {
// 如果解析失败,直接转发原始数据
await writer.write(encoder.encode(`${line}\n\n`));
return previousContent;
}
}
// 处理流
async function handleStream(reader, writer, previousContent, timeout) {
const encoder = new TextEncoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
clearTimeout(timeout);
// 处理剩余的缓冲区
if (buffer) {
const lines = buffer.split('\n');
for (const line of lines) {
if (line.trim().startsWith('data: ')) {
await processLine(line, writer, previousContent);
}
}
}
await writer.write(encoder.encode('data: [DONE]\n\n'));
await writer.close();
break;
}
const chunk = new TextDecoder().decode(value);
buffer += chunk;
// 处理完整的行
const lines = buffer.split('\n');
buffer = lines.pop() || ''; // 保留不完整的行
for (const line of lines) {
if (line.trim().startsWith('data: ')) {
const result = await processLine(line, writer, previousContent);
if (result) {
previousContent = result;
}
}
}
}
} catch (error) {
clearTimeout(timeout);
await writer.write(encoder.encode(`data: {"error":true,"message":"${error.message}"}\n\n`));
await writer.write(encoder.encode('data: [DONE]\n\n'));
await writer.close();
}
}
async function handleRequest(request) {
try {
// 处理获取模型列表的请求
if (request.method === 'GET' && new URL(request.url).pathname === '/api/models') {
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', {
status: 401,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
try {
const response = await fetchWithRetry(QWEN_MODELS_URL, {
headers: {
'Authorization': authHeader
}
});
const modelsResponse = await response.text();
return new Response(modelsResponse, {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
} catch (error) {
return new Response(JSON.stringify({
error: true,
message: error.message
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
}
if (request.method !== 'POST') {
return new Response('Method not allowed', {
status: 405,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
// 获取授权头
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', {
status: 401,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
const requestData = await request.json();
const { messages, stream = false, model, max_tokens } = requestData;
if (!model) {
return new Response(JSON.stringify({
error: true,
message: 'Model parameter is required'
}), {
status: 400,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
// 构建发送到 Qwen 的请求
const qwenRequest = {
model,
messages,
stream
};
// 只有当用户设置了 max_tokens 时才添加
if (max_tokens !== undefined) {
qwenRequest.max_tokens = max_tokens;
}
// 发送请求到 Qwen API
const response = await fetch(QWEN_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': authHeader
},
body: JSON.stringify(qwenRequest)
});
// 如果是流式响应
if (stream) {
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
const reader = response.body.getReader();
let previousContent = '';
// 设置超时
const timeout = setTimeout(() => {
writer.write(new TextEncoder().encode('data: {"error":true,"message":"Response timeout"}\n\n'));
writer.write(new TextEncoder().encode('data: [DONE]\n\n'));
writer.close();
}, 60000);
// 处理流
handleStream(reader, writer, previousContent, timeout).catch(async (error) => {
clearTimeout(timeout);
const encoder = new TextEncoder();
await writer.write(encoder.encode(`data: {"error":true,"message":"${error.message}"}\n\n`));
await writer.write(encoder.encode('data: [DONE]\n\n'));
await writer.close();
});
return new Response(readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
// 非流式响应
const responseText = await response.text();
return new Response(responseText, {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
} catch (error) {
return new Response(JSON.stringify({
error: true,
message: error.message
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
这段内容主要讨论了一个将qwenlm.ai转换为API的项目(Chat2Api)。该项目通过GitHub提供,并支持自动获取模型。用户可以通过浏览器开发者工具获取OpenWebUI的API密钥。一些用户认为该项目本质上是一个代理,而非真正的Chat2API,因为它只是将OpenAI格式的API请求转发到qwenlm.ai。同时,也有用户指出直接使用qwenlm.ai的API即可,无需额外转换,并且使用反向代理可能会带来安全风险。
Qwen团队推出了基于Open WebUI的“Qwen Chat”在线体验平台,用户可以免费体验包括Qwen2.5-Plus在内的多种模型。此外,社区讨论还涉及其他AI动态,如GitHub Copilot支持Claude模型、智谱清言推出免费模型、Grok网页版上线以及xAI推出API等。
这个内容介绍了一个使用 Cloudflare Workers 和 KV 存储构建的简易管理系统,方便用户在手机端查看、删除和更新 KV 值。该系统支持字符串、数组和 JSON 对象,用户需要将代码复制到 Workers 中,设置环境变量 AUTH_CODE
,并配置要管理的 KV 变量。源代码已在 GitHub 上提供。
这个Python脚本是一个用于操作阿里云OSS(对象存储服务)的工具。它允许用户上传文件和文件夹,列出文件(包括指定前缀的文件),删除文件(包括指定前缀的文件),以及创建目录。用户可以通过命令行交互选择功能,并输入相应的路径或前缀。该脚本主要用于方便地将图片或文件上传到OSS,生成URL,以便用于AI或图床等场景。
import os
import oss2
from dotenv import load_dotenv
from pathlib import Path
import logging
from rich.console import Console
from rich.prompt import Prompt, IntPrompt
from rich.table import Table
# 初始化日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
console = Console()
# 加载环境变量,需在.env文件中配置OSS_ENDPOINT,OSS_ACCESS_KEY_ID,OSS_ACCESS_KEY_SECRET,OSS_BUCKET_NAME
load_dotenv()
class OSSManager:
def __init__(self, OSS_ENDPOINT, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET, OSS_BUCKET_NAME):
"""
初始化OSSManager,设置认证和存储桶
"""
self.auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
self.bucket = oss2.Bucket(self.auth, OSS_ENDPOINT, OSS_BUCKET_NAME)
self.OSS_ENDPOINT = OSS_ENDPOINT
self.OSS_ACCESS_KEY_ID = OSS_ACCESS_KEY_ID
self.OSS_ACCESS_KEY_SECRET = OSS_ACCESS_KEY_SECRET
self.OSS_BUCKET_NAME = OSS_BUCKET_NAME
logging.info("OSSManager 初始化成功")
def upload_file(self, local_path: str, upload_path: str = "uploads", object_name: str = None):
"""
上传文件到阿里云OSS并返回文件URL
:param local_path: 本地文件路径
:param upload_path: 上传到OSS后的目录
:param object_name: 上传到OSS后的文件名
:return: 文件名和URL
"""
local_file = Path(local_path)
if not local_file.exists():
logging.error(f"文件不存在: {local_path}")
return None, None
upload_dir = Path(upload_path) if upload_path else Path("uploads")
self.create_dir(str(upload_dir))
object_name = object_name or local_file.name
object_key = f"{upload_dir}/{object_name}"
try:
self.bucket.put_object_from_file(str(object_key), str(local_file))
logging.info(f"文件上传成功: {object_key}")
except oss2.exceptions.OssError as e:
logging.error(f"上传失败: {e}")
return None, None
url = f"https://{self.OSS_BUCKET_NAME}.{self.OSS_ENDPOINT}/{object_key}"
return object_key, url
def upload_dir(self, local_dir: str, upload_path: str = None):
"""
上传目录到阿里云OSS并返回上传的目录路径
:param local_dir: 本地目录路径
:param upload_path: 上传到OSS后的目录
:return: 上传后的目录路径
"""
local_directory = Path(local_dir)
if not local_directory.exists() or not local_directory.is_dir():
logging.error(f"目录不存在或不是目录: {local_dir}")
return None
upload_dir = upload_path or local_directory.name
self.create_dir(upload_dir)
for item in local_directory.iterdir():
if item.is_file():
self.upload_file(str(item), upload_dir)
elif item.is_dir():
self.upload_dir(str(item), f"{upload_dir}/{item.name}")
logging.info(f"目录上传完成: {upload_dir}")
return upload_dir
def delete_file(self, file_path: str):
"""
删除文件
:param file_path: 文件路径
"""
try:
self.bucket.delete_object(file_path)
logging.info(f"文件已删除: {file_path}")
except oss2.exceptions.OssError as e:
logging.error(f"删除失败: {e}")
def delete_files_by_prefix(self, prefix: str):
"""
删除指定前缀的文件
:param prefix: 前缀
"""
try:
for obj in oss2.ObjectIterator(self.bucket, prefix=prefix):
self.bucket.delete_object(obj.key)
logging.info(f"文件已删除: {obj.key}")
except oss2.exceptions.OssError as e:
logging.error(f"删除失败: {e}")
def list_files(self, prefix: str = ""):
"""
列举指定前缀的文件
:param prefix: 前缀
"""
try:
table = Table(title="OSS 文件列表")
table.add_column("序号", justify="right", style="cyan", no_wrap=True)
table.add_column("文件名", style="magenta")
table.add_column("URL", style="green")
for idx, obj in enumerate(oss2.ObjectIterator(self.bucket, prefix=prefix)):
url = f"https://{self.OSS_BUCKET_NAME}.{self.OSS_ENDPOINT}/{obj.key}"
table.add_row(str(idx), obj.key, url)
console.print(table)
except oss2.exceptions.OssError as e:
logging.error(f"列举文件失败: {e}")
return oss2.ObjectIterator(self.bucket, prefix=prefix)
def create_dir(self, dir_path: str):
"""
创建目录
:param dir_path: 目录路径
"""
try:
self.bucket.put_object(f"{dir_path}/", '')
logging.info(f"目录已创建: {dir_path}")
except oss2.exceptions.OssError as e:
logging.error(f"创建目录失败: {e}")
def main():
# 从环境变量中获取配置信息
OSS_ENDPOINT = os.getenv('OSS_ENDPOINT')
OSS_ACCESS_KEY_ID = os.getenv('OSS_ACCESS_KEY_ID')
OSS_ACCESS_KEY_SECRET = os.getenv('OSS_ACCESS_KEY_SECRET')
OSS_BUCKET_NAME = os.getenv('OSS_BUCKET_NAME')
if not all([OSS_ENDPOINT, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET, OSS_BUCKET_NAME]):
console.print("[red]缺少必要的环境变量,请检查 .env 文件。[/red]")
return
oss_manager = OSSManager(OSS_ENDPOINT, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET, OSS_BUCKET_NAME)
while True:
console.print("\n[bold blue]请选择功能:[/bold blue]")
console.print("1. 上传文件")
console.print("2. 上传文件夹")
console.print("3. 列举所有文件")
console.print("4. 列举指定前缀的文件")
console.print("5. 删除文件")
console.print("6. 删除指定前缀的文件")
console.print("7. 创建目录")
console.print("8. 退出")
choice = Prompt.ask("请输入选择", choices=[str(i) for i in range(1, 9)])
if choice == "1":
local_path = Prompt.ask("请输入本地文件路径")
upload_path = Prompt.ask("请输入上传到OSS后的目录(默认为 uploads)", default="uploads")
object_name = Prompt.ask("请输入上传到OSS后的文件名(回车使用文件的原名)", default="", show_default=False)
object_name = object_name if object_name else None
oss_manager.upload_file(local_path, upload_path, object_name)
elif choice == "2":
folder_path = Prompt.ask("请输入本地文件夹路径")
upload_path = Prompt.ask("请输入上传到OSS后的目录(默认为同名目录)", default="", show_default=False)
upload_path = upload_path if upload_path else None
oss_manager.upload_dir(folder_path, upload_path)
elif choice == "3":
oss_manager.list_files()
elif choice == "4":
prefix = Prompt.ask("请输入前缀", default="")
oss_manager.list_files(prefix)
elif choice == "5":
file_path = Prompt.ask("请输入文件路径")
oss_manager.delete_file(file_path)
elif choice == "6":
prefix = Prompt.ask("请输入前缀")
oss_manager.delete_files_by_prefix(prefix)
elif choice == "7":
dir_path = Prompt.ask("请输入目录路径")
oss_manager.create_dir(dir_path)
elif choice == "8":
console.print("退出程序。")
break
else:
console.print("[red]无效选择,请重新输入。[/red]")
if __name__ == "__main__":
main()
这个工具主要功能是帮助用户快速处理网页内容。它可以将网页内容复制为Markdown格式,并进行智能总结,提取要点。总结后的内容可以转换为Markdown思维导图或直接下载为XMind文件。此外,它还提供历史记录功能方便用户回顾。该工具使用Gemini 2.0 Flash模型,速度快且总结效果好,但需要用户提供自己的API Key才能使用。
这段内容介绍了如何使用开源的Gemini-Search项目,用户可以通过fork项目并填写自己的Google AI Key,快速搭建自己的Google Research。由于服务器部署困难,文中还分享了一些已经部署好的Gemini-Search网站链接,并提醒用户注意隐私。
fofa搜 title="Gemini Search" && is_domain=true
Doclingo是一款专注于PDF翻译的工具,提供PC、手机等多平台支持,并具备高还原格式、精准翻译和OCR识别等特色。现在通过留言“兑换码领取”即可获得7天会员试用,体验包括GPT4o、Gemini2.0等付费引擎的增值服务,以及免费的PDF编辑和总结工具。开发者希望用户体验后能提供建议,以便进一步优化产品。
已转存百度网盘
这段内容分享了一个免费的图片和视频换脸软件,据说效果比Stable Diffusion好,速度也很快。使用时注意安装路径和文件名不能有中文。软件启动后有教程,但链接有效期10天。分享者声明资源仅供学习,禁止商用和违法用途。软件基于开源模型,并做了一些整合。提供了夸克网盘和百度网盘的下载链接。
Clone Replayer 是安卓上最好的软件,配合 Aboboo (PC) 可以自动断句并生成 LRC/SRT 字幕。使用时,需要在设置中将播放速度控制改为 Sonic 模式,以避免暂停/继续时崩溃的问题。
阅读辅助,根据章节自动总结和梳理逻辑,形成图表,辅助阅读理解
这个项目整合了之前的三个项目,提供了一个快速部署Gemini多模态对话网站的方案。它使用Deno或Cloudflare Worker进行无服务器部署,解决了国内使用Cloudflare Worker可能出现的地区限制问题。项目将Gemini API转换为OpenAI格式,方便国内用户直连,并可对接AI编程和ChatBox等客户端。用户只需准备Gemini API Key,即可通过简单的步骤快速部署使用。
ping0.cc的jskey计算办法
def parse_window_x(html):
logger.info('Parsing window.x value...')
match = re.search(r"window\.x\s*=\s*'([^']+)'", html)
window_x = match.group(1) if match else None
logger.info(f'Parsed window.x: {window_x}')
return window_x
改为
def parse_window_x(html):
logger.info('Parsing window.x value...')
match = re.search(r"window\.x\d*\s*=\s*'([^']+)'", html)
window_x = match.group(1) if match else None
logger.info(f'Parsed window.x: {window_x}')
return parse_window_x1_to_jskey(window_x)
补充多方法
def parse_window_x1_to_jskey(window_x1):
"""
解析 `window.x1` 的值,按每 8 个字符切分,转换为十六进制并累加,返回 `jskey`
:param window_x1: 输入的字符串
:return: 解析出的 `jskey` 值 (整数)
"""
total = 0
for i in range(0, len(window_x1), 8): # 每 8 个字符切片
slice_value = window_x1[i:i + 8]
total += int(slice_value, 16) # 转换为十六进制数并累加
return str(total)
Edge TTS Worker 是一个部署在 Cloudflare Worker 上的代理服务,它可以让你在无需微软认证的情况下,使用微软高质量的语音合成服务。它提供兼容 OpenAI 格式的 API 接口,支持多种语言和语音,并且完全免费。你可以通过简单的步骤在 Cloudflare 上部署,并使用提供的测试脚本来选择合适的语音效果。该服务支持自定义 API 密钥和域名,但请注意仅供学习和个人使用,不建议用于商业项目。
curl --location --request POST 'https://api.openai-hub.com/v1/audio/speech' \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data-raw '{ "model": "tts-1", "response_format":"mp3", "input": "The quick brown fox jumped over the lazy dog.", "voice": "alloy" }'
Edge TTS Worker 是一个部署在 Cloudflare Worker 上的代理服务,它将微软 Edge TTS 服务封装成 OpenAI 兼容的 API 接口。该服务主要解决国内使用 Edge TTS 时遇到的 403 错误,无需微软认证即可使用高质量的语音合成服务。简单来说,它让你在国内也能方便地用上微软的语音合成功能。
都说读书是跟作者对话。
可以让AI扮演作者,解释他写的每一句话。:)
Prompt如下:
作为这段文字的作者,请用简单的话帮我解释:
{selection}
要求:
- 用3-4句话说明我写这句话时想表达的核心意思
- 像跟朋友聊天一样,用通俗易懂的语言
- 解释我选择这样表达的原因
请直接以第一人称开始回答,不需要任何格式引导语。
这段内容列出了一系列在线工具,包括SEO、写作、设计、学习等领域的工具,例如SEMrush、Canva、ChatGPT等。同时提供了一个工具集合网站toolspik的注册链接,并说明优惠截止到2025年1月31日。简单来说,这是一个在线工具列表,并附带一个优惠注册链接。