Files
BossAi/web2/src/components/common/Markdown.tsx
T
selfreleaseandDevin 4de36547cd
CI / 后端测试 (push) Has been cancelled
CI / 前端构建 (push) Has been cancelled
feat: BossAi v2.0 UI/UX 重构 + 看板断点续接 + 多轮上下文
- 新增 web2/ 前端:任务卡看板、风险雷达、决策日志、我的团队、快捷指令
- ChatPanel 增强:风险卡置顶、案卡接入、起诉状向导、语音输入
- 事项看板:点击查看详情弹窗、断点续接(恢复历史对话+matterId)
- 保密模式恢复:密码验证后重建内存 session,保留多轮上下文
- 多轮对话:ask/ask-stream/ask-analyze 注入 session 历史,AI 有上下文
- 追问优化:紧扣用户问题,不再偏离主题(社保问社保,工伤问工伤)
- 对话区可滚动,输入区固定可见
- 状态机放宽:允许从任意非终态转到 pending_expert
- session 存活检查端点,后端重启后自动重建 session
- DB migration:风险推送表、案卡表

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-31 08:38:33 +08:00

113 lines
3.5 KiB
TypeScript

import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import rehypeHighlight from 'rehype-highlight'
import { cn } from '@/lib/utils'
/** Markdown 渲染组件:支持 GFM(表格/列表/删除线)+ 代码高亮。
* 用于 AI 回复的富文本展示,替代纯 whitespace-pre-wrap。
* 流式输出时传入 streaming=true 显示光标。
*/
export function Markdown({
content,
streaming,
className,
}: {
content: string
streaming?: boolean
className?: string
}) {
return (
<div
className={cn(
'prose prose-sm max-w-none dark:prose-invert',
// 继承父级文字色,不被 prose 默认色覆盖
'prose-headings:text-foreground prose-p:text-foreground prose-li:text-foreground',
'prose-strong:text-foreground prose-a:text-primary',
// 紧凑间距
'prose-headings:my-2 prose-headings:first:mt-0 prose-p:my-2',
'prose-ul:my-2 prose-ol:my-2 prose-li:my-0.5',
'prose-pre:my-3 prose-code:before:content-none prose-code:after:content-none',
'prose-table:my-3 prose-th:px-2 prose-th:py-1 prose-td:px-2 prose-td:py-1',
className,
)}
>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeHighlight]}
components={{
// 代码块:圆角 + 暗色背景
pre: ({ children, ...props }) => (
<pre
className="rounded-lg bg-slate-900 p-3 text-xs text-slate-100 overflow-x-auto"
{...props}
>
{children}
</pre>
),
// 行内代码:浅灰背景
code: ({ children, className, ...props }) => {
const isInline = !className?.includes('language-')
if (isInline) {
return (
<code
className="rounded bg-muted px-1 py-0.5 text-xs font-mono"
{...props}
>
{children}
</code>
)
}
return (
<code className={className} {...props}>
{children}
</code>
)
},
// 表格:外层包裹 overflow-x-auto 防止小屏溢出
table: ({ children, ...props }) => (
<div className="overflow-x-auto">
<table
className="w-full min-w-full border-collapse border text-xs"
{...props}
>
{children}
</table>
</div>
),
th: ({ children, ...props }) => (
<th
className="border bg-muted/50 font-semibold text-left"
{...props}
>
{children}
</th>
),
td: ({ children, ...props }) => (
<td className="border" {...props}>
{children}
</td>
),
// 链接:新标签打开
a: ({ children, href, ...props }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-primary underline underline-offset-2"
{...props}
>
{children}
</a>
),
}}
>
{content + (streaming && content && !content.endsWith('\n') ? '\n' : '')}
</ReactMarkdown>
{streaming && (
<span className="ml-0.5 inline-block h-4 w-0.5 animate-pulse bg-current align-text-bottom" aria-hidden />
)}
</div>
)
}