Compare commits
2 Commits
39be510299
...
1977477fd3
| Author | SHA1 | Date |
|---|---|---|
|
|
1977477fd3 | |
|
|
a27102d1fa |
|
|
@ -144,11 +144,11 @@ const MindMap = ({ content, title }) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const processedMarkdown = preprocessMarkdownForMindMap(markdown, title);
|
const processedMarkdown = preprocessMarkdownForMindMap(markdown, title);
|
||||||
console.log('=== 思维导图数据调试 ===');
|
// console.log('=== 思维导图数据调试 ===');
|
||||||
console.log('原始markdown内容:');
|
// console.log('原始markdown内容:');
|
||||||
console.log(markdown);
|
// console.log(markdown);
|
||||||
console.log('预处理后的markdown:');
|
// console.log('预处理后的markdown:');
|
||||||
console.log(processedMarkdown);
|
// console.log(processedMarkdown);
|
||||||
|
|
||||||
const transformer = new Transformer();
|
const transformer = new Transformer();
|
||||||
const { root } = transformer.transform(processedMarkdown);
|
const { root } = transformer.transform(processedMarkdown);
|
||||||
|
|
@ -161,10 +161,20 @@ const MindMap = ({ content, title }) => {
|
||||||
|
|
||||||
markmapRef.current.fit();
|
markmapRef.current.fit();
|
||||||
|
|
||||||
// 延迟一下再次调用fit,确保内容完全渲染
|
// 延迟一下再次调用fit并放大,确保内容完全渲染
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (markmapRef.current) {
|
if (markmapRef.current) {
|
||||||
markmapRef.current.fit();
|
markmapRef.current.fit();
|
||||||
|
// 在fit之后,再放大1.8倍以获得更好的可读性
|
||||||
|
try {
|
||||||
|
const { state } = markmapRef.current;
|
||||||
|
if (state) {
|
||||||
|
const currentScale = state.transform.k;
|
||||||
|
markmapRef.current.rescale(currentScale * 1.8);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('缩放调整失败:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -504,6 +504,7 @@ const EditMeeting = ({ user }) => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowUploadArea(false);
|
setShowUploadArea(false);
|
||||||
setAudioFile(null);
|
setAudioFile(null);
|
||||||
|
setError('');
|
||||||
// Reset file input
|
// Reset file input
|
||||||
const fileInput = document.getElementById('audio-file');
|
const fileInput = document.getElementById('audio-file');
|
||||||
if (fileInput) fileInput.value = '';
|
if (fileInput) fileInput.value = '';
|
||||||
|
|
@ -550,6 +551,36 @@ const EditMeeting = ({ user }) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Error message for audio upload - shown right after upload area */}
|
||||||
|
{error && showUploadArea && (
|
||||||
|
<div className="error-message">{error}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Upload Confirmation Modal - moved here to be right after upload area */}
|
||||||
|
{showUploadConfirm && (
|
||||||
|
<div className="delete-modal-overlay" onClick={() => setShowUploadConfirm(false)}>
|
||||||
|
<div className="delete-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<h3>确认重新上传</h3>
|
||||||
|
<p>重传音频文件将清空已有的会话转录,是否继续?</p>
|
||||||
|
<div className="modal-actions">
|
||||||
|
<button
|
||||||
|
className="btn-cancel"
|
||||||
|
onClick={() => setShowUploadConfirm(false)}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn-submit"
|
||||||
|
onClick={handleUploadAudio}
|
||||||
|
disabled={isUploading}
|
||||||
|
>
|
||||||
|
确定重传
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -604,7 +635,7 @@ const EditMeeting = ({ user }) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && !showUploadArea && (
|
||||||
<div className="error-message">{error}</div>
|
<div className="error-message">{error}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -624,31 +655,6 @@ const EditMeeting = ({ user }) => {
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upload Confirmation Modal */}
|
|
||||||
{showUploadConfirm && (
|
|
||||||
<div className="delete-modal-overlay" onClick={() => setShowUploadConfirm(false)}>
|
|
||||||
<div className="delete-modal" onClick={(e) => e.stopPropagation()}>
|
|
||||||
<h3>确认重新上传</h3>
|
|
||||||
<p>重传音频文件将清空已有的会话转录,是否继续?</p>
|
|
||||||
<div className="modal-actions">
|
|
||||||
<button
|
|
||||||
className="btn-cancel"
|
|
||||||
onClick={() => setShowUploadConfirm(false)}
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn-submit"
|
|
||||||
onClick={handleUploadAudio}
|
|
||||||
disabled={isUploading}
|
|
||||||
>
|
|
||||||
确定重传
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,6 @@
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #f1f5f9;
|
|
||||||
color: #64748b;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -102,9 +100,20 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-new-kb {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-new-kb:hover {
|
.btn-new-kb:hover {
|
||||||
background: #e2e8f0;
|
background: #5568d3;
|
||||||
color: #667eea;
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-toggle-sidebar {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-toggle-sidebar:hover {
|
.btn-toggle-sidebar:hover {
|
||||||
|
|
@ -119,6 +128,27 @@
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 日期分组 */
|
||||||
|
.kb-date-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-group-header {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #64748b;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 6px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
.kb-list-item {
|
.kb-list-item {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
|
@ -627,13 +657,25 @@
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meeting-item label {
|
.meeting-item-content {
|
||||||
cursor: pointer;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-item-title {
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
color: #475569;
|
color: #475569;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-item-creator {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-meetings-info {
|
.selected-meetings-info {
|
||||||
|
|
|
||||||
|
|
@ -74,10 +74,14 @@ const KnowledgeBasePage = ({ user }) => {
|
||||||
// 获取所有知识库(个人和共享)
|
// 获取所有知识库(个人和共享)
|
||||||
apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.LIST))
|
apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.LIST))
|
||||||
.then(response => {
|
.then(response => {
|
||||||
setKbs(response.data.kbs);
|
// 按创建时间倒序排序
|
||||||
|
const sortedKbs = response.data.kbs.sort((a, b) =>
|
||||||
|
new Date(b.created_at) - new Date(a.created_at)
|
||||||
|
);
|
||||||
|
setKbs(sortedKbs);
|
||||||
// 如果有知识库且没有选中,默认选中第一个
|
// 如果有知识库且没有选中,默认选中第一个
|
||||||
if (response.data.kbs.length > 0 && !selectedKb) {
|
if (sortedKbs.length > 0 && !selectedKb) {
|
||||||
loadKbDetail(response.data.kbs[0].kb_id);
|
loadKbDetail(sortedKbs[0].kb_id);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
|
|
@ -177,6 +181,45 @@ const KnowledgeBasePage = ({ user }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatTime = (dateString) => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleTimeString('zh-CN', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatShortDate = (dateString) => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString('zh-CN', {
|
||||||
|
month: 'numeric',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isToday = (dateString) => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
const today = new Date();
|
||||||
|
return date.getDate() === today.getDate() &&
|
||||||
|
date.getMonth() === today.getMonth() &&
|
||||||
|
date.getFullYear() === today.getFullYear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupKbsByDate = (kbList) => {
|
||||||
|
const todayKbs = [];
|
||||||
|
const pastKbs = [];
|
||||||
|
|
||||||
|
kbList.forEach(kb => {
|
||||||
|
if (isToday(kb.created_at)) {
|
||||||
|
todayKbs.push(kb);
|
||||||
|
} else {
|
||||||
|
pastKbs.push(kb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { todayKbs, pastKbs };
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogoClick = () => {
|
const handleLogoClick = () => {
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
};
|
};
|
||||||
|
|
@ -445,7 +488,15 @@ const KnowledgeBasePage = ({ user }) => {
|
||||||
<p>暂无知识库条目</p>
|
<p>暂无知识库条目</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
kbs.map(kb => (
|
(() => {
|
||||||
|
const { todayKbs, pastKbs } = groupKbsByDate(kbs);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* 今天的知识库 */}
|
||||||
|
{todayKbs.length > 0 && (
|
||||||
|
<div className="kb-date-group">
|
||||||
|
<div className="date-group-header">今天</div>
|
||||||
|
{todayKbs.map(kb => (
|
||||||
<div
|
<div
|
||||||
key={kb.kb_id}
|
key={kb.kb_id}
|
||||||
className={`kb-list-item ${selectedKb && selectedKb.kb_id === kb.kb_id ? 'active' : ''}`}
|
className={`kb-list-item ${selectedKb && selectedKb.kb_id === kb.kb_id ? 'active' : ''}`}
|
||||||
|
|
@ -453,7 +504,7 @@ const KnowledgeBasePage = ({ user }) => {
|
||||||
>
|
>
|
||||||
<div className="kb-list-item-header">
|
<div className="kb-list-item-header">
|
||||||
<h3>{kb.title}</h3>
|
<h3>{kb.title}</h3>
|
||||||
{isCreator && (
|
{String(kb.creator_id) === String(user.user_id) && (
|
||||||
<div className="kb-item-actions">
|
<div className="kb-item-actions">
|
||||||
<button
|
<button
|
||||||
className="btn-edit-kb"
|
className="btn-edit-kb"
|
||||||
|
|
@ -479,17 +530,68 @@ const KnowledgeBasePage = ({ user }) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="kb-list-item-meta">
|
<div className="kb-list-item-meta">
|
||||||
<span className="meta-item">
|
<span className="meta-time">{formatTime(kb.created_at)}</span>
|
||||||
<Calendar size={12} />
|
|
||||||
{formatDate(kb.created_at)}
|
|
||||||
</span>
|
|
||||||
<span className="meta-item">
|
<span className="meta-item">
|
||||||
<Database size={12} />
|
<Database size={12} />
|
||||||
{kb.source_meeting_count || 0} 个数据源
|
{kb.source_meeting_count || 0} 个数据源
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 之前的知识库 */}
|
||||||
|
{pastKbs.length > 0 && (
|
||||||
|
<div className="kb-date-group">
|
||||||
|
<div className="date-group-header">之前</div>
|
||||||
|
{pastKbs.map(kb => (
|
||||||
|
<div
|
||||||
|
key={kb.kb_id}
|
||||||
|
className={`kb-list-item ${selectedKb && selectedKb.kb_id === kb.kb_id ? 'active' : ''}`}
|
||||||
|
onClick={() => handleKbSelect(kb)}
|
||||||
|
>
|
||||||
|
<div className="kb-list-item-header">
|
||||||
|
<h3>{kb.title}</h3>
|
||||||
|
{String(kb.creator_id) === String(user.user_id) && (
|
||||||
|
<div className="kb-item-actions">
|
||||||
|
<button
|
||||||
|
className="btn-edit-kb"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
navigate(`/knowledge-base/edit/${kb.kb_id}`);
|
||||||
|
}}
|
||||||
|
title="编辑"
|
||||||
|
>
|
||||||
|
<Edit size={14} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn-delete-kb"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleDelete(kb);
|
||||||
|
}}
|
||||||
|
title="删除"
|
||||||
|
>
|
||||||
|
<Trash2 size={14} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="kb-list-item-meta">
|
||||||
|
<span className="meta-date">{formatShortDate(kb.created_at)}</span>
|
||||||
|
<span className="meta-item">
|
||||||
|
<Database size={12} />
|
||||||
|
{kb.source_meeting_count || 0} 个数据源
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -644,9 +746,18 @@ const KnowledgeBasePage = ({ user }) => {
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedMeetings.includes(meeting.meeting_id)}
|
checked={selectedMeetings.includes(meeting.meeting_id)}
|
||||||
onChange={() => toggleMeetingSelection(meeting.meeting_id)}
|
onChange={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleMeetingSelection(meeting.meeting_id);
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
<label>{meeting.title}</label>
|
<div className="meeting-item-content">
|
||||||
|
<div className="meeting-item-title">{meeting.title}</div>
|
||||||
|
{meeting.creator_username && (
|
||||||
|
<div className="meeting-item-creator">创建人: {meeting.creator_username}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
import rehypeSanitize from 'rehype-sanitize';
|
import rehypeSanitize from 'rehype-sanitize';
|
||||||
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
|
import { buildApiUrl, API_ENDPOINTS, API_BASE_URL } from '../config/api';
|
||||||
import ContentViewer from '../components/ContentViewer';
|
import ContentViewer from '../components/ContentViewer';
|
||||||
import TagDisplay from '../components/TagDisplay';
|
import TagDisplay from '../components/TagDisplay';
|
||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue