Skip to content

Commit

Permalink
💩 Optimize build
Browse files Browse the repository at this point in the history
This reverts commit 50c2635.
  • Loading branch information
MuelNova committed Sep 28, 2024
1 parent a753851 commit 3b13062
Show file tree
Hide file tree
Showing 3 changed files with 330 additions and 2 deletions.
160 changes: 160 additions & 0 deletions src/plugins/ai-summary/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// 获取 docusaurus 配置
const path = require("path");
const fs = require("fs");
import {
LoadContext,
Plugin,
PluginOptions,
PluginContentLoadedActions,
} from "@docusaurus/types";
import openai from "openai";

interface BlogPluginOptions extends PluginOptions {
id?: string;
path?: string;
routeBasePath?: string;
[key: string]: any;
}

interface PluginConfig extends PluginOptions {
OPENAI_API_KEY?: string;
OPENAI_BASE_URL?: string;
OPENAI_SUMMARY_MODEL?: string;
OPENAI_SUMMARY_SYSTEM_PROMPT?: string;
}

export default async function AISummary(
context: LoadContext,
options: PluginConfig
): Promise<Plugin> {
// ...
return {
name: "ai-summary",
async loadContent() {
const { siteConfig } = context;
const blogPlugins = siteConfig.plugins.filter(
(plugin) =>
Array.isArray(plugin) &&
plugin[0] === "@docusaurus/plugin-content-blog"
);

const summaries: { [key: string]: { [key: string]: string } } = {};
let skipFlag = false;

if (!options || !options.OPENAI_API_KEY) {
console.warn(
"OPENAI_API key is not set in siteConfig.customFields, skipping AI summary generation"
);
skipFlag = true;
}

const openaiClient = skipFlag
? new openai({ apiKey: "dummy_api" })
: new openai({
apiKey: options.OPENAI_API_KEY,
baseURL: options.OPENAI_BASE_URL,
});
const model = (options.OPENAI_SUMMARY_MODEL as string) || "gpt-4o";
const systemPrompt =
(options.OPENAI_SUMMARY_SYSTEM_PROMPT as string) ||
`你是一位专业的内容总结助手,你的任务是根据用户提供的文本生成简洁的总结。
请确保总结清晰、简明,抓住文章的主要内容和作者的主要经历。
注意,你需要使用和文章主要语言相同的语种,更推荐你不使用 markdown。
在数个自然段内完成总结,注意 nova 即为博客作者,且为男性。`;
const generateSummary = async (content: string) => {
const response = await openaiClient.chat.completions.create({
model: model,
messages: [
{
role: "system",
content: systemPrompt,
},
{
role: "user",
content,
},
],
});
return response.choices[0]?.message?.content;
};

for (const blogPlugin of blogPlugins) {
if (!blogPlugin) {
console.warn("No blog plugin found, skipping AI summary generation");
skipFlag = true;
}
const pluginOptions = blogPlugin[1] as BlogPluginOptions;
const blogPath = pluginOptions.path;
const blogRoute = pluginOptions.routeBasePath || "blog";

const blogDir = path.join(context.siteDir, blogPath);
const files = fs.readdirSync(blogDir);

for (const file of files) {
// 判断是否是 md 或者 mdx 文件
if (
(!file.endsWith(".md") && !file.endsWith(".mdx")) ||
file.startsWith("__")
) {
continue;
}
const filePath = path.join(blogDir, file);
const fileName = file.replace(/\.(md|mdx)$/, "").replace(" ", "-");
// 判断是不是文件夹
if (fs.statSync(filePath).isDirectory()) {
continue;
}
let content = fs.readFileSync(filePath, "utf-8");
// 使用正则表达式全局替换代码块和引用块
const codeBlockRegex = /```[^]+?```/g;
content = content.replace(codeBlockRegex, "");

// 替换多行引用块
const quoteRegex = />>[^]+/g;
content = content.replace(quoteRegex, "");

// 检查文件是否需要更新摘要(可以根据文件的时间戳或其他标志)
const summaryFilePath = filePath.replace(/\.(md|mdx)$/, ".summary");
const summaryExists = fs.existsSync(summaryFilePath);
if (summaryExists) {
const summaryStats = fs.statSync(summaryFilePath);
const blogStats = fs.statSync(filePath);
if (summaryStats.mtime >= blogStats.mtime) {
summaries[blogRoute] = summaries[blogRoute] || {};
summaries[blogRoute][fileName] = fs.readFileSync(
summaryFilePath,
"utf-8"
);
continue;
}
}

// 生成摘要
if (skipFlag) continue;
console.log("Generating summary for", file);
const response = await generateSummary(content);
if (!response) {
console.warn(`Failed to generate summary for ${file}`);
continue;
}
summaries[blogRoute] = summaries[blogRoute] || {};
summaries[blogRoute][file] = response;
fs.writeFileSync(summaryFilePath, response);
}
}
return { aisummary: summaries };
},

async contentLoaded({
content,
actions,
}: {
content: any;
actions: PluginContentLoadedActions;
}) {
const { createData } = actions;
createData("aisummary.json", JSON.stringify(content.aisummary, null, 2));
},
/* other lifecycle API */
};
}
157 changes: 157 additions & 0 deletions src/plugins/ai-translate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { Plugin, LoadContext, PluginOptions } from "@docusaurus/types";
import openai from "openai";

import path from "path";
import fs from "fs";

interface BlogPluginOptions extends PluginOptions {
id?: string;
path: string;
routeBasePath?: string;
[key: string]: any;
}

interface PluginConfig extends PluginOptions {
OPENAI_API_KEY?: string;
OPENAI_BASE_URL?: string;
OPENAI_TRANSLATE_MODEL?: string;
OPENAI_TRANSLATE_SYSTEM_PROMPT?: string;
OPENAI_TOKEN_SIZE?: string;
}

export default async function AITranslate(
context: LoadContext,
options: PluginConfig
) {
return {
name: "ai-translate",
async loadContent() {
// 获取 docusaurus 配置
const { siteConfig } = context;
const blogPlugins = siteConfig.plugins.filter(
(plugin) =>
Array.isArray(plugin) &&
plugin[0] === "@docusaurus/plugin-content-blog"
);

if (!options || !options.OPENAI_API_KEY) {
console.warn(
"OPENAI_API key is not set in Plugin Options, skipping AI Translate generation"
);
return;
}

const openaiClient = new openai({
apiKey: options.OPENAI_API_KEY,
baseURL: options.OPENAI_BASE_URL,
});
const model = (options.OPENAI_TRANSLATE_MODEL as string) || "gpt-4o";
const systemPrompt =
(options.OPENAI_TRANSLATE_SYSTEM_PROMPT as string) ||
`你是一位专业的内容翻译助手,任务是将用户文本翻译为英文,同时翻译代码块注释。
注意你不应该修改任何文章结构,不要对标题的括号进行修改。
你的回答应该只包含翻译部分,不包含任何说明。`;
const generateTranslate = async (content: string) => {
const response = await openaiClient.chat.completions.create({
model: model,
messages: [
{
role: "system",
content: systemPrompt,
},
{
role: "user",
content,
},
],
});
console.debug(response);
return response.choices[0]?.message?.content;
};

const translatedPath = siteConfig.i18n.path;
if (!translatedPath) {
console.warn(
"i18n.path is not set in siteConfig, skipping AI Translate generation"
);
return;
}

for (const blogPlugin of blogPlugins) {
if (!blogPlugin) {
console.warn(
"No blog plugin found, skipping AI Translate generation"
);
return;
}
const pluginOptions = blogPlugin[1] as BlogPluginOptions;
const blogPath = pluginOptions.path;
const blogDir = path.join(context.siteDir, blogPath);
const translatedBlogPath = path.join(
context.siteDir,
translatedPath,
"en",
"docusaurus-plugin-content-blog" +
(pluginOptions.id == "default" ? "" : "-" + pluginOptions.id)
);
const files = fs.readdirSync(blogDir);

for (const file of files) {
// 判断是否是 md 或者 mdx 文件
if (
(!file.endsWith(".md") && !file.endsWith(".mdx")) ||
file.startsWith("__")
) {
continue;
}
const filePath = path.join(blogDir, file);
// 判断是不是文件夹
if (fs.statSync(filePath).isDirectory()) {
continue;
}

const translatedFilePath = path.join(translatedBlogPath, file);

if (fs.existsSync(translatedFilePath)) {
const translateStats = fs.statSync(translatedFilePath);
const blogStats = fs.statSync(filePath);
if (translateStats.mtime >= blogStats.mtime) {
continue;
}

const content = fs.readFileSync(translatedFilePath, "utf-8");
if (!content.includes("<!-- AI -->")) {
console.info(
`Skipping ${translatedFilePath} as it has been translated by yourself at ${translateStats.mtime}`
);
continue;
}
}

const content = fs.readFileSync(filePath, "utf-8");
console.info("Translating", file);
let translatedContent = "";
// split content, translate each part, and join them back
let currentIdx = 0;
// only 8k for deepseek
console.log(options);

const TRANS_SIZE = Number(options.OPENAI_TOKEN_SIZE) || 8192;
while (currentIdx < content.length) {
const chunk = content.slice(currentIdx, currentIdx + TRANS_SIZE);
console.log("Translating chunk", currentIdx, chunk.length);
const translatedChunk = await generateTranslate(chunk);
translatedContent += translatedChunk;
currentIdx += TRANS_SIZE;
}

translatedContent +=
"\n\n:::info\nThis Content is generated by ChatGPT and might be wrong / incomplete, refer to Chinese version if you find something wrong.\n:::\n\n<!-- AI -->\n";

// write translated content to file
fs.writeFileSync(translatedFilePath, translatedContent);
}
}
},
};
}
15 changes: 13 additions & 2 deletions src/theme/BlogPostPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ function BlogSummary({
}): JSX.Element {
try {
const Data = require("@site/.docusaurus/ai-summary/default/aisummary.json");
if (!content.metadata.editUrl) {
console.warn(
"No editUrl found in metadata, skipping AI summary generation"
);
return <></>;
}
const link = content.metadata.editUrl.split("/");
const blog = link[link.length - 2],
post = link[link.length - 1].replace(/\.(md|mdx)$/, "");
Expand All @@ -45,8 +51,13 @@ function BlogSummary({
</Admonition>
);
} catch (e) {
console.warn(e);
console.warn("No ai-summary plugin found, skipping AI summary generation");
if (e.code === "MODULE_NOT_FOUND") {
console.debug(
"No ai-summary plugin found, skipping AI summary generation"
);
} else {
console.warn(e);
}
return <></>;
}
}
Expand Down

0 comments on commit 3b13062

Please sign in to comment.