"use client"; import { memo } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import type { Components } from "react-markdown"; import { BookOpen, BrainCircuit, ArrowRight } from "lucide-react"; import { useRouter } from "next/navigation"; import type { Chunk } from "@/lib/types"; const mdComponents: Components = { h1: ({ children }) => (
{children}
, ul: ({ children }) =>{children}), table: ({ children }) => (
{children}
{children}
);
},
pre: ({ children }) => <>{children}>,
};
/**
* 推荐应用跳转徽章组件:点击后跳转到同机构内的其他应用
*/
function AppLinkBadge({ slug, name }: { slug: string; name: string }) {
const router = useRouter();
return (
);
}
interface GovMarkdownProps {
content: string;
className?: string;
chunks?: Chunk[];
}
function stripOuterCodeFence(text: string): string {
const trimmed = text.trim();
const match = trimmed.match(/^```[\w]*\s*\n([\s\S]*?)```\s*$/);
if (match) return match[1].trim();
return text;
}
/**
* 预处理 markdown 内容:将来源标注转为特殊链接格式,由 ReactMarkdown 的 a 组件拦截渲染
*
* 支持的标注格式:
* - [[chunk:N]] → 知识库引用(使用 chunks 映射表解析为文档名)
* - [[知识库:文档名]] → 知识库引用徽章
* - [[AI建议]] → AI建议徽章
* - [[推荐应用:名称:slug]] → 可点击跳转链接
*/
function preprocessCitations(content: string, chunks?: Chunk[]): string {
let result = content;
console.log("[preprocessCitations] chunks:", chunks);
// 先处理 chunk 编号引用 [[chunk:N]]
result = result.replace(/\[\[chunk:(\d+)\]\]/g, (_, idx) => {
const i = parseInt(idx, 10);
if (chunks && chunks[i]) {
// 使用 chunks 映射表,转换为带文档名的知识库引用
const docName = chunks[i].doc_name || chunks[i].content?.slice(0, 20) || "知识库片段";
console.log(`[preprocessCitations] chunk[${i}]:`, docName);
return `[知识库:${docName}](#cite-kb)`;
}
// 无映射时显示后备文本
console.log(`[preprocessCitations] chunk[${i}] not found, chunks length:`, chunks?.length);
return `[知识库片段](#cite-kb)`;
});
// 知识库引用:[[知识库:文献名称]] 或 [[知识库:文献名称:条款]]
result = result.replace(/\[\[(知识库:[^\]]+)\]\]/g, (_, label) => `[${label}](#cite-kb)`);
// AI建议:标准格式 [[AI建议]]
result = result.replace(/\[\[AI建议\]\]/g, "[AI建议](#cite-ai)");
// AI建议:来源说明块中的 **AI建议:** 或 **AI建议** 标题(仅匹配行首或 > 后)
result = result.replace(/^(\s*\>?\s*)\*\*AI建议[::]\*\*/gm, "$1[AI建议](#cite-ai)");
// AI建议:无加粗的 AI建议: 标题行(仅匹配行首或 > 后)
result = result.replace(/^(\s*\>?\s*)AI建议[::]\s*$/gm, "$1[AI建议](#cite-ai)");
// 推荐应用:[[推荐应用:应用名称:slug]] → 可点击跳转链接
result = result.replace(
/\[\[推荐应用:([^:]+):([^\]]+)\]\]/g,
(_, name, slug) => `[${name}](#cite-app:${slug})`,
);
return result;
}
const GovMarkdown = memo(function GovMarkdown({ content, className, chunks }: GovMarkdownProps) {
if (!content) {
return (