Files
AIRegulation-DocAnalysis/frontend/index.html

1150 lines
37 KiB
HTML
Raw Normal View History

2026-04-28 11:29:33 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI+合规智能中枢 - 文档测试平台</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg-primary: #0a0f1a;
--bg-secondary: #121a2e;
--bg-card: #1a2438;
--bg-hover: #243050;
--text-primary: #e8edf5;
--text-secondary: #8a9bb0;
--text-muted: #5a6b80;
--accent-gold: #d4a54a;
--accent-gold-dim: #8a6d2a;
--accent-cyan: #3dd9d0;
--accent-coral: #e85555;
--border-subtle: #2a3a52;
--border-glow: #3dd9d040;
--shadow-deep: 0 8px 32px rgba(0, 0, 0, 0.4);
--radius-sm: 6px;
--radius-md: 12px;
--radius-lg: 20px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Noto Sans SC', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
line-height: 1.6;
}
/* Background atmosphere */
.bg-layer {
position: fixed;
inset: 0;
z-index: -1;
background:
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(212, 165, 74, 0.08), transparent),
radial-gradient(ellipse 60% 40% at 100% 100%, rgba(61, 217, 208, 0.05), transparent),
linear-gradient(180deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
}
/* Grid pattern overlay */
.grid-pattern {
position: fixed;
inset: 0;
z-index: -1;
opacity: 0.03;
background-image:
linear-gradient(var(--border-subtle) 1px, transparent 1px),
linear-gradient(90deg, var(--border-subtle) 1px, transparent 1px);
background-size: 60px 60px;
}
/* Header */
.header {
padding: 24px 40px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border-subtle);
backdrop-filter: blur(12px);
position: sticky;
top: 0;
z-index: 100;
}
.logo {
display: flex;
align-items: center;
gap: 16px;
}
.logo-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--accent-gold), var(--accent-gold-dim));
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
font-size: 20px;
color: var(--bg-primary);
}
.logo-text {
font-family: 'JetBrains Mono', monospace;
font-size: 18px;
font-weight: 600;
letter-spacing: 2px;
}
.logo-sub {
font-size: 11px;
color: var(--text-muted);
letter-spacing: 1px;
}
/* Status indicator */
.status-group {
display: flex;
align-items: center;
gap: 24px;
}
.status-item {
display: flex;
align-items: center;
gap: 8px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
position: relative;
}
.status-dot.online {
background: var(--accent-cyan);
animation: pulse 2s infinite;
}
.status-dot.offline {
background: var(--accent-coral);
}
.status-dot.loading {
background: var(--accent-gold);
animation: blink 1s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(61, 217, 208, 0.4); }
50% { opacity: 0.8; box-shadow: 0 0 0 8px rgba(61, 217, 208, 0); }
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.status-label {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--text-secondary);
letter-spacing: 1px;
}
/* Main layout */
.main {
max-width: 1200px;
margin: 0 auto;
padding: 40px;
}
/* Section cards */
.section {
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: 32px;
margin-bottom: 32px;
position: relative;
overflow: hidden;
}
.section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, var(--accent-gold), transparent 60%);
}
.section-title {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
font-weight: 600;
color: var(--accent-gold);
letter-spacing: 3px;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 12px;
}
.section-title::before {
content: '';
width: 24px;
height: 2px;
background: var(--accent-gold);
}
/* Upload zone */
.upload-zone {
border: 2px dashed var(--border-subtle);
border-radius: var(--radius-md);
padding: 48px;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
background: var(--bg-secondary);
}
.upload-zone:hover,
.upload-zone.dragover {
border-color: var(--accent-gold);
background: var(--bg-hover);
}
.upload-zone.dragover {
transform: scale(1.02);
}
.upload-zone.dragover::after {
content: '';
position: absolute;
inset: 0;
background: rgba(212, 165, 74, 0.1);
border-radius: var(--radius-md);
}
.upload-icon {
width: 64px;
height: 64px;
margin: 0 auto 20px;
opacity: 0.6;
}
.upload-icon svg {
width: 100%;
height: 100%;
stroke: var(--text-secondary);
}
.upload-text {
font-size: 16px;
color: var(--text-secondary);
margin-bottom: 8px;
}
.upload-hint {
font-size: 13px;
color: var(--text-muted);
}
.upload-types {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 24px;
}
.type-tag {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
padding: 6px 12px;
background: var(--bg-primary);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
color: var(--text-muted);
}
/* Form fields */
.form-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin-top: 24px;
}
.form-field {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-label {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--text-muted);
letter-spacing: 1px;
}
.form-input {
background: var(--bg-primary);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
padding: 12px 16px;
color: var(--text-primary);
font-size: 14px;
transition: border-color 0.2s;
}
.form-input:focus {
outline: none;
border-color: var(--accent-gold);
}
/* Buttons */
.btn {
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
font-weight: 600;
padding: 14px 28px;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
transition: all 0.2s ease;
letter-spacing: 1px;
display: inline-flex;
align-items: center;
gap: 10px;
}
.btn-primary {
background: linear-gradient(135deg, var(--accent-gold), var(--accent-gold-dim));
color: var(--bg-primary);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(212, 165, 74, 0.3);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: var(--bg-primary);
color: var(--text-secondary);
border: 1px solid var(--border-subtle);
}
.btn-secondary:hover {
border-color: var(--text-secondary);
}
/* Pipeline visualization */
.pipeline {
display: flex;
justify-content: center;
gap: 8px;
margin: 32px 0;
opacity: 0;
transition: opacity 0.3s;
}
.pipeline.active {
opacity: 1;
}
.pipeline-step {
display: flex;
align-items: center;
gap: 8px;
}
.step-node {
width: 48px;
height: 48px;
border-radius: var(--radius-sm);
background: var(--bg-primary);
border: 2px solid var(--border-subtle);
display: flex;
align-items: center;
justify-content: center;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--text-muted);
transition: all 0.3s;
}
.step-node.active {
border-color: var(--accent-gold);
color: var(--accent-gold);
background: rgba(212, 165, 74, 0.1);
}
.step-node.done {
border-color: var(--accent-cyan);
color: var(--accent-cyan);
background: rgba(61, 217, 208, 0.1);
}
.step-connector {
width: 24px;
height: 2px;
background: var(--border-subtle);
position: relative;
}
.step-connector.active {
background: var(--accent-gold);
}
.step-connector.active::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 6px;
height: 6px;
background: var(--accent-gold);
border-radius: 50%;
animation: flowRight 1s infinite;
}
@keyframes flowRight {
0% { opacity: 0; transform: translateY(-50%) translateX(-12px); }
50% { opacity: 1; }
100% { opacity: 0; transform: translateY(-50%) translateX(0); }
}
/* Results */
.result-panel {
background: var(--bg-secondary);
border-radius: var(--radius-md);
padding: 24px;
margin-top: 24px;
display: none;
}
.result-panel.show {
display: block;
animation: slideUp 0.4s ease;
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.result-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.result-icon {
width: 32px;
height: 32px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
}
.result-icon.success {
background: rgba(61, 217, 208, 0.2);
color: var(--accent-cyan);
}
.result-icon.error {
background: rgba(232, 85, 85, 0.2);
color: var(--accent-coral);
}
.result-title {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
}
.result-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.stat-item {
background: var(--bg-primary);
border-radius: var(--radius-sm);
padding: 16px;
}
.stat-label {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--text-muted);
letter-spacing: 1px;
margin-bottom: 6px;
}
.stat-value {
font-family: 'JetBrains Mono', monospace;
font-size: 18px;
color: var(--accent-cyan);
}
/* Search section */
.search-input-group {
display: flex;
gap: 16px;
}
.search-input {
flex: 1;
background: var(--bg-primary);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
padding: 14px 20px;
color: var(--text-primary);
font-size: 14px;
}
.search-input:focus {
outline: none;
border-color: var(--accent-gold);
}
.search-input::placeholder {
color: var(--text-muted);
}
/* Search results */
.search-results {
margin-top: 24px;
}
.result-card {
background: var(--bg-secondary);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 20px;
margin-bottom: 12px;
transition: all 0.2s;
cursor: pointer;
}
.result-card:hover {
border-color: var(--accent-gold);
background: var(--bg-hover);
}
.result-meta {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.score-bar {
width: 80px;
height: 6px;
background: var(--bg-primary);
border-radius: 3px;
overflow: hidden;
}
.score-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent-gold), var(--accent-cyan));
border-radius: 3px;
}
.score-value {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--accent-gold);
}
.doc-name {
font-size: 12px;
color: var(--text-muted);
}
.result-content {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.8;
}
.result-footer {
display: flex;
gap: 16px;
margin-top: 12px;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--text-muted);
}
/* Empty state */
.empty-state {
text-align: center;
padding: 48px;
color: var(--text-muted);
}
.empty-icon {
width: 48px;
height: 48px;
margin: 0 auto 16px;
opacity: 0.4;
}
/* File info */
.file-info {
display: none;
background: var(--bg-secondary);
border-radius: var(--radius-md);
padding: 16px 20px;
margin-top: 16px;
align-items: center;
gap: 16px;
}
.file-info.show {
display: flex;
}
.file-icon {
width: 40px;
height: 40px;
background: var(--bg-primary);
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
font-weight: 600;
color: var(--accent-gold);
}
.file-details {
flex: 1;
}
.file-name {
font-size: 14px;
color: var(--text-primary);
margin-bottom: 4px;
}
.file-size {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--text-muted);
}
.file-remove {
width: 32px;
height: 32px;
border-radius: var(--radius-sm);
background: transparent;
border: 1px solid var(--border-subtle);
color: var(--text-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.file-remove:hover {
border-color: var(--accent-coral);
color: var(--accent-coral);
}
/* Responsive */
@media (max-width: 768px) {
.main {
padding: 20px;
}
.form-row {
grid-template-columns: 1fr;
}
.result-stats {
grid-template-columns: repeat(2, 1fr);
}
.header {
padding: 16px 20px;
}
.logo-text {
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="bg-layer"></div>
<div class="grid-pattern"></div>
<!-- Header -->
<header class="header">
<div class="logo">
<div class="logo-icon">AI</div>
<div>
<div class="logo-text">合规智能中枢</div>
<div class="logo-sub">COMPLIANCE INTELLIGENCE HUB</div>
</div>
</div>
<div class="status-group">
<div class="status-item">
<div class="status-dot" id="apiStatus"></div>
<span class="status-label">API</span>
</div>
<div class="status-item">
<div class="status-dot" id="milvusStatus"></div>
<span class="status-label">MILVUS</span>
</div>
</div>
</header>
<!-- Main content -->
<main class="main">
<!-- Upload section -->
<section class="section">
<div class="section-title">文档上传测试</div>
<div class="upload-zone" id="uploadZone">
<div class="upload-icon">
<svg viewBox="0 0 24 24" fill="none" stroke-width="1.5">
<path d="M12 16V4m0 0L8 8m4-4l4 4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20 16v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2" stroke-linecap="round"/>
</svg>
</div>
<div class="upload-text">拖拽文件到此处,或点击选择</div>
<div class="upload-hint">支持上传法规文档进行智能解析</div>
<div class="upload-types">
<span class="type-tag">.PDF</span>
<span class="type-tag">.DOCX</span>
<span class="type-tag">.DOC</span>
</div>
<input type="file" id="fileInput" accept=".pdf,.docx,.doc" hidden>
</div>
<div class="file-info" id="fileInfo">
<div class="file-icon" id="fileExt">PDF</div>
<div class="file-details">
<div class="file-name" id="fileName">-</div>
<div class="file-size" id="fileSize">-</div>
</div>
<button class="file-remove" id="fileRemove">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="form-row">
<div class="form-field">
<label class="form-label">文档名称</label>
<input type="text" class="form-input" id="docName" placeholder="可选,默认为文件名">
</div>
<div class="form-field">
<label class="form-label">法规类型</label>
<input type="text" class="form-input" id="regType" placeholder="如:车辆安全、数据安全">
</div>
<div class="form-field">
<label class="form-label">文档版本</label>
<input type="text" class="form-input" id="docVersion" placeholder="如GB/T 12345-2024">
</div>
</div>
<!-- Pipeline visualization -->
<div class="pipeline" id="pipeline">
<div class="pipeline-step">
<div class="step-node" id="step1">解析</div>
<div class="step-connector" id="conn1"></div>
</div>
<div class="pipeline-step">
<div class="step-node" id="step2">分块</div>
<div class="step-connector" id="conn2"></div>
</div>
<div class="pipeline-step">
<div class="step-node" id="step3">嵌入</div>
<div class="step-connector" id="conn3"></div>
</div>
<div class="pipeline-step">
<div class="step-node" id="step4">入库</div>
</div>
</div>
<div style="display: flex; gap: 16px; justify-content: center; margin-top: 24px;">
<button class="btn btn-primary" id="uploadBtn" disabled>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 2L11 13M22 2l-7 20-4-4-4 4-7-20 15 7 7-7z" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
开始处理
</button>
<button class="btn btn-secondary" id="clearBtn">
清除结果
</button>
</div>
<div class="result-panel" id="uploadResult">
<div class="result-header">
<div class="result-icon success" id="resultIcon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 6L9 17l-5-5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="result-title" id="resultTitle">处理成功</div>
</div>
<div class="result-stats">
<div class="stat-item">
<div class="stat-label">DOC ID</div>
<div class="stat-value" id="statDocId">-</div>
</div>
<div class="stat-item">
<div class="stat-label">CHUNKS</div>
<div class="stat-value" id="statChunks">-</div>
</div>
<div class="stat-item">
<div class="stat-label">STATUS</div>
<div class="stat-value" id="statStatus">-</div>
</div>
<div class="stat-item">
<div class="stat-label">TIME</div>
<div class="stat-value" id="statTime">-</div>
</div>
</div>
</div>
</section>
<!-- Search section -->
<section class="section">
<div class="section-title">法规检索测试</div>
<div class="search-input-group">
<input type="text" class="search-input" id="searchQuery" placeholder="输入检索关键词,如:机动车安全标准、数据合规要求...">
<button class="btn btn-primary" id="searchBtn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/>
<path d="M21 21l-4.35-4.35" stroke-linecap="round"/>
</svg>
检索
</button>
</div>
<div class="search-results" id="searchResults">
<div class="empty-state" id="searchEmpty">
<div class="empty-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<circle cx="11" cy="11" r="8"/>
<path d="M21 21l-4.35-4.35" stroke-linecap="round"/>
</svg>
</div>
<div>请输入关键词检索法规内容</div>
</div>
</div>
</section>
</main>
<script>
// 动态获取API地址使用当前页面的host
// 前端和API在同一服务器时自动适配
const API_HOST = window.location.hostname;
const API_URL = `http://${API_HOST}:8000`;
// Elements
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
const fileInfo = document.getElementById('fileInfo');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const fileExt = document.getElementById('fileExt');
const fileRemove = document.getElementById('fileRemove');
const uploadBtn = document.getElementById('uploadBtn');
const clearBtn = document.getElementById('clearBtn');
const pipeline = document.getElementById('pipeline');
const uploadResult = document.getElementById('uploadResult');
const searchQuery = document.getElementById('searchQuery');
const searchBtn = document.getElementById('searchBtn');
const searchResults = document.getElementById('searchResults');
const searchEmpty = document.getElementById('searchEmpty');
// Status elements
const apiStatus = document.getElementById('apiStatus');
const milvusStatus = document.getElementById('milvusStatus');
let selectedFile = null;
// Check API health
async function checkHealth() {
apiStatus.className = 'status-dot loading';
try {
const res = await fetch(`${API_URL}/health`);
const data = await res.json();
apiStatus.className = 'status-dot online';
// Check Milvus status from health response
if (data.milvus && data.milvus.connected) {
milvusStatus.className = 'status-dot online';
} else {
milvusStatus.className = 'status-dot offline';
}
} catch (e) {
apiStatus.className = 'status-dot offline';
milvusStatus.className = 'status-dot offline';
}
}
// Initial health check
checkHealth();
setInterval(checkHealth, 30000);
// Upload zone interactions
uploadZone.addEventListener('click', () => fileInput.click());
uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
uploadZone.classList.add('dragover');
});
uploadZone.addEventListener('dragleave', () => {
uploadZone.classList.remove('dragover');
});
uploadZone.addEventListener('drop', (e) => {
e.preventDefault();
uploadZone.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
function handleFile(file) {
const ext = file.name.split('.').pop().toUpperCase();
if (!['PDF', 'DOCX', 'DOC'].includes(ext)) {
alert('不支持的文件类型,请上传 PDF、DOCX 或 DOC 文件');
return;
}
selectedFile = file;
fileName.textContent = file.name;
fileSize.textContent = formatSize(file.size);
fileExt.textContent = ext;
fileInfo.classList.add('show');
uploadBtn.disabled = false;
}
function formatSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
fileRemove.addEventListener('click', () => {
selectedFile = null;
fileInput.value = '';
fileInfo.classList.remove('show');
uploadBtn.disabled = true;
});
// Upload document
uploadBtn.addEventListener('click', async () => {
if (!selectedFile) return;
uploadBtn.disabled = true;
uploadResult.classList.remove('show');
pipeline.classList.add('active');
const startTime = Date.now();
// Animate pipeline steps
const steps = ['step1', 'step2', 'step3', 'step4'];
const conns = ['conn1', 'conn2', 'conn3'];
for (let i = 0; i < steps.length; i++) {
document.getElementById(steps[i]).classList.add('active');
if (i < conns.length) {
document.getElementById(conns[i]).classList.add('active');
}
await new Promise(r => setTimeout(r, 500));
}
try {
const formData = new FormData();
formData.append('file', selectedFile);
formData.append('doc_name', document.getElementById('docName').value || selectedFile.name);
formData.append('regulation_type', document.getElementById('regType').value || '');
formData.append('version', document.getElementById('docVersion').value || '');
const res = await fetch(`${API_URL}/api/v1/documents/upload`, {
method: 'POST',
body: formData
});
const data = await res.json();
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
// Mark all steps as done
steps.forEach(id => {
document.getElementById(id).classList.remove('active');
document.getElementById(id).classList.add('done');
});
conns.forEach(id => document.getElementById(id).classList.remove('active'));
if (res.ok && data.status === 'success') {
document.getElementById('resultIcon').className = 'result-icon success';
document.getElementById('resultIcon').innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
document.getElementById('resultTitle').textContent = '处理成功';
document.getElementById('statDocId').textContent = data.doc_id;
document.getElementById('statChunks').textContent = data.num_chunks || 0;
document.getElementById('statStatus').textContent = 'SUCCESS';
document.getElementById('statTime').textContent = elapsed + 's';
} else {
document.getElementById('resultIcon').className = 'result-icon error';
document.getElementById('resultIcon').innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/></svg>';
document.getElementById('resultTitle').textContent = '处理失败';
document.getElementById('statDocId').textContent = '-';
document.getElementById('statChunks').textContent = '-';
document.getElementById('statStatus').textContent = 'ERROR';
document.getElementById('statTime').textContent = elapsed + 's';
}
uploadResult.classList.add('show');
} catch (e) {
document.getElementById('resultIcon').className = 'result-icon error';
document.getElementById('resultTitle').textContent = '请求失败: ' + e.message;
uploadResult.classList.add('show');
}
uploadBtn.disabled = false;
});
// Clear results
clearBtn.addEventListener('click', () => {
uploadResult.classList.remove('show');
pipeline.classList.remove('active');
['step1', 'step2', 'step3', 'step4'].forEach(id => {
document.getElementById(id).classList.remove('active', 'done');
});
['conn1', 'conn2', 'conn3'].forEach(id => {
document.getElementById(id).classList.remove('active');
});
});
// Search
searchBtn.addEventListener('click', async () => {
const query = searchQuery.value.trim();
if (!query) {
alert('请输入检索关键词');
return;
}
searchBtn.disabled = true;
searchEmpty.style.display = 'none';
try {
const res = await fetch(`${API_URL}/api/v1/knowledge/search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, top_k: 10 })
});
const data = await res.json();
// Clear previous results
searchResults.innerHTML = '';
if (data.results && data.results.length > 0) {
data.results.forEach(item => {
const scorePercent = Math.round(item.score * 100);
const card = document.createElement('div');
card.className = 'result-card';
card.innerHTML = `
<div class="result-meta">
<div class="score-bar">
<div class="score-fill" style="width: ${scorePercent}%"></div>
</div>
<span class="score-value">${scorePercent}%</span>
<span class="doc-name">${item.metadata.doc_name || '-'}</span>
</div>
<div class="result-content">${item.content.substring(0, 300)}${item.content.length > 300 ? '...' : ''}</div>
<div class="result-footer">
<span>ID: ${item.id}</span>
<span>章节: ${item.metadata.section_title || '-'}</span>
<span>条款: ${item.metadata.clause_number || '-'}</span>
<span>页码: ${item.metadata.page_number || '-'}</span>
</div>
`;
searchResults.appendChild(card);
});
} else {
searchResults.innerHTML = `
<div class="empty-state">
<div class="empty-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M9 12h6M12 9v6" stroke-linecap="round"/>
<circle cx="12" cy="12" r="10"/>
</svg>
</div>
<div>未找到相关法规内容</div>
</div>
`;
}
} catch (e) {
searchResults.innerHTML = `
<div class="empty-state">
<div style="color: var(--accent-coral);">检索失败: ${e.message}</div>
</div>
`;
}
searchBtn.disabled = false;
});
// Enter key for search
searchQuery.addEventListener('keypress', (e) => {
if (e.key === 'Enter') searchBtn.click();
});
</script>
</body>
</html>