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>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user