深度解析:如何利用ChatGPT人工智能AI模型结合Next.js框架构建自动化AI生成帖子的完整教程与实战指南!

项目内容

通过简单的不久可以实现在LinkedIn上创建AI生成的帖子,风靡一时

图片[1]-深度解析:如何利用ChatGPT人工智能AI模型结合Next.js框架构建自动化AI生成帖子的完整教程与实战指南!

配置环境

使用Tea下一代跨平台包管理器来构建开发环境,安装node,npm,vercel和开发所需的其他软件包。

sh <(curl https://tea.xyz)

# --- or ---
# use brew
brew install tea

使用TypeScript和Tailwindcss设置Next.js

创建一个新的 Next.js 项目可以使用 TypeScript + Tailwind CSS的形式,例如:

npx create-next-app  
  
# ---  
  
What is your project named? my-app  
Would you like to add TypeScript with this project? (y/N)  
# Type 'y' to add TypeScript  
Would you like to use ESLint with this project? (y/N)  
# Type 'y' to use ESLint  
Would you like to use Tailwind CSS with this project? (y/N)  
# Type 'y' to use Tailwind CSS  
Would you like to use the `src/` directory with this project? (y/N)  
# Type 'n' to not use the `src/` directory  
What import alias would you like configured? @/*  
# Type '@/*' as the import alias

注意:

  1. 在提示中,通常默认选项是大写的(如 N),表示如果你什么都不输入,它会选择默认选项(即不使用该功能)。
  2. 对于 What import alias would you like configured? 这个问题,实际上是在告诉你想使用的别名模式,而不是询问是否要配置别名。所以这里没有 y/N 的选项。

按照这些提示操作,可以创建一个带有 TypeScript、ESLint、Tailwind CSS 并配置 @/* 作为导入别名的 Next.js 项目,但不使用 src/ 目录结构。

构建项目

1:twitter-algorithm https://github.com/coryetzkorn/twitter-algorithm

图片[2]-深度解析:如何利用ChatGPT人工智能AI模型结合Next.js框架构建自动化AI生成帖子的完整教程与实战指南!
图片[3]-深度解析:如何利用ChatGPT人工智能AI模型结合Next.js框架构建自动化AI生成帖子的完整教程与实战指南!

2:Twitter Bio generator https://github.com/Nutlope/twitterbio

图片[4]-深度解析:如何利用ChatGPT人工智能AI模型结合Next.js框架构建自动化AI生成帖子的完整教程与实战指南!

构建项目

使用以下函数:

  1. 检测多个主题标签的函数
  2. 检测图像或视频的函数
  3. 检测网址的函数
  4. 支持帖子使用表情符号的函数
  5. 宣传负面内容的函数
  6. 像帖子格式一样优先处理中断的函数
  7. 缩短线长的函数
  8. 提问的函数

原代码(未优化):

// 检测多个标签的函数
function multipleHashtags({ post }: PostData): Rank {
  const regex = /#[\w-]+/g;
  const hashtags = post.match(regex);
  const lowerCasePost = post.toLowerCase();

  if (hashtags && hashtags.length > 3) {
    return {
      score: 0.5,
      message: "使用了太多的标签。",
    };
  }
  if (hashtags && hashtags.length <= 3) {
    if (
      lowerCasePost.includes("#follow") ||
      lowerCasePost.includes("#comment") ||
      lowerCasePost.includes("#like")
    ) {
      return {
        score: 0.5,
        message: "避免使用类似“follow”、“comment”或“like”的标签。",
      };
    }
    return {
      score: 1,
      message: "结合一般性和特定性的标签。",
    };
  }

  return {
    score: 1.0,
  };
}

// 检测帖子中是否有图像或视频的函数
function imageVideoBoost({ postMedia }: PostData): Rank {
  const has_media = postMedia;
  if (has_media) {
    return {
      score: 2.0,
      // message: "包含图像或视频。",
    };
  }
  return {
    score: 1.0,
  };
}

// 检测帖子中的网址的函数
function postHasUrl({ post }: PostData): Rank {
  const regex =
    /https?:\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/g;
  const urls = post.match(regex);
  if (urls && urls.length > 0) {
    return {
      score: 0.5,
      message: "从帖子中删除链接并在评论中添加链接。",
    };
  }
  return {
    score: 1.0,
  };
}

/**
 * 偏好使用表情符号的帖子的函数
 */
function emojis({ post, sentiment }: PostData): Rank {
  const regex = new RegExp("[\uD800-\uDBFF][\uDC00-\uDFFF]", "g");
  const emojis = post.match(regex) || [];
  const totalMatches = emojis.length;
  if (totalMatches > 0) {
    return {
      score: 1.5,
      // message: `在帖子中包含了${totalMatches}个表情符号。`,
    };
  }
  return {
    score: 1,
    message: "帖子中没有找到表情符号。",
    type: "negative"
  };
}

/**
 * 促进消极内容,因为它更有可能传播。
 * 隐藏任何积极或令人振奋的内容。
 */
function sentiment({ post, sentiment }: PostData): Rank {
  if (sentiment.comparative >= 0.5) {
    if (sentiment.comparative > 1.5) {
      return {
        score: 1.5,
        // message: "极其积极。",
      };
    } else {
      return {
        score: 1.1,
        // message: "积极情感。",
      };
    }
  } else if (sentiment.comparative <= -0.5) {
    if (sentiment.comparative < -1.5) {
      return {
        score: 0.5,
        // message: "极其消极。",
      };
    } else {
      return {
        score: 0.9,
        // message: "消极情感。",
      };
    }
  } else {
    return {
      score: 1,
    };
  }
}

/**
 * 优先考虑帖子格式的断行。
 */
function lineBreaks({ post, sentiment }: PostData): Rank {
  const breaks = post.split(/\n\s*\n/);
  const totalBreaks = breaks.length - 1;
  if (totalBreaks >= 1) {

    return {
      score: 1.5,
      // message: `使用了${totalBreaks}个断行。`,
    };
  } else {
    return {
      score: 1,
      message: "在行之间添加断行。",
      type: "negative"
    };
  }
}

/**
 * 减少行长度
 */
function lineLength({ post }: PostData): Rank {
  const lines = post.split('\n');
  let score = 1.0;
  for (let i = 0; i < lines.length; i++) {
    if (lines[i].length > 200) {
      return {
        score: 0.9,
        message: "缩短行长度以提高可读性(200个字符)。",
      };
    }
  }
  return {
    score: 1,
    // message: "很好,保持行长度在200个字符或以下。",
    type: "positive"
  };
}
/**
* 提问的函数
*/
function questions({ post, sentiment }: PostData): Rank {
  if (post.includes("?")) {
    return {
      score: 1.5,
      // message: "很棒!问题可以帮助激活讨论"
    };
  } else {
    return {
      score: 1,
      message: "添加问题以激活讨论",
      type: "negative"
    };
  }
}

优化代码:

// 检测多个标签的函数
function multipleHashtags({ post }: PostData): Rank {
  const regex = /#[\w-]+/g;
  const hashtags = post.match(regex) || [];
  const lowerCasePost = post.toLowerCase();

  if (hashtags.length > 3) {
    return { score: 0.5, message: "使用了太多的标签。" };
  }

  const prohibitedTags = ["#follow", "#comment", "#like"];
  const containsProhibitedTags = prohibitedTags.some(tag => lowerCasePost.includes(tag));

  if (containsProhibitedTags) {
    return { score: 0.5, message: "避免使用类似“follow”、“comment”或“like”的标签。" };
  }

  return { score: 1, message: "结合一般性和特定性的标签。" };
}

// 检测帖子中是否有图像或视频的函数
function imageVideoBoost({ postMedia }: PostData): Rank {
  return postMedia ? { score: 2.0 } : { score: 1.0 };
}

// 检测帖子中的网址的函数
function postHasUrl({ post }: PostData): Rank {
  const regex = /https?:\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/g;
  const urls = post.match(regex);
  return urls ? { score: 0.5, message: "从帖子中删除链接并在评论中添加链接。" } : { score: 1.0 };
}

// 偏好使用表情符号的帖子的函数
function emojis({ post }: PostData): Rank {
  const regex = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
  const emojis = post.match(regex) || [];
  const totalMatches = emojis.length;

  return totalMatches > 0
    ? { score: 1.5 }
    : { score: 1, message: "帖子中没有找到表情符号。", type: "negative" };
}

// 促进消极内容,因为它更有可能传播。隐藏任何积极或令人振奋的内容。
function sentiment({ sentiment }: PostData): Rank {
  const { comparative } = sentiment;

  if (comparative >= 0.5) {
    return comparative > 1.5 ? { score: 1.5 } : { score: 1.1 };
  } else if (comparative <= -0.5) {
    return comparative < -1.5 ? { score: 0.5 } : { score: 0.9 };
  }

  return { score: 1.0 };
}

// 优先考虑帖子格式的断行。
function lineBreaks({ post }: PostData): Rank {
  const totalBreaks = (post.split(/\n\s*\n/).length - 1);
  
  return totalBreaks >= 1
    ? { score: 1.5 }
    : { score: 1, message: "在行之间添加断行。", type: "negative" };
}

// 减少行长度
function lineLength({ post }: PostData): Rank {
  const lines = post.split('\n');

  for (const line of lines) {
    if (line.length > 200) {
      return { score: 0.9, message: "缩短行长度以提高可读性(200个字符)。" };
    }
  }

  return { score: 1, type: "positive" };
}

// 提问的函数
function questions({ post }: PostData): Rank {
  return post.includes('?')
    ? { score: 1.5 }
    : { score: 1, message: "添加问题以激活讨论", type: "negative" };
}

算法的 UI 界面

检测上述代码中的所有函数,并对其中一些函数展示改进建议。

原代码(未优化):

return (
  <>
    <div>
      <div className="slider bg-gray-300 h-4 rounded-full relative overflow-hidden">
        <div
          className={classNames(
            "absolute top-0 transition-width duration-250 ease-linear h-20",
            sliderColor
          )}
          style={{ width: percentage }}
        />
      </div>
      {/* <p className="explanation text-gray-600 italic text-sm mt-2">
        Positive rankings result in greater reach
      </p> */}

      <ul className="mt-5 p-0">
        {positive.map((item, index) => (
          <li
            className="positive text-green-600 flex items-center space-x-2 list-style-none my-5 text-sm"
            key={`positive-${index}`}
          >
            <span>👍</span>
            <span>{item.message.replace(/\(\s*[+-]?\d+\s*\)/, '')}</span>
          </li>
        ))}
        {negative.map((item, index) => (


          <li
            className="negative text-red-600 flex items-center space-x-2 list-style-none my-1 text-sm"
            key={`negative-${index}`}
          >
            <span>👎</span>
            <span>{item.message.replace(/\(\s*[+-]?\d+\s*\)/, '')}</span>
          </li>
        ))}
      </ul>
    </div>
    <style jsx>{`
      .slider:after {
        content: " ";
        display: block;
        width: 2px;
        height: 20px;
        position: absolute;
        top: 0;
        left: calc(25% - 1px);
        background: #000;
      }
    `}</style>
  </>
);
};

优化代码:

import classNames from 'classnames'; // Assuming classNames is being used

const RankingUI = ({ sliderColor, percentage, positive, negative }) => {
  return (
    <div>
      <div className="slider bg-gray-300 h-4 rounded-full relative overflow-hidden">
        <div
          className={classNames(
            "absolute top-0 transition-width duration-250 ease-linear h-full",
            sliderColor
          )}
          style={{ width: `${percentage}%` }}
        />
      </div>

      <ul className="mt-5 p-0">
        {positive.map((item, index) => (
          <li
            className="positive text-green-600 flex items-center space-x-2 list-style-none my-5 text-sm"
            key={`positive-${index}`}
          >
            <span>👍</span>
            <span>{item.message.replace(/\(\s*[+-]?\d+\s*\)/, '')}</span>
          </li>
        ))}
        {negative.map((item, index) => (
          <li
            className="negative text-red-600 flex items-center space-x-2 list-style-none my-1 text-sm"
            key={`negative-${index}`}
          >
            <span>👎</span>
            <span>{item.message.replace(/\(\s*[+-]?\d+\s*\)/, '')}</span>
          </li>
        ))}
      </ul>
      <style jsx>{`
        .slider:after {
          content: " ";
          display: block;
          width: 2px;
          height: 20px;
          position: absolute;
          top: 0;
          left: calc(25% - 1px);
          background: #000;
        }
      `}</style>
    </div>
  );
};

export default RankingUI;

打开AI API和提示生成器

连接open ai api使用 Prompt +类型过滤器来生成这篇帖子

const handlePrompt = () => {
    let prompt;
    switch (vibe) {

会出现以下 Prompt提示内容:

prompt = `Generate post using this prompt, based on ${post}.  You are a LinkedinGPT, a large language model that generates viral posts for Linkedin. You are given a prompt of a post and must generate a post that is more likely to be liked and reposted than the original post.
The Linkedin algorithm contains boosts and demotions based on what you are writing. Positive boosts are:

- in each post add emoji
- 200 characters in sentence maximum
- Start each sentecnce from new line and ad numbers in first 2 lines
- add 3 hashtags which 2 are generic and one very specific (at the end) Tags relate to post theme
- add a question at the end of the post to start a discussion. Before the hashtags
- first two lines should be catchy
- Dont add links - links are not good.
- If post copied in the field contain some numbers keep them the same.

Add idea about which image or visual can be added at the end of the post (this text is not counted as part of post)
${post}
---
Generated post length must be more than 800-1200 characters
---
Between each line must be a space
---
Keep all mentions of people in there
---
Start the firs line from smth like: I did smth, In year, I do, Tired of, Sometimes it is just, A path toward, Because this is not,I've been struggling,  (change the begginign depends on the context )
---
Add emoji if it fits
---
It should be a story`;

生成器界面

<main>
  <nav className="bg-blue-900 text-white ">
    <div className="px-5">
      <div className="max-w-5xl mx-auto">
        <div className="flex justify-between items-center h-16 ">
          <div className="flex items-center text-base ">
            <a
              target="_blank"
              href="https://www.linkedin.com/in/iuliia-shnai/"
              rel="noreferrer"
              className="text-white flex max-w-fit items-center justify-center space-x-2 text-xl"
            >
              <p>👩‍💼</p>
            </a>
          </div>
        </div>
      </div>
    </div>
  </nav>
  <section className="py-10 lg:py-20 ">
    {/* bg-[url('/image1.svg')] */}
    <div className="px-4">
      <div className="max-w-5xl mx-auto">
        <div className="w-full mx-auto">
          <h1 className="text-6xl text-center font-bold pb-1 text-slate-900">
            Linkedin Post Generator 🚀
          </h1>
          <p className="mt-3 mb-10 text-center">
            See how your post performs and generate a better one with AI. Time
            to go viral. <br />
          </p>
          <div className="flex flex-col md:flex-row w-full md:space-x-20">
            <div className="flex md:w-1/2 flex-col">
              <h2
                className="

text-xl font-bold"
              >
                Your Ranking
              </h2>
              <div className="pt-1">
                <Ranking ranking={ranking} />
              </div>
              <div className="w-full my-1 mx-auto">
                <Post
                  post={post}
                  setPost={setPost}
                  media={media}
                  setMedia={setMedia}
                />
              </div>
              <div className="flex mb-5 items-center space-x-3"></div>
              <div className="block">
                <DropDown vibe={vibe} setVibe={setVibe} />
              </div>
              <div className="my-4">
                <button
                  disabled={loading}
                  onClick={(e) => optimizePost(e)}
                  className="bg-blue-800 font-medium rounded-md w-full text-white px-4 py-2 hover:bg-blue-600 disabled:bg-blue-800"
                >
                  {loading && <LoadingDots color="white" style="large" />}
                  {!loading && `Generate new post `}
                </button>
              </div>
            </div>
            <div className="flex md:w-1/2 md:flex-col">
              <Toaster
                position="top-right"
                reverseOrder={false}
                toastOptions={{ duration: 2000 }}
              />
              {optimizedPost && (
                <div className="my-1">
                  <div className="flex justify-between items-center pb-2 border-b border-gray-300">
                    <h2 className="text-xl font-bold">Your Generated Post</h2>
                  </div>
                  <div className="max-w-2xl my-4 mx-auto">
                    <div
                      className="bg-white rounded-xl shadow-md p-4 hover:bg-gray-100 transition cursor-copy border"
                      onClick={() => {
                        navigator.clipboard.write([
                          new ClipboardItem({
                            "text/html": new Blob([optimizedPost], {
                              type: "text/html",
                            }),
                          }),
                        ]);
                        toast("Post copied to clipboard", {
                          icon: "📋",
                        });
                      }}
                      key={optimizedPost}
                    >
                      <p
                        className="text-black-700"
                        dangerouslySetInnerHTML={{ __html: optimizedPost }}
                      />
                    </div>
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  </section>
  <div className="max-w-5xl mx-auto">
    <Footer />
  </div>
</main>

© 版权声明
THE END
支持我嘛~
点赞18 分享
碎语词话 共2条

请登录后发表评论