Files
llm-index-rag/static/js/main.js
coder 8c7a99d83f feat: 添加配置管理功能和修复搜索问题
新增:
- 系统设置页面 (/settings) - 支持动态配置LLM、索引、文档处理参数
- 配置API - 保存配置、测试LLM连接
- 前端JS交互文件 - 搜索、文档管理功能

修复:
- 首页搜索框无法正常工作的问题(缺少main.js)
- 服务支持动态读取配置(无需重启生效)

改进:
- LLM/索引/文档配置支持热更新
- 添加测试LLM连接功能
2026-04-09 12:54:31 +08:00

247 lines
8.9 KiB
JavaScript

/**
* LLM Index RAG - 前端交互脚本
*/
// 搜索表单处理
document.getElementById('searchForm')?.addEventListener('submit', async function(e) {
e.preventDefault();
const query = document.getElementById('queryInput').value.trim();
const mode = document.querySelector('input[name="mode"]:checked').value;
if (!query) {
alert('请输入查询内容');
return;
}
// 显示加载状态
const resultsSection = document.getElementById('resultsSection');
const ragSection = document.getElementById('ragSection');
const resultsContainer = document.getElementById('resultsContainer');
const ragAnswer = document.getElementById('ragAnswer');
const ragSources = document.getElementById('ragSources');
const resultCount = document.getElementById('resultCount');
resultsSection.style.display = 'none';
ragSection.style.display = 'none';
resultsContainer.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-primary"></div><p class="mt-2">正在检索...</p></div>';
resultsSection.style.display = 'block';
try {
if (mode === 'search') {
// 文档检索模式
const response = await fetch('/api/search', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({query: query, top_k: 10})
});
const data = await response.json();
if (data.error) {
resultsContainer.innerHTML = `<div class="alert alert-danger">${data.error}</div>`;
return;
}
resultCount.textContent = data.total;
if (data.results && data.results.length > 0) {
resultsContainer.innerHTML = data.results.map(r => `
<div class="result-item">
<h6 class="mb-1">
<a href="/documents" class="text-decoration-none">${r.title || r.document_title || '文档'}</a>
</h6>
<p class="text-muted small mb-1">${r.summary || r.content?.substring(0, 200) + '...' || ''}</p>
<div class="d-flex gap-2 align-items-center">
<span class="source-tag">${r.source || '本地文档'}</span>
<span class="badge bg-primary">${(r.score * 100).toFixed(1)}%</span>
</div>
</div>
`).join('');
} else {
resultsContainer.innerHTML = `
<div class="text-center py-4 text-muted">
<i class="bi bi-search display-4"></i>
<p class="mt-2">未找到相关结果</p>
<p class="small">请尝试其他关键词,或先上传并索引文档</p>
</div>
`;
}
} else {
// RAG问答模式
resultsContainer.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-primary"></div><p class="mt-2">正在生成回答...</p></div>';
const response = await fetch('/api/rag/answer', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({query: query, top_k: 5})
});
const data = await response.json();
if (data.error) {
resultsContainer.innerHTML = `<div class="alert alert-danger">${data.error}</div>`;
return;
}
resultsSection.style.display = 'none';
ragSection.style.display = 'block';
// 显示回答
ragAnswer.innerHTML = `
<div class="d-flex align-items-start gap-3">
<div class="bg-primary text-white rounded-circle p-2">
<i class="bi bi-robot"></i>
</div>
<div class="flex-grow-1">
<div class="markdown-content">${data.answer || '抱歉,无法生成回答。'}</div>
</div>
</div>
`;
// 显示来源
if (data.sources && data.sources.length > 0) {
ragSources.innerHTML = data.sources.map(s => `
<div class="result-item">
<h6 class="mb-1">${s.title || s.document_title || '参考文档'}</h6>
<p class="text-muted small mb-0">${s.content?.substring(0, 150) + '...' || ''}</p>
</div>
`).join('');
} else {
ragSources.innerHTML = '<p class="text-muted">无参考来源</p>';
}
}
} catch (err) {
resultsContainer.innerHTML = `<div class="alert alert-danger">请求失败: ${err.message}</div>`;
}
});
// 文档上传
document.getElementById('uploadForm')?.addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const fileInput = document.getElementById('fileInput');
if (!fileInput.files.length) {
alert('请选择文件');
return;
}
const uploadBtn = document.getElementById('uploadBtn');
uploadBtn.disabled = true;
uploadBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> 上传中...';
try {
const response = await fetch('/api/documents', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
alert('上传成功!');
location.reload();
} else {
alert('上传失败: ' + (data.error || '未知错误'));
}
} catch (err) {
alert('上传失败: ' + err.message);
} finally {
uploadBtn.disabled = false;
uploadBtn.innerHTML = '<i class="bi bi-upload"></i> 上传';
}
});
// 索引文档
async function indexDocument(docId) {
if (!confirm('确定要索引此文档吗?这可能需要一些时间。')) return;
try {
const response = await fetch(`/api/index/${docId}`, {method: 'POST'});
const data = await response.json();
if (data.success) {
alert('索引完成!');
location.reload();
} else {
alert('索引失败: ' + (data.error || '未知错误'));
}
} catch (err) {
alert('索引失败: ' + err.message);
}
}
// 删除文档
async function deleteDocument(docId) {
if (!confirm('确定要删除此文档吗?此操作不可恢复。')) return;
try {
const response = await fetch(`/api/documents/${docId}`, {method: 'DELETE'});
const data = await response.json();
if (data.success) {
alert('删除成功!');
location.reload();
} else {
alert('删除失败: ' + (data.error || '未知错误'));
}
} catch (err) {
alert('删除失败: ' + err.message);
}
}
// 批量索引
async function batchIndex() {
if (!confirm('确定要索引所有待处理文档吗?')) return;
try {
const response = await fetch('/api/index/batch', {method: 'POST'});
const data = await response.json();
alert(`索引完成!成功: ${data.success}, 失败: ${data.failed}`);
location.reload();
} catch (err) {
alert('批量索引失败: ' + err.message);
}
}
// 重建索引
async function rebuildIndex() {
if (!confirm('重建索引将清除所有现有索引,确定继续吗?')) return;
try {
const response = await fetch('/api/index/rebuild', {method: 'POST'});
const data = await response.json();
alert(`重建完成!成功: ${data.success}, 失败: ${data.failed}`);
location.reload();
} catch (err) {
alert('重建索引失败: ' + err.message);
}
}
// 加载统计信息
async function loadStats() {
try {
const response = await fetch('/api/stats');
const stats = await response.json();
document.getElementById('statDocs').textContent = stats.total_documents || 0;
document.getElementById('statChunks').textContent = stats.total_chunks || 0;
document.getElementById('statTerms').textContent = stats.total_terms || 0;
document.getElementById('statWords').textContent = (stats.total_words || 0).toLocaleString();
} catch (err) {
console.error('加载统计失败:', err);
}
}
// 页面加载时刷新统计
document.addEventListener('DOMContentLoaded', function() {
// 如果在首页,定时刷新统计
if (document.getElementById('statDocs')) {
loadStats();
setInterval(loadStats, 30000); // 每30秒刷新
}
});