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

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

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

284 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统设置 - LLM Index RAG</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body { background-color: #f8f9fa; }
.config-card { border-radius: 10px; border: none; box-shadow: 0 2px 10px rgba(0,0,0,0.05); }
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">
<i class="bi bi-search"></i> LLM Index RAG
</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" href="/">首页</a>
<a class="nav-link" href="/documents">文档管理</a>
<a class="nav-link" href="/search">知识检索</a>
<a class="nav-link active" href="/settings">系统设置</a>
</div>
</div>
</nav>
<div class="container py-4">
<h4 class="mb-4"><i class="bi bi-gear"></i> 系统设置</h4>
<!-- LLM配置 -->
<div class="card config-card mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-cpu text-primary"></i> 大模型配置</h5>
</div>
<div class="card-body">
<form id="llmConfigForm">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">API地址</label>
<input type="text" class="form-control" id="apiBase"
value="{{ config.llm.api_base }}" placeholder="http://localhost:1234/v1">
<small class="text-muted">LLM API的基础URL</small>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">API Key</label>
<input type="text" class="form-control" id="apiKey"
value="{{ config.llm.api_key }}" placeholder="sk-xxx">
<small class="text-muted">API密钥</small>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">模型名称</label>
<input type="text" class="form-control" id="model"
value="{{ config.llm.model }}" placeholder="qwen/qwen3.5-35b-a3b">
</div>
<div class="col-md-3 mb-3">
<label class="form-label">Max Tokens</label>
<input type="number" class="form-control" id="maxTokens"
value="{{ config.llm.max_tokens }}">
</div>
<div class="col-md-3 mb-3">
<label class="form-label">Temperature</label>
<input type="number" class="form-control" id="temperature"
value="{{ config.llm.temperature }}" step="0.1" min="0" max="2">
</div>
</div>
<div class="mb-3">
<label class="form-label">超时时间(秒)</label>
<input type="number" class="form-control" id="timeout"
value="{{ config.llm.timeout }}">
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-save"></i> 保存配置
</button>
<button type="button" class="btn btn-outline-secondary" onclick="testConnection()">
<i class="bi bi-plug"></i> 测试连接
</button>
</div>
</form>
<div id="testResult" class="mt-3"></div>
</div>
</div>
<!-- 索引配置 -->
<div class="card config-card mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-list-columns text-success"></i> 索引配置</h5>
</div>
<div class="card-body">
<form id="indexConfigForm">
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">BM25 K1参数</label>
<input type="number" class="form-control" id="bm25K1"
value="{{ config.index.bm25_k1 }}" step="0.1">
<small class="text-muted">词频饱和参数推荐1.2-2.0</small>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">BM25 B参数</label>
<input type="number" class="form-control" id="bm25B"
value="{{ config.index.bm25_b }}" step="0.05">
<small class="text-muted">文档长度归一化推荐0.75</small>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">最大返回结果</label>
<input type="number" class="form-control" id="maxResults"
value="{{ config.index.max_results }}">
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">标题权重</label>
<input type="number" class="form-control" id="titleWeight"
value="{{ config.index.title_weight }}" step="0.5">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">关键词权重</label>
<input type="number" class="form-control" id="keywordWeight"
value="{{ config.index.keyword_weight }}" step="0.5">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">内容权重</label>
<input type="number" class="form-control" id="contentWeight"
value="{{ config.index.content_weight }}" step="0.5">
</div>
</div>
<button type="submit" class="btn btn-success">
<i class="bi bi-save"></i> 保存配置
</button>
</form>
</div>
</div>
<!-- 文档配置 -->
<div class="card config-card">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-file-earmark-text text-warning"></i> 文档处理配置</h5>
</div>
<div class="card-body">
<form id="docConfigForm">
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">分块大小</label>
<input type="number" class="form-control" id="chunkSize"
value="{{ config.doc.chunk_size }}">
<small class="text-muted">字符数</small>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">分块重叠</label>
<input type="number" class="form-control" id="chunkOverlap"
value="{{ config.doc.chunk_overlap }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">最大关键词数</label>
<input type="number" class="form-control" id="maxKeywords"
value="{{ config.doc.max_keywords }}">
</div>
</div>
<button type="submit" class="btn btn-warning">
<i class="bi bi-save"></i> 保存配置
</button>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 保存LLM配置
document.getElementById('llmConfigForm')?.addEventListener('submit', async function(e) {
e.preventDefault();
const config = {
api_base: document.getElementById('apiBase').value,
api_key: document.getElementById('apiKey').value,
model: document.getElementById('model').value,
max_tokens: parseInt(document.getElementById('maxTokens').value),
temperature: parseFloat(document.getElementById('temperature').value),
timeout: parseInt(document.getElementById('timeout').value)
};
try {
const res = await fetch('/api/config/llm', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(config)
});
const data = await res.json();
if (data.success) {
alert('LLM配置已保存');
} else {
alert('保存失败: ' + data.error);
}
} catch (err) {
alert('保存失败: ' + err.message);
}
});
// 测试连接
async function testConnection() {
const resultDiv = document.getElementById('testResult');
resultDiv.innerHTML = '<div class="alert alert-info"><span class="spinner-border spinner-border-sm"></span> 测试中...</div>';
try {
const res = await fetch('/api/config/test', {method: 'POST'});
const data = await res.json();
if (data.success) {
resultDiv.innerHTML = '<div class="alert alert-success"><i class="bi bi-check-circle"></i> 连接成功!模型: ' + data.model + '</div>';
} else {
resultDiv.innerHTML = '<div class="alert alert-danger"><i class="bi bi-x-circle"></i> 连接失败: ' + data.error + '</div>';
}
} catch (err) {
resultDiv.innerHTML = '<div class="alert alert-danger"><i class="bi bi-x-circle"></i> 连接失败: ' + err.message + '</div>';
}
}
// 保存索引配置
document.getElementById('indexConfigForm')?.addEventListener('submit', async function(e) {
e.preventDefault();
const config = {
bm25_k1: parseFloat(document.getElementById('bm25K1').value),
bm25_b: parseFloat(document.getElementById('bm25B').value),
max_results: parseInt(document.getElementById('maxResults').value),
title_weight: parseFloat(document.getElementById('titleWeight').value),
keyword_weight: parseFloat(document.getElementById('keywordWeight').value),
content_weight: parseFloat(document.getElementById('contentWeight').value)
};
try {
const res = await fetch('/api/config/index', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(config)
});
const data = await res.json();
if (data.success) {
alert('索引配置已保存!');
} else {
alert('保存失败: ' + data.error);
}
} catch (err) {
alert('保存失败: ' + err.message);
}
});
// 保存文档配置
document.getElementById('docConfigForm')?.addEventListener('submit', async function(e) {
e.preventDefault();
const config = {
chunk_size: parseInt(document.getElementById('chunkSize').value),
chunk_overlap: parseInt(document.getElementById('chunkOverlap').value),
max_keywords: parseInt(document.getElementById('maxKeywords').value)
};
try {
const res = await fetch('/api/config/doc', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(config)
});
const data = await res.json();
if (data.success) {
alert('文档配置已保存!');
} else {
alert('保存失败: ' + data.error);
}
} catch (err) {
alert('保存失败: ' + err.message);
}
});
</script>
</body>
</html>