新增: - 系统设置页面 (/settings) - 支持动态配置LLM、索引、文档处理参数 - 配置API - 保存配置、测试LLM连接 - 前端JS交互文件 - 搜索、文档管理功能 修复: - 首页搜索框无法正常工作的问题(缺少main.js) - 服务支持动态读取配置(无需重启生效) 改进: - LLM/索引/文档配置支持热更新 - 添加测试LLM连接功能
284 lines
14 KiB
HTML
284 lines
14 KiB
HTML
<!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> |