hts/apps/blogai/lib/mdx-helper.ts

453 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { promises as fs } from "fs";
import path from "path";
import GithubSlugger from "github-slugger";
import { type MDXRemoteSerializeResult } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import { notFound } from "next/navigation";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeCodeTitles from "rehype-code-titles";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeSlug from "rehype-slug";
import remarkGfm from "remark-gfm";
import { BUNDLED_LANGUAGES, type HighlighterOptions, getHighlighter } from "shiki";
import gitHubLight from "shiki/themes/github-light.json";
import service from "./http/service";
import { ArticleData, QAData } from "@/components/article/article";
export const BLOG_PATH = path.join(process.cwd(), "content", "blog");
export const CHANGELOG_PATH = path.join(process.cwd(), "content", "changelog");
export const POLICY_PATH = path.join(process.cwd(), "content", "policies");
export const JOBS_PATH = path.join(process.cwd(), "content", "jobs");
export const changelogFilePaths = fs.readdir(CHANGELOG_PATH);
// console.log(BLOG_PATH)
export const getFilePaths = async (contentPath: string) => {
return await fs.readdir(contentPath);
};
export const raw = async ({
contentPath,
filepath,
}: {
contentPath: string;
filepath: string;
}) => {
console.log(contentPath, filepath)
try {
const fileContent = await fs.readFile(`${contentPath}/${filepath}.mdx`, "utf-8");
return fileContent;
} catch {
return notFound();
}
};
type Headings = {
slug: string | undefined;
level: string;
text: string | undefined;
};
type Post<TFrontmatter> = {
serialized: MDXRemoteSerializeResult;
frontmatter: TFrontmatter;
headings: Headings[];
};
type AIPost<TFrontmatter> = {
serialized: MDXRemoteSerializeResult;
frontmatter: TFrontmatter;
headings: Headings;
};
type Changelog<TFrontmatter> = {
serialized: MDXRemoteSerializeResult;
frontmatter: TFrontmatter;
};
type ChangeLogFrontmatter = {
title: string;
date: string;
description: string;
};
type Policy<TFrontmatter> = {
serialized: MDXRemoteSerializeResult;
frontmatter: TFrontmatter;
};
type PolicyFrontmatter = {
title: string;
};
type Job<TFrontmatter> = {
serialized: MDXRemoteSerializeResult;
frontmatter: TFrontmatter;
};
type JobFrontmatter = {
title: string;
description: string;
visible: boolean;
salary: string;
};
type Frontmatter = {
title: string;
date: string;
description: string;
author: string;
visible: boolean | undefined;
salary: string | undefined;
level: string | undefined;
};
type AIFrontmatter = {
id:string;
p_name:string;
title: string;
date: string;
description: string;
author: string;
image_url: string;
logo_url: string;
model_parameter: number;
visible: boolean | undefined;
salary: string | undefined;
level: string | undefined;
tags?: string[];
};
const options = {
theme: gitHubLight,
getHighlighter: (options: HighlighterOptions) =>
getHighlighter({
...options,
langs: [...BUNDLED_LANGUAGES],
}),
defaultLang: {
block: "typescript",
},
};
// Serialize the MDX content and parse the frontmatter
export const mdxSerialized = async ({ rawMdx }: { rawMdx: string }) => {
return await serialize(rawMdx, {
parseFrontmatter: true,
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
[rehypePrettyCode, options],
rehypeAutolinkHeadings,
rehypeSlug,
rehypeCodeTitles,
],
},
});
};
const getHeadings = async ({ rawMdx }: { rawMdx: string }) => {
const slugger = new GithubSlugger();
const regXHeader = /\n(?<flag>#{1,6})\s+(?<content>.+)/g;
const headings = Array.from(rawMdx.matchAll(regXHeader)).map(({ groups }) => {
const flag = groups?.flag;
const content = groups?.content;
return {
level: flag?.length === 1 ? "one" : flag?.length === 2 ? "two" : "three",
text: content,
slug: content ? slugger.slug(content) : undefined,
};
});
return headings;
};
const getMoreContent = async ({
contentPath,
filepath,
}: {
contentPath: string;
filepath: string;
}) => {
const moreContent = await fs.readdir(contentPath);
const moreContentFiltered = moreContent
.filter((path) => /\.mdx?$/.test(path))
.filter((post) => post !== filepath)
.slice(0, 2);
console.log("-------", moreContentFiltered)
return moreContentFiltered;
};
export const getAllMDXData = async ({
contentPath,
}: {
contentPath: string;
}) => {
const allPosts = await fs.readdir(contentPath);
const allPostsFiltered = allPosts.filter((path) => /\.mdx?$/.test(path));
const allPostsData = await Promise.all(
allPostsFiltered.map(async (post) => {
const rawMdx = await raw({
contentPath,
filepath: post.replace(/\.mdx?$/, ""),
});
const serializedMdx = await mdxSerialized({ rawMdx });
const frontmatter = serializedMdx.frontmatter as Frontmatter;
return {
frontmatter,
slug: post.replace(/\.mdx$/, ""),
};
}),
);
return allPostsData;
};
export const getContentData = async ({
contentPath,
filepath,
}: {
contentPath: string;
filepath: string;
}) => {
const moreContent = await getMoreContent({ contentPath, filepath });
const moreContentData = await Promise.all(
moreContent.map(async (content) => {
const rawMdx = await raw({
contentPath,
filepath: content.replace(/\.mdx?$/, ""),
});
const serializedMdx = await mdxSerialized({ rawMdx });
const frontmatter = serializedMdx.frontmatter as Frontmatter;
return {
frontmatter,
slug: content.replace(/\.mdx$/, ""),
};
}),
);
return moreContentData;
};
export const getPost = async (filepath: string): Promise<Post<Frontmatter>> => {
const rawMdx = await raw({ contentPath: BLOG_PATH, filepath: filepath });
console.log("rawMdx", rawMdx)
// Serialize the MDX content and parse the frontmatter
const serialized = await mdxSerialized({ rawMdx });
const frontmatter = serialized.frontmatter as Frontmatter;
const headings = await getHeadings({ rawMdx });
return {
frontmatter,
serialized,
headings,
};
};
export const getPostContent = async (language: string, slug: string): Promise<AIPost<AIFrontmatter>> => {
// const rawMdx = await raw({ contentPath: BLOG_PATH, filepath: filepath });
const PageSize = 1
let data: ArticleData = await service.post('/api/v1/news/list', {
"id": Number(slug),
// "tag": "", // #Blockchain
language,
"page_no": 0,
"page_size": PageSize
}, {
headers: {
// 'Authorization': token
}
}).then((result: any) => {
console.log("result:", result)
if (result && result.header.code != 0) {
return
}
return result.data.list[0]
}).catch((err) => {
console.log(err);
});
let rawMdx = data.content
// Serialize the MDX content and parse the frontmatter
const serialized = await mdxSerialized({ rawMdx });
const frontmatter = serialized.frontmatter as AIFrontmatter;
frontmatter.id = String(data.id)
frontmatter.p_name = data.p_name
frontmatter.title = data.main_title
frontmatter.description = data.sub_title || data.main_title
frontmatter.date = data.updated_time
frontmatter.tags = data.tags
frontmatter.image_url = data.image_url
frontmatter.logo_url = data.logo_url
frontmatter.model_parameter = data.model_parameter
// const headings = data.main_title;
// 描述来自sub_title字段如果没有则跟main_title一致
// 关键词来自seo_keywords字段如果seo_keywords为空而取keywords字段如果都为空则跟首页的关键字一致
// 特殊处理
let text = ""
if (data.seo_keywords.length > 0 && typeof data.seo_keywords == "string") {
try {
const parsedArray = JSON.parse(data.seo_keywords); // 尝试解析字符串为数组
console.log("---parsedArray---", parsedArray)
text = Array.isArray(parsedArray) ? parsedArray.join(', ') : ""
} catch (error) {
console.error('解析失败seo_keywords', error); // 输出解析失败的错误信息
}
} else if (Array.isArray(data.seo_keywords)) {
text = data.seo_keywords.join(', ')
}
if (text.length == 0 && typeof data.keywords == "string") {
try {
const parsedArray = JSON.parse(data.keywords);
text = Array.isArray(parsedArray) ? parsedArray.join(', ') : ""
} catch (error) {
console.error('解析失败seo_keywords', data.seo_keywords, error);
}
} else if (Array.isArray(data.keywords)) {
text = data.keywords.join(', ')
}
console.log('----------rawMdx:', data)
return {
frontmatter,
serialized,
headings: {
slug: data.main_title,
level: "",
text: text,
},
};
};
export const getQAContent = async (language: string, slug: string): Promise<AIPost<AIFrontmatter>> => {
// const rawMdx = await raw({ contentPath: BLOG_PATH, filepath: filepath });
const PageSize = 1
let data: QAData = await service.post('/api/v1/qa/list', {
"id": Number(slug),
// "tag": "", // #Blockchain
language,
"page_no": 0,
"page_size": PageSize
}, {
headers: {
// 'Authorization': token
}
}).then((result: any) => {
console.log("result:", result)
if (result && result.header.code != 0) {
return
}
return result.data.list[0]
}).catch((err) => {
console.log(err);
});
let rawMdx = data.answer
// Serialize the MDX content and parse the frontmatter
const serialized = await mdxSerialized({ rawMdx });
const frontmatter = serialized.frontmatter as AIFrontmatter;
frontmatter.title = data.question
frontmatter.description = data.question
frontmatter.date = data.updated_time
// frontmatter.tags = data.tags
// const headings = data.main_title;
console.log('----------rawMdx:', data)
return {
frontmatter,
serialized,
headings: {
slug: data.question,
level: "",
text: "",
},
};
};
export const getChangelog = async (filepath: string): Promise<Changelog<ChangeLogFrontmatter>> => {
const rawMdx = await raw({ contentPath: CHANGELOG_PATH, filepath: filepath });
// Serialize the MDX content and parse the frontmatter
const serialized = await mdxSerialized({ rawMdx });
const frontmatter = serialized.frontmatter as ChangeLogFrontmatter;
return {
frontmatter,
serialized,
};
};
export const getPolicy = async (filepath: string): Promise<Policy<PolicyFrontmatter>> => {
const rawMdx = await raw({ contentPath: POLICY_PATH, filepath: filepath });
// Serialize the MDX content and parse the frontmatter
const serialized = await mdxSerialized({ rawMdx });
const frontmatter = serialized.frontmatter as PolicyFrontmatter;
return {
frontmatter,
serialized,
};
};
export const getJob = async (filepath: string): Promise<Job<JobFrontmatter>> => {
const rawMdx = await raw({ contentPath: JOBS_PATH, filepath: filepath });
// Serialize the MDX content and parse the frontmatter
const serialized = await mdxSerialized({ rawMdx });
const frontmatter = serialized.frontmatter as JobFrontmatter;
return {
frontmatter,
serialized,
};
};
export const getAllJobsData = async ({
contentPath,
}: {
contentPath: string;
}) => {
const allJobs = await fs.readdir(contentPath);
const allJosFiltered = allJobs.filter((path) => /\.mdx?$/.test(path));
const allPostsData = await Promise.all(
allJosFiltered.map(async (post) => {
const rawMdx = await raw({
contentPath,
filepath: post.replace(/\.mdx?$/, ""),
});
const serializedMdx = await mdxSerialized({ rawMdx });
const frontmatter = serializedMdx.frontmatter as JobFrontmatter;
if (frontmatter.visible === false) {
return;
}
return {
frontmatter,
slug: post.replace(/\.mdx$/, ""),
};
}),
);
return allPostsData;
};