发布于 2026-01-05 13 阅读
0

🧙‍​​♂️ 使用 ChatGPT 助手生成博客 🪄 ✨ TL;DR 由 Mux 呈现的 DEV 全球展示挑战赛:展示你的项目!

🧙‍​​♂️ 使用 ChatGPT 助手生成博客 🪄 ✨

太长不看

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

太长不看

我们都见识过 ChatGPT 的功能(这对任何人来说都不是什么新鲜事)。

使用 ChatGPT 撰写的文章太多了,而且很多文章都是重复撰写的。

实际上,DEV 上一半的文章都是用 ChatGPT 编写的。

你可以使用一些人工智能内容检测器来查看。

问题在于,ChatGPT 除了它内部已有的(经过训练/微调的)内容之外,永远不会生成任何非凡的内容。

但是,我们可以利用 RAG(OpenAI 助手)超越当前训练的内容。

在上一篇文章中,我们讨论了如何利用文档“训练”ChatGPT;今天,我们来看看如何从中生成大量内容。我们将:

  • 使用 Docusaurus 创建一个新的博客系统。
  • 请 ChatGPT 为我们撰写一篇与文档相关的博客文章。

博客


您的背景工作平台🔌

Trigger.dev 是一个开源库,它使您能够使用 NextJS、Remix、Astro 等为您的应用程序创建和监控长时间运行的任务!

 

GiveUsStars

请给我们点个赞🥹。
这将帮助我们创作更多类似的文章💖

给 Trigger.dev 仓库点赞⭐️


上次回顾⏰

  • 我们创建了一个任务,用于读取文档 XML 并提取所有 URL。
  • 我们抓取了每个网站的URL,并提取了标题和内容。
  • 我们将所有内容保存到一个文件中,并将其发送给了 ChatGPT 助手。
  • 我们创建了一个聊天机器人屏幕,用来向 ChatGPT 询问有关文档的问题。

您可以在这里找到上一篇教程的完整源代码


工具

稍作修改⚙️

上次,我们创建了一个文档助手。我们写道:

You are a documentation assistant, loaded with documentation from ' + payload.url + ', return everything in an MD format.
Enter fullscreen mode Exit fullscreen mode

让我们把它改成博客作者,转到jobs/process.documentation.ts第 92 行,并将其替换为以下内容:

You are a content writer assistant. You have been loaded with documentation from ${payload.url}, you write blog posts based on the documentation and return everything in the following MD format:
---
slug: [post-slug]
title: [post-title]
---

[post-content]
Enter fullscreen mode Exit fullscreen mode

必须使用 `<script>`slug和 `<script>`title格式,因为这是我们的博客系统 Docusaurus 可以接受的格式(当然,我们也会以 MD 格式发送所有输出)。


恐龙纪录片

Docusaurus 🦖

你可以使用多种类型的博客系统!

在我们的用例中,我们将使用 Docusaurus,它可以读取基于 MD 格式的文件(即我们从 ChatGPT 请求的输出)。我们可以通过运行以下命令来安装 Docusaurus:

npx create-docusaurus@latest blog classic --typescript 
Enter fullscreen mode Exit fullscreen mode

接下来,我们可以进入创建的目录并运行以下命令:

npm run start
Enter fullscreen mode Exit fullscreen mode

这将启动 Docusaurus。您可以留意一下。还有一个blog包含所有博客文章的目录;我们将把 ChatGPT 生成的博客文章保存到那里。

例子


博客

生成博客📨

我们需要创造一份工作,

  • 获取博客标题
  • 使用 ChatGPT 生成完整的博客文章
  • 将其保存到我们博客上的 MD 文件中

我们可以轻松地使用 ChatGPT 来实现这一点!

进入jobs文件夹并添加一个名为 . 的新文件process.blog.ts。添加以下代码:

import { eventTrigger } from "@trigger.dev/sdk";
import { client } from "@openai-assistant/trigger";
import {object, string} from "zod";
import {openai} from "@openai-assistant/helper/open.ai";
import {writeFileSync} from "fs";
import slugify from "slugify";

client.defineJob({
  // This is the unique identifier for your Job, it must be unique across all Jobs in your project.
  id: "process-blog",
  name: "Process Blog",
  version: "0.0.1",
  // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction
  trigger: eventTrigger({
    name: "process.blog.event",
    schema: object({
      title: string(),
      aId: string(),
    })
  }),
  integrations: {
    openai
  },
  run: async (payload, io, ctx) => {
      const {title, aId} = payload;
      const thread = await io.openai.beta.threads.create('create-thread');

      await io.openai.beta.threads.messages.create('create-message', thread.id, {
          content: `
           title: ${title}
          `,
          role: 'user',
      });

      const run = await io.openai.beta.threads.runs.createAndWaitForCompletion('run-thread', thread.id, {
          model: 'gpt-4-1106-preview',
          assistant_id: payload.aId,
      });

      if (run.status !== "completed") {
          console.log('not completed');
          throw new Error(`Run finished with status ${run.status}: ${JSON.stringify(run.last_error)}`);
      }

      const messages = await io.openai.beta.threads.messages.list("list-messages", run.thread_id, {
          query: {
              limit: "1"
          }
      });

      return io.runTask('save-blog', async () => {
          const content = messages[0].content[0];
          if (content.type === 'text') {
              const fileName = slugify(title, {lower: true, strict: true, trim: true});
              writeFileSync(`./blog/blog/${fileName}.md`, content.text.value)
              return {fileName};
          }
      });
  },
});
Enter fullscreen mode Exit fullscreen mode
  • 我们添加了一些必要的变量:
    • title博客文章标题
    • aId上一篇文章中添加的助手 ID。
  • 我们为助手创建了一个新的线程io.openai.beta.threads.create——没有线程就无法提问。与之前的教程不同,这里我们每次请求都会创建一个新线程。我们不需要对话中最后几条消息的上下文。
  • 然后,我们在主题帖中添加一条新消息(io.openai.beta.threads.messages.create),内容为博客标题。无需提供其他说明——我们在第一部分已经完成了这部分 😀
  • 我们运行该命令io.openai.beta.threads.runs.createAndWaitForCompletion来启动流程——通常,你需要某种递归机制每分钟运行一次来​​检查任务是否完成,但Trigger.dev已经添加了一种方法,可以同时运行流程并等待其完成🥳
  • 我们在查询正文中运行该io.openai.beta.threads.messages.list查询,limit: 1以获取对话中的第一条消息(在 ChatGPT 结果中,第一条消息是最后一条消息)。
  • 然后,我们使用从 ChatGPT 获取的值保存新创建的博客writeFileSync- 请确保您拥有正确的博客路径。

请转到jobs/index.ts并添加以下行:

export * from "./process.blog";
Enter fullscreen mode Exit fullscreen mode

现在,让我们创建一个新路由来触发该任务。

转到app/api,创建一个名为的新文件夹blog,并在其中创建一个名为的新文件route.tsx

添加以下代码:

import {client} from "@openai-assistant/trigger";

export async function POST(request: Request) {
    const payload = await request.json();
    if (!payload.title || !payload.aId) {
        return new Response(JSON.stringify({error: 'Missing parameters'}), {status: 400});
    }

    // We send an event to the trigger to process the documentation
    const {id: eventId} = await client.sendEvent({
        name: "process.blog.event",
        payload
    });

    return new Response(JSON.stringify({eventId}), {status: 200});
}
Enter fullscreen mode Exit fullscreen mode
  • 我们检查标题和助理 ID 是否存在。
  • 我们在Trigger.dev中触发一个事件并发送信息。
  • 我们会将事件 ID 发送回客户端,以便跟踪作业进度。

前端

前端🎩

没什么办法了!

在我们的components目录下,创建一个名为 `.yml` 的新文件blog.component.tsx,并添加以下代码:

"use client";

import {FC, useCallback, useEffect, useState} from "react";
import {ExtendedAssistant} from "@openai-assistant/components/main";
import {SubmitHandler, useForm} from "react-hook-form";
import {useEventRunDetails} from "@trigger.dev/react";

interface Blog {
    title: string,
    aId: string;
}

export const BlogComponent: FC<{list: ExtendedAssistant[]}> = (props) => {
    const {list} = props;
    const {register, formState, handleSubmit} = useForm<Blog>();
    const [event, setEvent] = useState<string | undefined>(undefined);

    const addBlog: SubmitHandler<Blog> = useCallback(async (param) => {
        const {eventId} = await (await fetch('/api/blog', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(param)
        })).json();

        setEvent(eventId);
    }, []);

    return (
        <>
        <form className="flex flex-col gap-3 mt-5" onSubmit={handleSubmit(addBlog)}>
            <div className="flex flex-col gap-1">
                <div className="font-bold">Assistant</div>
                <select className="border border-gray-200 rounded-xl py-2 px-3" {...register('aId', {required: true})}>
                    {list.map(val => (
                        <option key={val.id} value={val.aId}>{val.url}</option>
                    ))}
                </select>
            </div>
            <div className="flex flex-col gap-1">
                <div className="font-bold">Title</div>
                <input className="border border-gray-200 rounded-xl py-2 px-3" placeholder="Blog title" {...register('title', {required: true})} />
            </div>
            <button className="border border-gray-200 rounded-xl py-2 px-3 bg-gray-100 hover:bg-gray-200" disabled={formState.isSubmitting}>Create blog</button>
        </form>
        {!!event && (
            <Blog eventId={event} />
        )}
        </>
    )
}

export const Blog: FC<{eventId: string}> = (props) => {
    const {eventId} = props;
    const { data, error } = useEventRunDetails(eventId);

    if (data?.status !== 'SUCCESS') {
        return <div className="pointer bg-yellow-300 border-yellow-500 p-1 px-3 text-yellow-950 border rounded-2xl">Loading</div>
    }
    return (
        <div>
            <a href={`http://localhost:3000/blog/${data.output.fileName}`}>Check blog post</a>
        </div>
    )
};
Enter fullscreen mode Exit fullscreen mode
  • 我们过去常常react-hook-form能够轻松控制输入。
  • 我们允许用户选择他们想要使用的助手。
  • 我们创建一个新的输入框,其中包含文章标题。
  • 我们将所有任务发送到我们之前创建的路线,并取回eventId任务结果。
  • 我们创建了一个新<Blog />组件,该组件会显示加载状态直到事件完成,并添加一个指向我们博客的链接,其中包含新创建的教程。

将该组件添加到我们的components/main.tsx文件中:

{assistantState.filter(f => !f.pending).length > 0 && <BlogComponent list={assistantState} />}
Enter fullscreen mode Exit fullscreen mode

我们完成了!

完毕

现在,让我们添加博客标题并点击生成。

博客


图片

让我们联系吧!🔌

作为一名开源开发者,您可以加入我们的 社区 ,贡献代码并与维护者互动。欢迎访问我们的 GitHub 代码库 ,为 Trigger.dev 贡献代码并创建相关问题。

本教程的原文链接如下:

https://github.com/triggerdotdev/blog/tree/main/openai-blog-writer

感谢阅读!

文章来源:https://dev.to/triggerdotdev/generate-blogs-with-chatgpt-assistant-1894