feat(components): Add markdown editing capability and enhance component styling

- Add editable mode to Markdown component with edit/save/cancel buttons
- Import EditOutlined, SaveOutlined, CloseOutlined icons from ant-design
- Add useState, useRef, useEffect hooks for managing edit state
- Add editable, onContentChange, and onSave props to RbMarkdownProps interface
- Create RbModal component with new index.css stylesheet for modal styling
- Add index.css stylesheet to KnowledgeBase components for consistent styling
- Update i18n translations in en.ts and zh.ts for new UI elements
- Refactor Markdown component handlers to accept and spread additional props
- Update InsertModal and RecallTestResult components for improved UX
- Fix prop spreading in component handlers to maintain compatibility with Ant Design components
This commit is contained in:
yujiangping
2025-12-22 17:03:31 +08:00
parent ad2f47029d
commit 54ff151ed8
8 changed files with 306 additions and 81 deletions

View File

@@ -1,4 +1,5 @@
import { Image, Input, Select, Form, Checkbox, Radio, ColorPicker, DatePicker, TimePicker, InputNumber, Slider } from 'antd' import { Image, Input, Select, Form, Checkbox, Radio, ColorPicker, DatePicker, TimePicker, InputNumber, Slider, Button } from 'antd'
import { EditOutlined, SaveOutlined, CloseOutlined } from '@ant-design/icons'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import RemarkGfm from 'remark-gfm' import RemarkGfm from 'remark-gfm'
import RemarkMath from 'remark-math' import RemarkMath from 'remark-math'
@@ -6,6 +7,7 @@ import RemarkBreaks from 'remark-breaks'
import RehypeKatex from 'rehype-katex' import RehypeKatex from 'rehype-katex'
import RehypeRaw from 'rehype-raw' import RehypeRaw from 'rehype-raw'
import type { FC } from 'react' import type { FC } from 'react'
import { useState, useRef, useEffect } from 'react'
import Code from './Code' import Code from './Code'
import VideoBlock from './VideoBlock' import VideoBlock from './VideoBlock'
@@ -16,42 +18,45 @@ import RbButton from './RbButton'
interface RbMarkdownProps { interface RbMarkdownProps {
content: string; content: string;
showHtmlComments?: boolean; // 是否显示 HTML 注释,默认为 false隐藏 showHtmlComments?: boolean; // 是否显示 HTML 注释,默认为 false隐藏
editable?: boolean; // 是否可编辑,默认为 false
onContentChange?: (content: string) => void; // 内容变化回调
onSave?: (content: string) => void; // 保存回调
} }
const components = { const components = {
h1: ({ children }: { children: string }) => <h1 className="rb:text-2xl rb:font-bold rb:mb-2">{children}</h1>, h1: ({ children, ...props }: any) => <h1 className="rb:text-2xl rb:font-bold rb:mb-2" {...props}>{children}</h1>,
h2: ({ children }: { children: string }) => <h2 className="rb:text-xl rb:font-bold rb:mb-2">{children}</h2>, h2: ({ children, ...props }: any) => <h2 className="rb:text-xl rb:font-bold rb:mb-2" {...props}>{children}</h2>,
h3: ({ children }: { children: string }) => <h3 className="rb:text-lg rb:font-bold rb:mb-2">{children}</h3>, h3: ({ children, ...props }: any) => <h3 className="rb:text-lg rb:font-bold rb:mb-2" {...props}>{children}</h3>,
h4: ({ children }: { children: string }) => <h4 className="rb:text-md rb:font-bold rb:mb-2">{children}</h4>, h4: ({ children, ...props }: any) => <h4 className="rb:text-md rb:font-bold rb:mb-2" {...props}>{children}</h4>,
h5: ({ children }: { children: string }) => <h5 className="rb:text-sm rb:font-bold rb:mb-2">{children}</h5>, h5: ({ children, ...props }: any) => <h5 className="rb:text-sm rb:font-bold rb:mb-2" {...props}>{children}</h5>,
h6: ({ children }: { children: string }) => <h6 className="rb:text-xs rb:font-bold rb:mb-2">{children}</h6>, h6: ({ children, ...props }: any) => <h6 className="rb:text-xs rb:font-bold rb:mb-2" {...props}>{children}</h6>,
ul: ({ children }: { children: string }) => <ul className="rb:list-disc rb:ml-6 rb:mb-2">{children}</ul>, ul: ({ children, ...props }: any) => <ul className="rb:list-disc rb:ml-6 rb:mb-2" {...props}>{children}</ul>,
ol: ({ children }: { children: string }) => <ol className="rb:list-decimal rb:ml-6 rb:mb-2">{children}</ol>, ol: ({ children, ...props }: any) => <ol className="rb:list-decimal rb:ml-6 rb:mb-2" {...props}>{children}</ol>,
li: ({ children }: { children: string }) => <li className="rb:mb-1">{children}</li>, li: ({ children, ...props }: any) => <li className="rb:mb-1" {...props}>{children}</li>,
blockquote: ({ children }: { children: string }) => <blockquote className="rb:border-l-4 rb:border-[#D9D9D9] rb:pl-4 rb:mb-2">{children}</blockquote>, blockquote: ({ children, ...props }: any) => <blockquote className="rb:border-l-4 rb:border-[#D9D9D9] rb:pl-4 rb:mb-2" {...props}>{children}</blockquote>,
p: ({ children }: { children: string }) => <p className="rb:mb-2">{children}</p>, p: ({ children, ...props }: any) => <p className="rb:mb-2" {...props}>{children}</p>,
strong: ({ children }: { children: string }) => <strong className="rb:font-bold">{children}</strong>, strong: ({ children, ...props }: any) => <strong className="rb:font-bold" {...props}>{children}</strong>,
em: ({ children }: { children: string }) => <em className="rb:italic">{children}</em>, em: ({ children, ...props }: any) => <em className="rb:italic" {...props}>{children}</em>,
del: ({ children }: { children: string }) => <del className="rb:line-through">{children}</del>, del: ({ children, ...props }: any) => <del className="rb:line-through" {...props}>{children}</del>,
span: ({ children, ...props }: any) => { span: ({ children, style, ...restProps }: any) => {
// 如果是 HTML 注释的 span应用特殊样式 // 如果是 HTML 注释的 span应用特殊样式
if (props.style?.color === '#999') { if (style?.color === '#999') {
return <span style={{ color: '#999', fontSize: '0.9em' }}>{children}</span> return <span style={{ color: '#999', fontSize: '0.9em' }}>{children}</span>
} }
return <span {...props}>{children}</span> return <span style={style} {...restProps}>{children}</span>
}, },
code: Code, code: ({ children, className, ...props }: any) => <Code children={String(children)} className={className || ''} {...props} />,
img: Image, img: ({ src, alt, ...props }: any) => <Image src={src} alt={alt} {...props} />,
video: VideoBlock, video: ({ src, ...props }: any) => <VideoBlock node={{ children: [{ properties: { src: src || '' } }] }} {...props} />,
audio: AudioBlock, audio: ({ src, ...props }: any) => <AudioBlock node={{ children: [{ properties: { src: src || '' } }] }} {...props} />,
a: Link, a: ({ href, children, ...props }: any) => <Link href={href || '#'} {...props}>{children}</Link>,
button: RbButton, button: ({ children, ...props }: any) => <RbButton node={{ children }}>{[children]}</RbButton>,
table: ({ children }: { children: string }) => <table className="rb:border rb:border-[#D9D9D9] rb:mb-2">{children}</table>, table: ({ children, ...props }: any) => <table className="rb:border rb:border-[#D9D9D9] rb:mb-2" {...props}>{children}</table>,
tr: ({ children }: { children: string }) => <tr className="rb:border rb:border-[#D9D9D9]">{children}</tr>, tr: ({ children, ...props }: any) => <tr className="rb:border rb:border-[#D9D9D9]" {...props}>{children}</tr>,
th: ({ children }: { children: string }) => <th className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left rb:font-bold">{children}</th>, th: ({ children, ...props }: any) => <th className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left rb:font-bold" {...props}>{children}</th>,
td: ({ children }: { children: string }) => <td className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left">{children}</td>, td: ({ children, ...props }: any) => <td className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left" {...props}>{children}</td>,
input: ({ children, ...props }: { children: string }) => { input: ({ children, ...props }: any) => {
switch (props.type) { switch (props.type) {
case 'color': case 'color':
return <ColorPicker {...props} /> return <ColorPicker {...props} />
@@ -74,7 +79,7 @@ const components = {
return <Slider {...props} /> return <Slider {...props} />
case 'submit': case 'submit':
case 'button': case 'button':
return <RbButton {...props}>{props.value}</RbButton> return <RbButton node={{ children: props.value || children }}>{[props.value || children]}</RbButton>
case 'checkbox': case 'checkbox':
return <Checkbox {...props}>{children}</Checkbox> return <Checkbox {...props}>{children}</Checkbox>
case 'password': case 'password':
@@ -85,37 +90,158 @@ const components = {
return <Input value={children} {...props} /> return <Input value={children} {...props} />
} }
}, },
select: ({ children, ...props }: { children: string }) => <Select style={{width: '100%'}} {...props}>{children}</Select>, select: ({ children, ...props }: any) => <Select style={{width: '100%'}} {...props}>{children}</Select>,
textarea: ({ children, ...props }: { children: string }) => <Input.TextArea {...props}>{children}</Input.TextArea>, textarea: ({ children, ...props }: any) => <Input.TextArea {...props}>{children}</Input.TextArea>,
form: ({ children }: { children: string }) => <Form>{children}</Form>, form: ({ children, ...props }: any) => <Form {...props}>{children}</Form>,
} }
const RbMarkdown: FC<RbMarkdownProps> = ({ const RbMarkdown: FC<RbMarkdownProps> = ({
content, content,
showHtmlComments = false, showHtmlComments = false,
editable = false,
onContentChange,
onSave,
}) => { }) => {
const [isEditing, setIsEditing] = useState(editable) // 如果可编辑,默认进入编辑模式
const [editContent, setEditContent] = useState(content)
const textareaRef = useRef<any>(null)
// 当外部 content 变化时,同步更新编辑内容
useEffect(() => {
setEditContent(content)
}, [content])
// 当editable变化时自动切换编辑状态
useEffect(() => {
if (editable) {
setIsEditing(true)
// 延迟聚焦,确保 textarea 已渲染
setTimeout(() => {
textareaRef.current?.focus()
}, 100)
}
}, [editable])
// 进入编辑模式
const handleEdit = () => {
setIsEditing(true)
setEditContent(content)
// 延迟聚焦,确保 textarea 已渲染
setTimeout(() => {
textareaRef.current?.focus()
}, 100)
}
// 保存编辑
const handleSave = () => {
onContentChange?.(editContent)
onSave?.(editContent)
if (!editable) {
setIsEditing(false) // 只有在非强制编辑模式下才退出编辑
}
}
// 取消编辑
const handleCancel = () => {
setEditContent(content) // 恢复原内容
if (!editable) {
setIsEditing(false) // 只有在非强制编辑模式下才退出编辑
}
}
// 处理 textarea 内容变化
const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newContent = e.target.value
setEditContent(newContent)
// 实时回调内容变化
onContentChange?.(newContent)
}
// 根据参数决定是否将 HTML 注释转换为可见文本 // 根据参数决定是否将 HTML 注释转换为可见文本
// 使用特殊的 markdown 语法来显示注释,避免被 rehype-raw 过滤 // 使用特殊的 markdown 语法来显示注释,避免被 rehype-raw 过滤
const processedContent = showHtmlComments const processedContent = showHtmlComments
? content.replace(/<!--([\s\S]*?)-->/g, (_match, commentContent) => { ? (isEditing ? editContent : content).replace(/<!--([\s\S]*?)-->/g, (_match, commentContent) => {
// 转换为带样式的文本,使用 <span class="html-comment"> 标记 // 转换为带样式的文本,使用 <span class="html-comment"> 标记
const escaped = commentContent.trim().replace(/</g, '&lt;').replace(/>/g, '&gt;') const escaped = commentContent.trim().replace(/</g, '&lt;').replace(/>/g, '&gt;')
return `<span class="html-comment">&lt;!-- ${escaped} --&gt;</span>` return `<span class="html-comment">&lt;!-- ${escaped} --&gt;</span>`
}) })
: content : (isEditing ? editContent : content)
// 如果是编辑模式,显示 textarea
if (isEditing) {
return (
<div className="rb:relative">
<style>{`
.html-comment {
color: #999;
font-size: 0.9em;
}
`}</style>
{/* 编辑工具栏 - 只在非强制编辑模式下显示 */}
{!editable && (
<div className="rb:flex rb:justify-end rb:gap-2 rb:mb-2">
<Button
type="primary"
size="small"
icon={<SaveOutlined />}
onClick={handleSave}
>
</Button>
<Button
size="small"
icon={<CloseOutlined />}
onClick={handleCancel}
>
</Button>
</div>
)}
{/* 编辑区域 */}
<Input.TextArea
ref={textareaRef}
value={editContent}
onChange={handleTextareaChange}
rows={editable ? 5 : 10}
className="rb:font-mono rb:text-sm"
placeholder="请输入 Markdown 内容..."
style={{ resize: 'vertical' }}
/>
</div>
)
}
// 预览模式
return ( return (
<div> <div className="rb:relative rb:group">
<style>{` <style>{`
.html-comment { .html-comment {
color: #999; color: #999;
font-size: 0.9em; font-size: 0.9em;
} }
`}</style> `}</style>
{/* 编辑按钮 - 只在非强制编辑模式且鼠标悬停时显示 */}
{!editable && (
<div className="rb:absolute rb:top-0 rb:right-0 rb:opacity-0 group-hover:rb:opacity-100 rb:transition-opacity rb:z-10">
<Button
type="text"
size="small"
icon={<EditOutlined />}
onClick={handleEdit}
className="rb:bg-white rb:shadow-sm"
>
</Button>
</div>
)}
<ReactMarkdown <ReactMarkdown
// allowElement={[]} // allowElement={[]}
// allowedElements={[]} // allowedElements={[]}
components={components} components={components as any}
disallowedElements={['script', 'iframe', 'head', 'html', 'meta', 'link', 'style', 'body']} disallowedElements={['script', 'iframe', 'head', 'html', 'meta', 'link', 'style', 'body']}
rehypePlugins={[ rehypePlugins={[
RehypeKatex, RehypeKatex,

View File

@@ -0,0 +1,3 @@
.rb-modal .ant-modal-header {
margin-bottom: 24px;
}

View File

@@ -1,11 +1,19 @@
/*
* @Description:
* @Version: 0.0.1
* @Author: yujiangping
* @Date: 2025-12-16 10:19:18
* @LastEditors: yujiangping
* @LastEditTime: 2025-12-22 12:31:31
*/
import { type FC } from 'react' import { type FC } from 'react'
import { Modal, type ModalProps } from 'antd' import { Modal, type ModalProps } from 'antd'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
const RbModal: FC<ModalProps> = ({ const RbModal: FC<ModalProps> = ({
onOk, onOk,
onCancel, onCancel,
children, children,
className,
...props ...props
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
@@ -16,6 +24,7 @@ const RbModal: FC<ModalProps> = ({
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
onOk={onOk} onOk={onOk}
destroyOnHidden={true} destroyOnHidden={true}
className={`rb-modal ${className || ''}`}
{...props} {...props}
> >
<div className='rb:max-h-[550px] rb:overflow-y-auto rb:overflow-x-hidden'> <div className='rb:max-h-[550px] rb:overflow-y-auto rb:overflow-x-hidden'>

View File

@@ -585,7 +585,6 @@ export const en = {
insertContent: 'Insert Content', insertContent: 'Insert Content',
editContent:'Edit Content', editContent:'Edit Content',
insertContentPlaceholder: 'Please enter the content', insertContentPlaceholder: 'Please enter the content',
pleaseEnterContent: 'Please enter content',
documentIdRequired: 'Document ID is required', documentIdRequired: 'Document ID is required',
editContentDesc:'Edit content', editContentDesc:'Edit content',
insertContentDesc:'Insert content', insertContentDesc:'Insert content',
@@ -598,6 +597,10 @@ export const en = {
semantic:'Semantic', semantic:'Semantic',
hybrid:'Hybrid', hybrid:'Hybrid',
updateEmbeddingContent:'Are you sure about updating the embedding model? After the update, will the block vector data need to be reconstructed?', updateEmbeddingContent:'Are you sure about updating the embedding model? After the update, will the block vector data need to be reconstructed?',
question: 'Question',
answer: 'Answer',
normalMode: 'Normal Mode',
qaMode: 'QA Mode',
createForm:{ createForm:{
name: 'Name', name: 'Name',
embedding_id: 'Embedding', embedding_id: 'Embedding',

View File

@@ -216,7 +216,6 @@ export const zh = {
insertContent: '插入内容', insertContent: '插入内容',
editContent: '编辑内容', editContent: '编辑内容',
insertContentPlaceholder: '请输入内容', insertContentPlaceholder: '请输入内容',
pleaseEnterContent: '请输入内容',
documentIdRequired: '文档ID是必需的', documentIdRequired: '文档ID是必需的',
editContentDesc: '编辑内容', editContentDesc: '编辑内容',
insertContentDesc: '插入内容', insertContentDesc: '插入内容',
@@ -229,6 +228,10 @@ export const zh = {
semantic: '语义', semantic: '语义',
hybrid: '混合', hybrid: '混合',
updateEmbeddingContent: '确定要更新嵌入模型吗?更新后,分块向量数据需要重新构建?', updateEmbeddingContent: '确定要更新嵌入模型吗?更新后,分块向量数据需要重新构建?',
question: '问题',
answer: '答案',
normalMode: '常规模式',
qaMode: '问答模式',
createForm: { createForm: {
name: '名称', name: '名称',
embedding_id: '嵌入模型', embedding_id: '嵌入模型',

View File

@@ -1,10 +1,9 @@
import { forwardRef, useImperativeHandle, useState } from 'react'; import { forwardRef, useImperativeHandle, useState } from 'react';
import { Input, message, Tabs } from 'antd'; import { message, Tabs } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import RbModal from '@/components/RbModal'; import RbModal from '@/components/RbModal';
import RbMarkdown from '@/components/Markdown'; import RbMarkdown from '@/components/Markdown';
import './index.css'
const { TextArea } = Input;
export interface InsertModalRef { export interface InsertModalRef {
handleOpen: (documentId: string, initialContent?: string, chunkId?: string) => void; handleOpen: (documentId: string, initialContent?: string, chunkId?: string) => void;
@@ -24,11 +23,14 @@ const InsertModal = forwardRef<InsertModalRef, InsertModalProps>(({ onInsert, on
const [content, setContent] = useState<string>(''); const [content, setContent] = useState<string>('');
const [chunkId, setChunkId] = useState<string | undefined>(undefined); const [chunkId, setChunkId] = useState<string | undefined>(undefined);
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
const [activeTab, setActiveTab] = useState<string>('edit'); const [activeTab, setActiveTab] = useState<string>('normalMode');
const [mode, setMode] = useState(0); // 0: 普通模式, 1: QA模式
const [question, setQuestion] = useState<string>('');
const [answer, setAnswer] = useState<string>('');
const handleOpen = (docId: string, initialContent?: string, chunkIdParam?: string) => { const handleOpen = (docId: string, initialContent?: string, chunkIdParam?: string) => {
setDocumentId(docId); setDocumentId(docId);
setContent(initialContent || ''); handleContent(initialContent || '')
setChunkId(chunkIdParam); setChunkId(chunkIdParam);
setIsEditMode(!!initialContent); setIsEditMode(!!initialContent);
setVisible(true); setVisible(true);
@@ -40,11 +42,63 @@ const InsertModal = forwardRef<InsertModalRef, InsertModalProps>(({ onInsert, on
setDocumentId(''); setDocumentId('');
setChunkId(undefined); setChunkId(undefined);
setIsEditMode(false); setIsEditMode(false);
setActiveTab('edit'); setActiveTab('normalMode');
setMode(0);
setQuestion('');
setAnswer('');
};
// 解析 QA 格式内容
const parseQAContent = (content: string) => {
if (!content) return null;
const qaRegex = /question:\s*(.*?)\s*answer:\s*(.*?)$/s;
const match = content.match(qaRegex);
if (match) {
const question = match[1]?.trim() || '';
const answer = match[2]?.trim() || '';
return { question, answer };
}
return null;
};
const handleContent = (value: string) => {
if (value === '') return;
const qaContent = parseQAContent(value);
if (qaContent) {
setMode(1); // 1 表示 QA 模式
setQuestion(qaContent.question);
setAnswer(qaContent.answer);
setContent(qaContent.answer); // 保持原始内容用于提交
setActiveTab('qaMode')
} else {
setMode(0);
setAnswer(value)
setContent(value);
setActiveTab('normalMode')
}
};
const handleTabsChange = (key: string) => {
if(key === 'qaMode'){
setMode(1);
}else{
setMode(0);
}
setActiveTab(key);
};
// 获取当前要提交的内容
const getCurrentContent = () => {
if (mode === 1) {
return `question: ${question}\n answer: ${answer}`;
}
return content;
}; };
const handleOk = async () => { const handleOk = async () => {
if (!content.trim()) { const currentContent = getCurrentContent();
if (!currentContent.trim()) {
message.warning(t('knowledgeBase.pleaseEnterContent') || '请输入内容'); message.warning(t('knowledgeBase.pleaseEnterContent') || '请输入内容');
return; return;
} }
@@ -57,7 +111,7 @@ const InsertModal = forwardRef<InsertModalRef, InsertModalProps>(({ onInsert, on
setLoading(true); setLoading(true);
try { try {
if (onInsert) { if (onInsert) {
const success = await onInsert(documentId, content.trim(), chunkId); const success = await onInsert(documentId, currentContent.trim(), chunkId);
if (success) { if (success) {
const successMsg = isEditMode const successMsg = isEditMode
? (t('knowledgeBase.updateSuccess') || '更新成功') ? (t('knowledgeBase.updateSuccess') || '更新成功')
@@ -86,47 +140,70 @@ const InsertModal = forwardRef<InsertModalRef, InsertModalProps>(({ onInsert, on
} }
}; };
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value);
};
// 暴露给父组件的方法 // 暴露给父组件的方法
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleOpen, handleOpen,
handleClose, handleClose,
})); }));
// 构建标签页项目content 为空或新增时不显示预览 // 构建标签页项目
const tabItems = [ const tabItems = [
{ {
key: 'edit', key: 'normalMode',
label: t('knowledgeBase.edit') || '编辑', label: t('knowledgeBase.normalMode'),
children: ( children: (
<TextArea // <div className='rb:border rb:border-[#D9D9D9] rb:rounded rb:p-4 rb:min-h-[280px] rb:max-h-[400px] rb:overflow-y-auto rb:bg-white'>
value={content} <RbMarkdown
onChange={handleContentChange} content={content}
placeholder={t('knowledgeBase.insertContentPlaceholder') || '请输入内容...'} showHtmlComments={true}
rows={10} editable={true}
maxLength={10000} onContentChange={setContent}
showCount onSave={(newContent) => {
autoFocus setContent(newContent);
/> }}
/>
// </div>
), ),
}, },
]; {
key: 'qaMode',
// 只有在编辑模式且有内容时才显示预览标签页 label: t('knowledgeBase.qaMode'),
if (isEditMode && content) {
tabItems.push({
key: 'preview',
label: t('knowledgeBase.preview') || '预览',
children: ( children: (
<div className='rb:border rb:border-[#D9D9D9] rb:rounded rb:p-4 rb:min-h-[280px] rb:max-h-[400px] rb:overflow-y-auto rb:bg-white'> // QA 模式的编辑界面
<RbMarkdown content={content} showHtmlComments={true} /> <div className='rb:flex rb:flex-col rb:gap-4'>
<div>
<div className='rb:w-full rb:font-medium rb:leading-8 rb:mb-2'>{t('knowledgeBase.question') || '问题'}</div>
{/* <div className='rb:border rb:border-[#D9D9D9] rb:rounded rb:p-4 rb:min-h-[120px] rb:max-h-[200px] rb:overflow-y-auto rb:bg-white'> */}
<RbMarkdown
content={question}
showHtmlComments={true}
editable={true}
onContentChange={setQuestion}
onSave={(newContent) => {
setQuestion(newContent);
}}
/>
{/* </div> */}
</div>
<div>
<div className='rb:w-full rb:font-medium rb:leading-8 rb:mb-2'>{t('knowledgeBase.answer') || '答案'}</div>
{/* <div className='rb:border rb:border-[#D9D9D9] rb:rounded rb:p-4 rb:min-h-[120px] rb:max-h-[200px] rb:overflow-y-auto rb:bg-white'> */}
<RbMarkdown
content={answer}
showHtmlComments={true}
editable={true}
onContentChange={setAnswer}
onSave={(newContent) => {
setAnswer(newContent);
}}
/>
{/* </div> */}
</div>
</div> </div>
), )
}); }
}
];
return ( return (
<RbModal <RbModal
@@ -141,11 +218,12 @@ const InsertModal = forwardRef<InsertModalRef, InsertModalProps>(({ onInsert, on
okText={t('common.confirm') || '确认'} okText={t('common.confirm') || '确认'}
cancelText={t('common.cancel') || '取消'} cancelText={t('common.cancel') || '取消'}
width={600} width={600}
className='rb:h-[800px]'
> >
<div className='rb:flex rb:flex-col rb:gap-4'> <div className='rb:flex rb:flex-col rb:gap-4'>
<Tabs <Tabs
activeKey={activeTab} activeKey={activeTab}
onChange={setActiveTab} onChange={handleTabsChange}
items={tabItems} items={tabItems}
/> />
</div> </div>

View File

@@ -4,7 +4,7 @@
* @Author: yujiangping * @Author: yujiangping
* @Date: 2025-11-18 16:19:58 * @Date: 2025-11-18 16:19:58
* @LastEditors: yujiangping * @LastEditors: yujiangping
* @LastEditTime: 2025-11-29 19:08:40 * @LastEditTime: 2025-12-22 13:47:53
*/ */
import { FileOutlined, FieldTimeOutlined, EditOutlined } from '@ant-design/icons'; import { FileOutlined, FieldTimeOutlined, EditOutlined } from '@ant-design/icons';
import { Skeleton } from 'antd'; import { Skeleton } from 'antd';
@@ -58,7 +58,7 @@ const RecallTestResult = ({
// 格式化 QA 内容为显示格式 // 格式化 QA 内容为显示格式
const formatQAContent = (question: string, answer: string) => { const formatQAContent = (question: string, answer: string) => {
return `**问题:** ${question}\n\n**答案:** ${answer}`; return `**${t('knowledgeBase.question')}:** ${question}\n**${t('knowledgeBase.answer')}:** ${answer}`;
}; };
const handleItemClick = (e: React.MouseEvent, item: RecallTestData, index: number) => { const handleItemClick = (e: React.MouseEvent, item: RecallTestData, index: number) => {

View File

@@ -0,0 +1,3 @@
.ant-modal-header {
margin-bottom: 0 !important;
}