AI摘要:

正在生成中……


感谢@Mayx 大佬的鼎力协助,帮助我完成Wordpress上的适配,大佬原文是基于静态博客制作的教程,而本教程将会在WordPress上部署。

创建一个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.

将创建好的数据库绑定到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


正因为知道可以在空中翱翔,才会畏惧展翅的那一刻而忘却疾风 努力学习ing