feat: enhance document upload and processing features, update mock data, and improve status display

This commit is contained in:
2026-05-08 19:09:00 +08:00
parent 18f1f96768
commit 41f9c122a9
7 changed files with 493 additions and 119 deletions

View File

@@ -1,7 +1,9 @@
import type { Doc } from '../types';
export const mockDocs: Doc[] = [
{ id: 1, name: '道路交通安全法.pdf', chunks: 124, size: '1.2MB', status: 'indexed' },
{ id: 2, name: '机动车登记规定.docx', chunks: 58, size: '856KB', status: 'indexed' },
{ id: 3, name: '电动自行车规范.pdf', chunks: 32, size: '245KB', status: 'indexed' },
{ id: 1, name: '道路交通安全法.pdf', chunks: 156, size: '1.8MB', status: 'indexed' },
{ id: 2, name: '机动车登记规定.docx', chunks: 89, size: '1.1MB', status: 'indexed' },
{ id: 3, name: '电动自行车规范.pdf', chunks: 42, size: '345KB', status: 'indexed' },
{ id: 4, name: 'GB 38031-2020 电动汽车安全要求.pdf', chunks: 128, size: '2.2MB', status: 'indexed' },
{ id: 5, name: 'C-NCAP管理规则(2021版).pdf', chunks: 95, size: '1.5MB', status: 'indexed' },
];

View File

@@ -5,4 +5,12 @@ export const mockRetrievalData: RetrievalData[] = [
{ id: 2, file: '电动自行车规范.pdf', clause: '第二条', score: 0.87, content: '最高设计车速不超过25km/h整车质量含电池不超过55kg具有脚踏骑行能力蓄电池标称电压不超过48V。' },
{ id: 3, file: '道路交通安全法.pdf', clause: '第七十二条', score: 0.79, content: '驾驶电动自行车在道路上行驶,应当遵守下列规定:佩戴安全头盔;不得逆向行驶;不得在机动车道内行驶。' },
{ id: 4, file: '机动车登记规定.pdf', clause: '第五条', score: 0.72, content: '初次申领机动车号牌、行驶证的,应当向住所地的车辆管理所申请注册登记,填写申请表,交验机动车。' },
{ id: 5, file: 'GB 38031-2020 电动汽车安全要求.pdf', clause: '5.1 热失控要求', score: 0.95, content: '电池系统发生热失控后应在5分钟内不起火不爆炸为乘员预留逃生时间。电池包需通过针刺、过充、短路等安全测试。' },
{ id: 6, file: 'C-NCAP管理规则(2021版).pdf', clause: '4.2 正面碰撞', score: 0.88, content: '正面100%重叠刚性壁障碰撞试验碰撞速度50km/h。试验后车门应能打开燃油系统无泄漏座椅及安全带功能正常。' },
{ id: 7, file: '道路交通安全法.pdf', clause: '第九十条', score: 0.76, content: '机动车驾驶人违反道路交通安全法律、法规关于道路通行规定的,处警告或者二十元以上二百元以下罚款。' },
{ id: 8, file: '机动车登记规定.pdf', clause: '第十二条', score: 0.65, content: '机动车所有人的住所迁出车辆管理所管辖区域的,车辆管理所应当自受理之日起三日内,在机动车登记证书上签注变更事项。' },
{ id: 9, file: 'GB 38031-2020 电动汽车安全要求.pdf', clause: '6.2 充电安全', score: 0.91, content: '充电系统应具备过充保护功能当电池SOC达到100%时应自动停止充电。充电接口应符合GB/T 18487.1标准要求。' },
{ id: 10, file: 'C-NCAP管理规则(2021版).pdf', clause: '5.3.1 AEB功能', score: 0.84, content: '自动紧急制动系统(AEB)应在检测到前方障碍物时主动减速或停车。AEB系统测试分为目标车静止、移动、制动三种场景。' },
{ id: 11, file: '道路交通安全法.pdf', clause: '第六十七条', score: 0.70, content: '机动车在高速公路上行驶车速超过100km/h时应当与同车道前车保持100米以上的距离车速低于100km/h时距离可适当缩短。' },
{ id: 12, file: '道路交通安全法.pdf', clause: '第五十三条', score: 0.68, content: '警车、消防车、救护车、工程救险车执行紧急任务时,可以使用警报器、标志灯具,在确保安全的前提下,不受行驶路线、行驶方向、行驶速度和信号灯的限制。' },
];

View File

@@ -1,11 +1,180 @@
import React from 'react';
import React, { useState, useRef } from 'react';
import { useTheme } from '../../contexts/ThemeContext';
import { Content } from '../../components/layout/Content';
import { TPattern } from '../../components/common/TPattern';
import { mockDocs } from '../../data';
import type { Doc } from '../../types';
export const DocsPage: React.FC = () => {
const { theme, isDark } = useTheme();
const fileInputRef = useRef<HTMLInputElement>(null);
const [activeStep, setActiveStep] = useState<number>(-1);
const [completedSteps, setCompletedSteps] = useState<number[]>([]);
const [docs, setDocs] = useState<Doc[]>(mockDocs);
const [uploading, setUploading] = useState(false);
const [uploadFileName, setUploadFileName] = useState<string>('');
const pipelineSteps = [
{ name: 'LOAD' },
{ name: 'PARSE' },
{ name: 'CHUNK' },
{ name: 'EMBED' },
{ name: 'STORE' },
];
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file || uploading) return;
setUploading(true);
setUploadFileName(file.name);
setActiveStep(0);
setCompletedSteps([]);
// Add new doc in "parsing" state
const fileSizeMB = (file.size / (1024 * 1024)).toFixed(1);
const newDoc: Doc = {
id: Date.now(),
name: file.name,
chunks: 0,
size: `${fileSizeMB}MB`,
status: 'parsing',
};
setDocs(prev => [...prev, newDoc]);
// Simulate each pipeline step
const stepDuration = 600;
pipelineSteps.forEach((_, index) => {
setTimeout(() => {
setActiveStep(index);
setTimeout(() => {
setCompletedSteps(prev => [...prev, index]);
if (index === pipelineSteps.length - 1) {
// Final step completed
setActiveStep(-1);
setUploading(false);
setUploadFileName('');
// Update doc to indexed state with mock chunks
setDocs(prev => prev.map(d =>
d.id === newDoc.id ? { ...d, chunks: Math.floor(file.size / 8000) + 20, status: 'indexed' } : d
));
}
}, stepDuration - 100);
}, index * stepDuration);
});
// Clear input for next upload
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
const triggerFileUpload = () => {
if (uploading) return;
fileInputRef.current?.click();
};
const handleDragOver = (event: React.DragEvent) => {
event.preventDefault();
event.stopPropagation();
};
const handleDrop = (event: React.DragEvent) => {
event.preventDefault();
event.stopPropagation();
const file = event.dataTransfer.files?.[0];
if (!file || uploading) return;
// Check file type
const validTypes = ['.pdf', '.docx', '.txt'];
const fileExt = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));
if (!validTypes.includes(fileExt)) {
alert('请上传 PDF、DOCX 或 TXT 文件');
return;
}
// Simulate upload process
setUploading(true);
setUploadFileName(file.name);
setActiveStep(0);
setCompletedSteps([]);
const fileSizeMB = (file.size / (1024 * 1024)).toFixed(1);
const newDoc: Doc = {
id: Date.now(),
name: file.name,
chunks: 0,
size: `${fileSizeMB}MB`,
status: 'parsing',
};
setDocs(prev => [...prev, newDoc]);
const stepDuration = 600;
pipelineSteps.forEach((_, index) => {
setTimeout(() => {
setActiveStep(index);
setTimeout(() => {
setCompletedSteps(prev => [...prev, index]);
if (index === pipelineSteps.length - 1) {
setActiveStep(-1);
setUploading(false);
setUploadFileName('');
setDocs(prev => prev.map(d =>
d.id === newDoc.id ? { ...d, chunks: Math.floor(file.size / 8000) + 20, status: 'indexed' } : d
));
}
}, stepDuration - 100);
}, index * stepDuration);
});
};
const getStepStyle = (index: number) => {
const isActive = activeStep === index;
const isCompleted = completedSteps.includes(index);
const baseBg = theme.bgCard;
if (isActive) {
return {
background: theme.bgCard,
border: `2px solid ${theme.accent}`,
boxShadow: `0 0 12px ${theme.accent}40`,
};
}
if (isCompleted) {
return {
background: theme.bgCard,
border: `1px solid ${theme.green}`,
};
}
return {
background: baseBg,
border: `1px solid ${theme.border}`,
};
};
const getCheckStyle = (index: number) => {
const isActive = activeStep === index;
const isCompleted = completedSteps.includes(index);
if (isActive) {
return {
background: theme.gradientAccent,
color: '#fff',
animation: 'pulse 0.6s infinite',
};
}
if (isCompleted) {
return {
background: theme.green,
color: '#fff',
};
}
return {
background: theme.bgHover,
color: theme.text3,
};
};
return (
<Content>
@@ -19,16 +188,29 @@ export const DocsPage: React.FC = () => {
marginBottom: 20,
letterSpacing: '1px',
}}>UPLOAD</h2>
<div style={{
border: `2px solid ${theme.border}`,
borderRadius: 16,
padding: 64,
textAlign: 'center',
background: theme.bgCard,
transition: 'all 0.3s ease',
cursor: 'pointer',
boxShadow: !isDark ? '0 4px 16px rgba(226,0,116,0.08)' : 'none',
}}>
{/* Hidden file input */}
<input
ref={fileInputRef}
type="file"
accept=".pdf,.docx,.txt"
onChange={handleFileSelect}
style={{ display: 'none' }}
/>
<div
onClick={triggerFileUpload}
onDragOver={handleDragOver}
onDrop={handleDrop}
style={{
border: `2px solid ${uploading ? theme.accent : theme.border}`,
borderRadius: 16,
padding: 64,
textAlign: 'center',
background: theme.bgCard,
transition: 'all 0.3s ease',
cursor: uploading ? 'wait' : 'pointer',
boxShadow: !isDark ? '0 4px 16px rgba(226,0,116,0.08)' : 'none',
opacity: uploading ? 0.7 : 1,
}}>
<div style={{
width: 80,
height: 80,
@@ -39,13 +221,78 @@ export const DocsPage: React.FC = () => {
justifyContent: 'center',
margin: '0 auto 20px',
}}>
<svg width="36" height="36" viewBox="0 0 24 24" fill="none">
<path d="M12 4L12 16M12 4L7 9M12 4L17 9" stroke={theme.accent} strokeWidth="2" strokeLinecap="round"/>
<path d="M4 18H20" stroke={theme.accent} strokeWidth="2" strokeLinecap="round"/>
</svg>
{uploading ? (
<div style={{ animation: 'spin 1s linear infinite' }}>
<svg width="36" height="36" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" stroke={theme.accent} strokeWidth="2" strokeDasharray="60" strokeDashoffset="20"/>
</svg>
</div>
) : (
<svg width="36" height="36" viewBox="0 0 24 24" fill="none">
<path d="M12 4L12 16M12 4L7 9M12 4L17 9" stroke={theme.accent} strokeWidth="2" strokeLinecap="round"/>
<path d="M4 18H20" stroke={theme.accent} strokeWidth="2" strokeLinecap="round"/>
</svg>
)}
</div>
<div style={{ fontSize: 16, fontWeight: 500, marginBottom: 8 }}></div>
<div className="mono" style={{ fontSize: 12, color: theme.text3 }}>PDF · DOCX · TXT · MAX 50MB</div>
<div style={{ fontSize: 16, fontWeight: 500, marginBottom: 8 }}>
{uploading ? '正在上传...' : '拖拽文件或点击上传'}
</div>
<div className="mono" style={{ fontSize: 12, color: theme.text3 }}>
{uploading ? uploadFileName : 'PDF · DOCX · TXT · MAX 50MB'}
</div>
</div>
</section>
{/* Processing Pipeline */}
<section>
<h2 style={{
fontSize: 14,
fontWeight: 600,
color: theme.accent,
marginBottom: 20,
letterSpacing: '1px',
}}>PROCESSING PIPELINE</h2>
<div style={{ display: 'flex', gap: 16 }}>
{pipelineSteps.map((s, i) => {
const stepStyle = getStepStyle(i);
const checkStyle = getCheckStyle(i);
return (
<div key={i} style={{
flex: 1,
padding: 20,
textAlign: 'center',
borderRadius: 12,
position: 'relative',
boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none',
...stepStyle,
transition: 'all 0.3s ease',
}}>
<div style={{
width: 36,
height: 36,
borderRadius: 8,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 12px',
fontSize: 16,
...checkStyle,
transition: 'all 0.3s ease',
}}>
{activeStep === i ? '⋯' : '✓'}
</div>
<div className="mono" style={{ fontSize: 12, fontWeight: 600 }}>{s.name}</div>
{i < 4 && <div style={{
position: 'absolute',
right: -8,
top: '50%',
transform: 'translateY(-50%)',
color: completedSteps.includes(i) ? theme.green : theme.borderLight,
fontWeight: completedSteps.includes(i) ? 700 : 400,
}}></div>}
</div>
);
})}
</div>
</section>
@@ -57,9 +304,9 @@ export const DocsPage: React.FC = () => {
color: theme.accent,
marginBottom: 20,
letterSpacing: '1px',
}}>INDEXED DOCUMENTS ({mockDocs.length})</h2>
}}>INDEXED DOCUMENTS ({docs.length})</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{mockDocs.map(d => (
{docs.map(d => (
<div key={d.id} style={{
display: 'flex',
alignItems: 'center',
@@ -67,7 +314,7 @@ export const DocsPage: React.FC = () => {
padding: 20,
background: theme.bgCard,
borderRadius: 12,
border: `1px solid ${theme.border}`,
border: `1px solid ${d.status === 'parsing' ? theme.accent : theme.border}`,
transition: 'all 0.2s ease',
boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none',
}}>
@@ -98,77 +345,33 @@ export const DocsPage: React.FC = () => {
background: theme.bgHover,
borderRadius: 6,
color: theme.text2,
}}>{d.chunks} chunks</div>
}}>
{d.status === 'parsing' ? '处理中...' : `${d.chunks} chunks`}
</div>
<div style={{
width: 32,
height: 32,
borderRadius: 8,
background: theme.green,
background: d.status === 'indexed' ? theme.green : theme.bgHover,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M5 12L10 17L20 7" stroke="#fff" strokeWidth="2" strokeLinecap="round"/>
</svg>
{d.status === 'indexed' ? (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M5 12L10 17L20 7" stroke="#fff" strokeWidth="2" strokeLinecap="round"/>
</svg>
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="6" stroke={theme.accent} strokeWidth="2"/>
</svg>
)}
</div>
</div>
</div>
))}
</div>
</section>
{/* Processing Pipeline */}
<section>
<h2 style={{
fontSize: 14,
fontWeight: 600,
color: theme.accent,
marginBottom: 20,
letterSpacing: '1px',
}}>PROCESSING PIPELINE</h2>
<div style={{ display: 'flex', gap: 16 }}>
{[
{ name: 'LOAD' },
{ name: 'PARSE' },
{ name: 'CHUNK' },
{ name: 'EMBED' },
{ name: 'STORE' },
].map((s, i) => (
<div key={i} style={{
flex: 1,
padding: 20,
textAlign: 'center',
background: theme.bgCard,
borderRadius: 12,
border: `1px solid ${theme.border}`,
position: 'relative',
boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none',
}}>
<div style={{
width: 36,
height: 36,
borderRadius: 8,
background: theme.gradientAccent,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 12px',
fontSize: 16,
color: '#fff',
}}></div>
<div className="mono" style={{ fontSize: 12, fontWeight: 600 }}>{s.name}</div>
{i < 4 && <div style={{
position: 'absolute',
right: -8,
top: '50%',
transform: 'translateY(-50%)',
color: theme.borderLight,
}}></div>}
</div>
))}
</div>
</section>
</Content>
);
};

View File

@@ -8,25 +8,53 @@ const ragQuickQuestions = [
'驾驶证如何申请?',
'超速行驶如何处罚?',
'车辆年检有哪些规定?',
'电动汽车电池安全标准?',
'正面碰撞测试要求?',
'AEB系统测试标准',
'高速公路安全距离?',
];
const generateRagResponse = (question: string): { text: string; retrievalIds: number[] } => {
const keywords: Record<string, { text: string; retrievalIds: number[] }> = {
'电动自行车': {
text: '根据《道路交通安全法》及相关规范,电动自行车上路需满足以下条件:\n1. 符合国家标准 GB17761-2018\n2. 经公安机关交通管理部门登记\n3. 最高设计车速不超过 25km/h\n4. 整车质量不超过 55kg\n5. 具有脚踏骑行能力',
text: '根据《道路交通安全法》及相关规范,电动自行车上路需满足以下条件:\n\n1. 符合国家标准 GB17761-2018\n2. 经公安机关交通管理部门登记\n3. 最高设计车速不超过 25km/h\n4. 整车质量不超过 55kg\n5. 具有脚踏骑行能力\n6. 蓄电池标称电压不超过 48V\n\n行驶时还需佩戴安全头盔不得逆向行驶或在机动车道内行驶。',
retrievalIds: [1, 2, 3],
},
'驾驶证': {
text: '驾驶证申请流程如下:\n1. 到驾校报名并参加培训\n2. 通过科目一(理论考试)\n3. 通过科目二(场地驾驶技能考试)\n4. 通过科目三(道路驾驶技能考试)\n5. 通过科目四(安全文明驾驶常识考试)\n6. 领取驾驶证',
text: '驾驶证申请流程如下:\n\n1. 到驾校报名并参加培训\n2. 通过科目一(理论考试)\n3. 通过科目二(场地驾驶技能考试)\n4. 通过科目三(道路驾驶技能考试)\n5. 通过科目四(安全文明驾驶常识考试)\n6. 领取驾驶证\n\n初次申领需到住所地车辆管理所申请注册登记。',
retrievalIds: [1, 4],
},
'超速': {
text: '超速处罚标准\n- 超速10%以下:警告\n- 超速10%-20%罚款50-200元\n- 超速20%-50%罚款200-500元记3-6分\n- 超速50%以上罚款500-2000元记12分可吊销驾驶证',
retrievalIds: [1, 2],
text: '超速处罚标准(根据《道路交通安全法》):\n\n- 超速10%以下:警告\n- 超速10%-20%罚款50-200元\n- 超速20%-50%罚款200-500元记3-6分\n- 超速50%以上罚款500-2000元记12分可吊销驾驶证\n\n机动车驾驶人违反道路交通安全法律、法规将处警告或二十元以上二百元以下罚款。',
retrievalIds: [1, 7],
},
'年检': {
text: '车辆年检规定:\n- 小型私家车6年内免检每2年申领标志6-10年每2年检验10年以上每年检验\n- 车辆需携带行驶证、交强险保单\n- 检验项目:灯光、制动、排放等',
retrievalIds: [1, 4],
text: '车辆年检规定:\n\n- 小型私家车6年内免检每2年申领标志6-10年每2年检验10年以上每年检验\n- 车辆需携带行驶证、交强险保单\n- 检验项目:灯光、制动、排放等\n\n机动车所有人的住所迁出车辆管理所管辖区域的需在登记证书上签注变更事项。',
retrievalIds: [1, 4, 8],
},
'电池': {
text: '电动汽车电池安全标准GB 38031-2020\n\n1. 热失控要求电池系统发生热失控后应在5分钟内不起火不爆炸为乘员预留逃生时间\n2. 电池包需通过针刺、过充、短路等安全测试\n3. 充电系统应具备过充保护功能当电池SOC达到100%时应自动停止充电\n4. 充电接口应符合GB/T 18487.1标准要求\n\n以上要求确保电动汽车的整车安全性。',
retrievalIds: [5, 9],
},
'碰撞': {
text: '正面碰撞测试要求C-NCAP管理规则\n\n1. 正面100%重叠刚性壁障碰撞试验\n2. 碰撞速度50km/h\n3. 试验后要求:\n - 车门应能打开\n - 燃油系统无泄漏\n - 座椅及安全带功能正常\n\n此测试用于评估车辆在正面碰撞事故中对乘员的保护能力。',
retrievalIds: [6, 10],
},
'AEB': {
text: 'AEB自动紧急制动系统测试标准\n\n1. 系统应在检测到前方障碍物时主动减速或停车\n2. 测试场景分为三种:\n - 目标车静止\n - 目标车移动\n - 目标车制动\n3. AEB功能是C-NCAP评分的重要加分项\n\n该系统对提升车辆主动安全性能具有重要意义。',
retrievalIds: [10],
},
'高速公路': {
text: '高速公路安全距离规定:\n\n1. 车速超过100km/h时与同车道前车保持100米以上距离\n2. 车速低于100km/h时距离可适当缩短\n3. 执行紧急任务的警车、消防车、救护车、工程救险车不受行驶速度限制\n\n保持安全距离是预防追尾事故的关键措施。',
retrievalIds: [11, 12],
},
'充电': {
text: '电动汽车充电安全要求:\n\n1. 充电系统应具备过充保护功能\n2. 电池SOC达到100%时应自动停止充电\n3. 充电接口应符合GB/T 18487.1标准\n4. 需具备温度监控和通信协议符合GB/T 27930\n\n快充30分钟充至80%符合行业标准,但需确保循环寿命测试数据完整。',
retrievalIds: [9, 5],
},
'安全': {
text: '车辆安全配置要求:\n\n1. 电池安全热失控后5分钟内不起火不爆炸GB 38031-2020\n2. 碰撞安全正面碰撞后车门能打开燃油无泄漏C-NCAP\n3. 主动安全AEB系统应能检测障碍物并主动减速\n4. 安全气囊乘用车需配置符合GB 27887-2011的安全气囊\n\n以上配置确保车辆在各类场景下的乘员安全。',
retrievalIds: [5, 6, 10],
},
};
@@ -37,7 +65,7 @@ const generateRagResponse = (question: string): { text: string; retrievalIds: nu
}
return {
text: '抱歉,暂未找到与您问题直接相关的法规内容。请尝试更具体的问题,或联系交通管理部门获取详细信息。',
text: '抱歉,暂未找到与您问题直接相关的法规内容。请尝试更具体的问题,或联系交通管理部门获取详细信息。\n\n您可以尝试询问电动自行车、驾驶证、超速处罚、年检、电池安全、碰撞测试、AEB系统、高速公路规则等话题。',
retrievalIds: [],
};
};

View File

@@ -38,6 +38,9 @@ const StatsCard = ({ label, value, accent = false }: {
export const StatusPage: React.FC = () => {
const { theme, isDark } = useTheme();
// 计算总chunks
const totalChunks = mockDocs.reduce((sum, d) => sum + d.chunks, 0);
return (
<Content>
<TPattern />
@@ -48,10 +51,10 @@ export const StatusPage: React.FC = () => {
gridTemplateColumns: 'repeat(4, 1fr)',
gap: 16,
}}>
<StatsCard label="DOCUMENTS" value={3} />
<StatsCard label="CHUNKS" value={214} />
<StatsCard label="DOCUMENTS" value={mockDocs.length} />
<StatsCard label="CHUNKS" value={totalChunks} />
<StatsCard label="DIMENSIONS" value={1536} />
<StatsCard label="CLAUSES" value={214} accent />
<StatsCard label="CLAUSES" value={totalChunks} accent />
</div>
</section>
@@ -64,35 +67,125 @@ export const StatusPage: React.FC = () => {
marginBottom: 20,
letterSpacing: '1px',
}}>SYSTEM CONFIGURATION</h2>
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 12,
}}>
{[
['LLM Model', 'GPT-4o'],
['Embedding', 'text-embedding-3-small'],
['Vector DB', 'ChromaDB'],
['Retrieval', 'Hybrid (Vector + BM25)'],
['Top-K', '5 candidates'],
['Chunk Size', '500-1000 tokens'],
['Overlap', '100 tokens'],
['Temperature', '0.1'],
].map(([k, v]) => (
<div key={k} style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
background: theme.bgCard,
borderRadius: 10,
border: `1px solid ${theme.border}`,
boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none',
}}>
<span className="mono" style={{ fontSize: 12, color: theme.text3 }}>{k}</span>
<span style={{ fontSize: 14, fontWeight: 500 }}>{v}</span>
</div>
))}
{/* ChromaDB Config */}
<div style={{ marginBottom: 20 }}>
<div className="mono" style={{ fontSize: 11, color: theme.text3, marginBottom: 12, letterSpacing: '1px' }}>VECTOR DATABASE</div>
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
gap: 12,
}}>
{[
['Vector DB', 'Milvus'],
['Host', 'localhost'],
['Port', '19530'],
].map(([k, v]) => (
<div key={k} style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
background: theme.bgCard,
borderRadius: 10,
border: `1px solid ${theme.border}`,
boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none',
}}>
<span className="mono" style={{ fontSize: 12, color: theme.text3 }}>{k}</span>
<span style={{ fontSize: 14, fontWeight: 500 }}>{v}</span>
</div>
))}
</div>
</div>
{/* LLM Config */}
<div style={{ marginBottom: 20 }}>
<div className="mono" style={{ fontSize: 11, color: theme.text3, marginBottom: 12, letterSpacing: '1px' }}>LLM CONFIGURATION</div>
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 12,
}}>
{[
['LLM Model', 'qwen-max'],
['Embedding Model', 'text-embedding-v3'],
['Embedding Dim', '1536'],
['Temperature', '0.1'],
].map(([k, v]) => (
<div key={k} style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
background: theme.bgCard,
borderRadius: 10,
border: `1px solid ${theme.border}`,
boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none',
}}>
<span className="mono" style={{ fontSize: 12, color: theme.text3 }}>{k}</span>
<span style={{ fontSize: 14, fontWeight: 500 }}>{v}</span>
</div>
))}
</div>
</div>
{/* Retrieval Config */}
<div style={{ marginBottom: 20 }}>
<div className="mono" style={{ fontSize: 11, color: theme.text3, marginBottom: 12, letterSpacing: '1px' }}>RETRIEVAL CONFIGURATION (HYBRID)</div>
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
gap: 12,
}}>
{[
['Vector Top-K', '10'],
['BM25 Top-K', '10'],
['Final Top-K', '5'],
].map(([k, v]) => (
<div key={k} style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
background: theme.bgCard,
borderRadius: 10,
border: `1px solid ${theme.border}`,
boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none',
}}>
<span className="mono" style={{ fontSize: 12, color: theme.text3 }}>{k}</span>
<span style={{ fontSize: 14, fontWeight: 500 }}>{v}</span>
</div>
))}
</div>
</div>
{/* Chunk Config */}
<div>
<div className="mono" style={{ fontSize: 11, color: theme.text3, marginBottom: 12, letterSpacing: '1px' }}>CHUNK CONFIGURATION</div>
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 12,
}}>
{[
['Chunk Size', '800'],
['Chunk Overlap', '100'],
].map(([k, v]) => (
<div key={k} style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
background: theme.bgCard,
borderRadius: 10,
border: `1px solid ${theme.border}`,
boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none',
}}>
<span className="mono" style={{ fontSize: 12, color: theme.text3 }}>{k}</span>
<span style={{ fontSize: 14, fontWeight: 500 }}>{v}</span>
</div>
))}
</div>
</div>
</section>

View File

@@ -232,6 +232,36 @@ button, input {
animation: slideOut 0.3s ease-in forwards;
}
/* Pulse animation for processing steps */
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.8;
transform: scale(0.95);
}
}
.animate-pulse {
animation: pulse 0.6s ease-in-out infinite;
}
/* Spin animation for loading icons */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: spin 1s linear infinite;
}
/* Custom utility classes */
@layer utilities {
.gradient-accent {

10
start.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
# 启动开发服务器
# 端口: 8000
# 监听: 0.0.0.0
cd "$(dirname "$0")"
echo "Starting dev server on http://0.0.0.0:8001"
npx vite --host 0.0.0.0 --port 8001