AI摘要:正在生成中……
# 特别鸣谢-
感谢@Mayx 大佬的鼎力协助,帮助我完成Wordpress上的适配,大佬原文是基于静态博客制作的教程,而本教程将会在WordPress上部署。
# 后端准备
## 创建Cloudflare Workers和DV 数据库:
创建一个LLM应用
部署以下代码:
async function sha(str) {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join(""); // convert bytes to hex string
return hashHex;
}
async function md5(str) {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const hashBuffer = await crypto.subtle.digest("MD5", data);
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join(""); // convert bytes to hex string
return hashHex;
}
export default {
async fetch(request, env, ctx) {
const db = env.blog_summary;
const url = new URL(request.url);
const query = decodeURIComponent(url.searchParams.get('id'));
const commonHeader = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': "*",
'Access-Control-Allow-Headers': "*",
'Access-Control-Max-Age': '86400',
}
if (query == "null") {
return new Response("id cannot be none", {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': "*",
'Access-Control-Allow-Headers': "*",
'Access-Control-Max-Age': '86400',
}
});
}
if (url.pathname.startsWith("/summary")) {
let result = await db.prepare(
"SELECT content FROM blog_summary WHERE id = ?1"
).bind(query).first("content");
if (!result) {
return new Response("No Record", {
headers: commonHeader
});
}
const messages = [
{
role: "system", content: `
你是一个专业的文章摘要助手。你的主要任务是对各种文章进行精炼和摘要,帮助用户快速了解文章的核心内容。你读完整篇文章后,能够提炼出文章的关键信息,以及作者的主要观点和结论。
技能
精炼摘要:能够快速阅读并理解文章内容,提取出文章的主要关键点,用简洁明了的中文进行阐述。
关键信息提取:识别文章中的重要信息,如主要观点、数据支持、结论等,并有效地进行总结。
客观中立:在摘要过程中保持客观中立的态度,避免引入个人偏见。
约束
输出内容必须以中文进行。
必须确保摘要内容准确反映原文章的主旨和重点。
尊重原文的观点,不能进行歪曲或误导。
在摘要中明确区分事实与作者的意见或分析。
提示
不需要在回答中注明摘要(不需要使用冒号),只需要输出内容。
格式
你的回答格式应该如下:
这篇文章介绍了<这里是内容>
` },
{ role: "user", content: result.substring(0, 5000) }
]
const stream = await env.AI.run('@cf/qwen/qwen1.5-14b-chat-awq', {
messages,
stream: true,
});
return new Response(stream, {
headers: {
"content-type": "text/event-stream; charset=utf-8",
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': "*",
'Access-Control-Allow-Headers': "*",
'Access-Control-Max-Age': '86400',
}
});
} else if (url.pathname.startsWith("/get_summary")) {
const orig_sha = decodeURIComponent(url.searchParams.get('sign'));
let result = await db.prepare(
"SELECT content FROM blog_summary WHERE id = ?1"
).bind(query).first("content");
if (!result) {
return new Response("no", {
headers: commonHeader
});
}
let result_sha = await sha(result);
if (result_sha != orig_sha) {
return new Response("no", {
headers: commonHeader
});
} else {
let resp = await db.prepare(
"SELECT summary FROM blog_summary WHERE id = ?1"
).bind(query).first("summary");
if (resp) {
return new Response(resp, {
headers: commonHeader
});
} else {
const messages = [
{
role: "system", content: `
你是一个专业的文章摘要助手。你的主要任务是对各种文章进行精炼和摘要,帮助用户快速了解文章的核心内容。你读完整篇文章后,能够提炼出文章的关键信息,以及作者的主要观点和结论。
技能
精炼摘要:能够快速阅读并理解文章内容,提取出文章的主要关键点,用简洁明了的中文进行阐述。
关键信息提取:识别文章中的重要信息,如主要观点、数据支持、结论等,并有效地进行总结。
客观中立:在摘要过程中保持客观中立的态度,避免引入个人偏见。
约束
输出内容必须以中文进行。
必须确保摘要内容准确反映原文章的主旨和重点。
尊重原文的观点,不能进行歪曲或误导。
在摘要中明确区分事实与作者的意见或分析。
提示
不需要在回答中注明摘要(不需要使用冒号),只需要输出内容。
格式
你的回答格式应该如下:
这篇文章介绍了<这里是内容>
` },
{ role: "user", content: result.substring(0, 5000) }
]
const answer = await env.AI.run('@cf/qwen/qwen1.5-14b-chat-awq', {
messages,
stream: false,
});
resp = answer.response
await db.prepare("UPDATE blog_summary SET summary = ?1 WHERE id = ?2")
.bind(resp, query).run();
return new Response(resp, {
headers: commonHeader
});
}
}
} else if (url.pathname.startsWith("/is_uploaded")) {
const orig_sha = decodeURIComponent(url.searchParams.get('sign'));
let result = await db.prepare(
"SELECT content FROM blog_summary WHERE id = ?1"
).bind(query).first("content");
if (!result) {
return new Response("no", {
headers: commonHeader
});
}
let result_sha = await sha(result);
if (result_sha != orig_sha) {
return new Response("no", {
headers: commonHeader
});
} else {
return new Response("yes", {
headers: commonHeader
});
}
} else if (url.pathname.startsWith("/upload_blog")) {
if (request.method == "POST") {
const data = await request.text();
let result = await db.prepare(
"SELECT content FROM blog_summary WHERE id = ?1"
).bind(query).first("content");
if (!result) {
await db.prepare("INSERT INTO blog_summary(id, content) VALUES (?1, ?2)")
.bind(query, data).run();
result = await db.prepare(
"SELECT content FROM blog_summary WHERE id = ?1"
).bind(query).first("content");
}
if (result != data) {
await db.prepare("UPDATE blog_summary SET content = ?1, summary = NULL WHERE id = ?2")
.bind(data, query).run();
}
return new Response("OK", {
headers: commonHeader
});
} else {
return new Response("need post", {
headers: commonHeader
});
}
} else if (url.pathname.startsWith("/count_click")) {
let id_md5 = await md5(query);
let count = await db.prepare("SELECT `counter` FROM `counter` WHERE `url` = ?1")
.bind(id_md5).first("counter");
if (url.pathname.startsWith("/count_click_add")) {
if (!count) {
await db.prepare("INSERT INTO `counter` (`url`, `counter`) VALUES (?1, 1)")
.bind(id_md5).run();
count = 1;
} else {
count += 1;
await db.prepare("UPDATE `counter` SET `counter` = ?1 WHERE `url` = ?2")
.bind(count, id_md5).run();
}
}
if (!count) {
count = 0;
}
return new Response(count, {
headers: commonHeader
});
} else {
return Response.redirect("这里是直接访问接口跳转的地址", 302)
}
}
}
如果想要私有接口而不想让别人使用,则可以简单做一个防盗链:
const referer = request.headers.get('Referer');
// 检查 Referer 是否为 www.gymxbl.com 或 www.iloli.xin
if (!referer || (!referer.includes('www.gymxbl.com'))) {
return new Response(JSON.stringify({
"code": "418",
"text": "Invalid referer, no permission"
}), { status: 418, headers: { 'Content-Type': 'application/json' } });
}
放入export default {
}内。
因为AI摘要不需要每次都重新生成,所以我们需要创建一个DV数据库。
引用一下大佬原文:另外我也不希望每次打开文章都重新生成摘要,那样不仅浪费计算资源,而且毫无意义,毕竟文章又不会变。所以我首先考虑怎么样存AI生成的结果呢?另外为了能通过POST把文章喂给AI我也得考虑存文章。最开始我想着用Workers的KV数据库,因为那是最早出的,虽然限制很多但当时没得选。但这次点开发现居然有个D1数据库,容量更大,延迟更低,操作次数更多而且还支持SQL语法,这不比那个KV数据库好太多了,这下都不知道这个KV数据库留着还有啥意义了,可能就单纯是为了兼容以前的应用不得不留着了吧。
导航到存储和数据库-D1 Sql数据库,名称无缺省值,位置位于亚太地区。
创建完成后,我们需要向数据库中创建一张表,在表中创建三个字段(列),分别为“summary”,“content”和“id”,分别存储生成好的简介,文章和标题内容,以及文章的唯一ID.
## 关联DV Sql数据库及域名设置:
将创建好的数据库绑定到Workers,导航到AI摘要Workers的设置页面,找到绑定选项,选择添加,绑定D1数据库,这里需要注意,D1数据库的名称必须为
blog_summary |
否则将无法正常工作。
另外,需要一个新的,单独的子域进行路由,例如ai.example.com,不能使用www.example.com/ai这样的路由方式。添加一个自定义域,正常情况下cloudflare会自动处理好一切,如果您使用SaaS域或 部分DNS托管 则需要进行进一步处理,如配置DNS确保边缘证书生效。
部署后访问Workers,id cannot be none则证明后端已准备就绪。
# 前端准备
需要将文章内容和标题提交给AI,才能生成简介,并根据文章ID索引已经生成的简介,原文中使用页面URL作为ID,因为我们使用Wordpress,所以直接使用Wordpress生成的文章唯一ID:
<blockquote><b>AI摘要</b>
<p id="ai-output">正在生成中……</p>
<script>
async function sha(str) {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join(""); // convert bytes to hex string
return hashHex;
}
async function ai_gen() {
// 获取文章标题,并去掉网站名称避免误导AI
var postTitle = document.querySelector('title').innerText.split('你的网站名称')[0].trim();
// 获取文章正文内容,请以主题为准,以Sakura为例,文章正文在<div class="entry-content">下
var articleElement = document.querySelector('.entry-content');
if (!articleElement) {
console.error('无法找到文章内容区域');
return;
}
// 克隆文章内容节点,以便在不影响页面的情况下操作内容
var clonedArticleElement = articleElement.cloneNode(true);
// 移除克隆节点中的 blockquote 标签,避免引用内容(版权信息访问时间过期提示AI摘要)
var blockquotes = clonedArticleElement.querySelectorAll('blockquote');
blockquotes.forEach(bq => bq.remove());
var postContent = clonedArticleElement.innerText;
// 打印处理后的内容以进行调试
//console.log("处理后的文章标题:", postTitle);
//console.log("处理后的文章内容:", postContent);
var postContentCombined = "文章标题:" + postTitle + ";文章内容:" + postContent;
var postContentSign = await sha(postContentCombined);
var outputContainer = document.getElementById("ai-output");
$.get("https://你的域名/is_uploaded?id=<?php echo get_the_ID(); ?>&sign=" + postContentSign, function (data) {
if (data == "yes") {
$.get("https://你的域名/get_summary?id=<?php echo get_the_ID(); ?>&sign=" + postContentSign, function (data2) {
outputContainer.textContent = data2;
});
} else {
$.post("https://你的域名/upload_blog?id=<?php echo get_the_ID(); ?>", postContentCombined, function (data) {
$.get("https://你的域名/get_summary?id=<?php echo get_the_ID(); ?>&sign=" + postContentSign);
const evSource = new EventSource("https://你的域名/summary?id=<?php echo get_the_ID(); ?>");
outputContainer.textContent = "";
evSource.onmessage = (event) => {
if (event.data == "[DONE]") {
evSource.close();
return;
} else {
const data = JSON.parse(event.data);
outputContainer.textContent += data.response;
}
}
});
}
});
}
// 睡一会儿防止页面未完全加载
setTimeout(ai_gen, 1500); // 延迟1.5秒执行 ai_gen 函数
</script>
</blockquote>
因为生成后摘要就被存储下来,下次直接提供而非实时生成,所以只有第一次生成会采用流式传输,再次访问则是提供生成好的摘要,如果想要摘要每次都像流式传输那样的效果,则可以使用一些LLM伪流式传输,原理为将获得的摘要以打字特效方式输出,就像这样:
这篇文章介绍了如何在Nginx中使用LUA脚本实现reCAPTCHA验证码验证,以防止HTTP-DDoS (CC) 攻击。作者首先分享了改用LUA而非PHP的原因,PHP在高访问压力下易导致服务器负载过高。文章详细介绍了安装和配置LUA环境,包括识别Nginx是否已安装LUA模块,以及需要的cjson和curl库。作者提供了一个LUA脚本,处理验证请求、获取和检查用户提交的验证码响应、并与reCAPTCHA服务器交互验证_
相关样式脚本不再赘述,将以上前端代码放入主题文章页面模板中,以Sakura为例,文章模板在tpl/content-single.php,重启PHP并清除页面缓存后,即可看到摘要....
# 小彩蛋
根据同样的原理,依据大佬指导,消失已久的Live2D聊天也已经重新上线,原先的图灵机器人API没钱买不好用才不得不下线,现在终于恢复啦!
版权声明:转载时请以超链接形式标明文章原始出处和作者信息,来源孤影墨香本文链接: https://www.gymxbl.com/4310.html
访问时间:2025-02-11 06:44:12
Comments | 1 条评论
Ciallo~(∠・ω< )⌒★ 小絮下午好!