feat: 增强聊天功能和 markdown 渲染
- 优化聊天 UI 组件交互体验 - 扩展 markdown 渲染功能支持 - 更新类型定义 - 重构 LLM 聊天处理逻辑 - 更新模型提供商种子数据
This commit is contained in:
@@ -6,6 +6,7 @@ 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 }) => (
|
||||
@@ -153,6 +154,7 @@ function AppLinkBadge({ slug, name }: { slug: string; name: string }) {
|
||||
interface GovMarkdownProps {
|
||||
content: string;
|
||||
className?: string;
|
||||
chunks?: Chunk[];
|
||||
}
|
||||
|
||||
function stripOuterCodeFence(text: string): string {
|
||||
@@ -164,22 +166,50 @@ function stripOuterCodeFence(text: string): string {
|
||||
|
||||
/**
|
||||
* 预处理 markdown 内容:将来源标注转为特殊链接格式,由 ReactMarkdown 的 a 组件拦截渲染
|
||||
*
|
||||
* 支持的标注格式:
|
||||
* - [[chunk:N]] → 知识库引用(使用 chunks 映射表解析为文档名)
|
||||
* - [[知识库:文档名]] → 知识库引用徽章
|
||||
* - [[AI建议]] → AI建议徽章
|
||||
* - [[推荐应用:名称:slug]] → 可点击跳转链接
|
||||
*/
|
||||
function preprocessCitations(content: string): string {
|
||||
return content
|
||||
// 知识库引用:[[知识库:文献名称]] 或 [[知识库:文献名称:条款]]
|
||||
.replace(/\[\[(知识库:[^\]]+)\]\]/g, (_, label) => `[${label}](#cite-kb)`)
|
||||
// AI建议:标准格式 [[AI建议]]
|
||||
.replace(/\[\[AI建议\]\]/g, "[AI建议](#cite-ai)")
|
||||
// AI建议:来源说明块中的 **AI建议:** 或 **AI建议** 标题(仅匹配行首或 > 后)
|
||||
.replace(/^(\s*>?\s*)\*\*AI建议[::]\*\*/gm, "$1[AI建议](#cite-ai)")
|
||||
// AI建议:无加粗的 AI建议: 标题行(仅匹配行首或 > 后)
|
||||
.replace(/^(\s*>?\s*)AI建议[::]\s*$/gm, "$1[AI建议](#cite-ai)")
|
||||
// 推荐应用:[[推荐应用:应用名称:slug]] → 可点击跳转链接
|
||||
.replace(/\[\[推荐应用:([^:]+):([^\]]+)\]\]/g, (_, name, slug) => `[${name}](#cite-app:${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 }: GovMarkdownProps) {
|
||||
const GovMarkdown = memo(function GovMarkdown({ content, className, chunks }: GovMarkdownProps) {
|
||||
if (!content) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground py-1">
|
||||
@@ -188,7 +218,7 @@ const GovMarkdown = memo(function GovMarkdown({ content, className }: GovMarkdow
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const cleaned = preprocessCitations(stripOuterCodeFence(content));
|
||||
const cleaned = preprocessCitations(stripOuterCodeFence(content), chunks);
|
||||
return (
|
||||
<div className={`gov-markdown ${className || ""}`}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={mdComponents}>
|
||||
@@ -198,4 +228,4 @@ const GovMarkdown = memo(function GovMarkdown({ content, className }: GovMarkdow
|
||||
);
|
||||
});
|
||||
|
||||
export default GovMarkdown;
|
||||
export default GovMarkdown;
|
||||
Reference in New Issue
Block a user