fix: 文件上传不再添加文件名标记,内容自然融入消息

This commit is contained in:
2026-04-14 09:15:46 +08:00
parent e6429b1f95
commit a34bef50ae
9 changed files with 720 additions and 30 deletions

Binary file not shown.

199
main.py
View File

@@ -382,6 +382,205 @@ async def get_config(db: Session = Depends(get_db)):
}
@app.get("/api/admin/ai-config")
async def get_ai_config(db: Session = Depends(get_db)):
"""获取AI配置"""
configs = {c.key: c.value for c in db.query(SystemConfig).filter(SystemConfig.key.startswith('ai_')).all()}
return {
"api_base": configs.get('ai_api_base', 'http://192.168.2.17:19007/v1'),
"api_key": configs.get('ai_api_key', 'xxxx'),
"model": configs.get('ai_model', 'auto'),
"use_mock": ai_service.use_mock
}
@app.post("/api/admin/ai-config")
async def update_ai_config(data: dict, db: Session = Depends(get_db)):
"""更新AI配置"""
api_base = data.get("api_base")
api_key = data.get("api_key")
model = data.get("model")
if api_base:
config = db.query(SystemConfig).filter(SystemConfig.key == 'ai_api_base').first()
if config:
config.value = api_base
else:
config = SystemConfig(key='ai_api_base', value=api_base, description='AI API地址')
db.add(config)
if api_key:
config = db.query(SystemConfig).filter(SystemConfig.key == 'ai_api_key').first()
if config:
config.value = api_key
else:
config = SystemConfig(key='ai_api_key', value=api_key, description='AI API密钥')
db.add(config)
if model:
config = db.query(SystemConfig).filter(SystemConfig.key == 'ai_model').first()
if config:
config.value = model
else:
config = SystemConfig(key='ai_model', value=model, description='AI模型名称')
db.add(config)
db.commit()
# 更新AI服务配置
configs = {c.key: c.value for c in db.query(SystemConfig).filter(SystemConfig.key.startswith('ai_')).all()}
ai_service.update_config(
configs.get('ai_api_base', 'http://192.168.2.17:19007/v1'),
configs.get('ai_api_key', 'xxxx'),
configs.get('ai_model', 'auto')
)
return {"success": True, "message": "AI配置已更新"}
@app.get("/api/admin/models")
async def get_available_models(db: Session = Depends(get_db)):
"""获取可用模型列表"""
import httpx
# 从数据库读取最新配置
configs = {c.key: c.value for c in db.query(SystemConfig).filter(SystemConfig.key.startswith('ai_')).all()}
api_base = configs.get('ai_api_base', '')
api_key = configs.get('ai_api_key', 'xxxx')
if not api_base:
# 返回默认模型列表
return {
"models": [
{"id": "auto", "name": "auto (自动选择)", "owned_by": "system"},
{"id": "qwen3.5-4b", "name": "qwen3.5-4b", "owned_by": "local"},
{"id": "dsv32", "name": "dsv32", "owned_by": "deepseek"},
{"id": "glm-4", "name": "glm-4", "owned_by": "zhipu"},
{"id": "gpt-4o", "name": "gpt-4o", "owned_by": "openai"},
{"id": "claude-3-opus", "name": "claude-3-opus", "owned_by": "anthropic"}
],
"success": False,
"message": "请先配置API地址"
}
try:
# 从当前配置的API地址获取模型列表
url = f"{api_base}/models"
headers = {"Authorization": f"Bearer {api_key}"}
logger.info(f"获取模型列表: url={url}")
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
models = []
for m in data.get('data', []):
model_id = m.get('id', '')
if model_id:
models.append({
"id": model_id,
"name": m.get('name', model_id),
"owned_by": m.get('owned_by', 'unknown')
})
return {"models": models, "success": True}
except Exception as e:
logger.error(f"获取模型列表失败: {e}")
# 返回默认模型列表
return {
"models": [
{"id": "auto", "name": "auto (自动选择)", "owned_by": "system"},
{"id": "qwen3.5-4b", "name": "qwen3.5-4b", "owned_by": "local"},
{"id": "dsv32", "name": "dsv32", "owned_by": "deepseek"},
{"id": "glm-4", "name": "glm-4", "owned_by": "zhipu"},
{"id": "gpt-4o", "name": "gpt-4o", "owned_by": "openai"},
{"id": "claude-3-opus", "name": "claude-3-opus", "owned_by": "anthropic"}
],
"success": False,
"message": "无法从API获取模型列表显示默认列表"
}
@app.post("/api/admin/test-ai")
async def test_ai_connection(db: Session = Depends(get_db)):
"""测试AI连接"""
import httpx
# 从数据库读取最新配置,如果没有则使用默认值
configs = {c.key: c.value for c in db.query(SystemConfig).filter(SystemConfig.key.startswith('ai_')).all()}
# 使用数据库值或默认值
api_base = configs.get('ai_api_base') or 'http://192.168.2.17:19007/v1'
api_key = configs.get('ai_api_key') or 'xxxx'
model = configs.get('ai_model') or 'auto'
# 判断是否使用默认值
using_defaults = not configs.get('ai_api_base')
try:
url = f"{api_base}/chat/completions"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": [{"role": "user", "content": "测试连接"}],
"max_tokens": 50
}
logger.info(f"测试AI连接: url={url}, model={model}, using_defaults={using_defaults}")
async with httpx.AsyncClient(timeout=15.0) as client:
response = await client.post(url, headers=headers, json=payload)
if response.status_code == 200:
data = response.json()
content = data['choices'][0]['message']['content']
result = {
"success": True,
"message": f"连接成功!模型响应: {content[:100]}",
"model": model,
"api_base": api_base
}
if using_defaults:
result["message"] += "\n(使用默认配置,点击「保存配置」可持久化)"
return result
else:
error_text = response.text[:200] if response.text else ""
return {
"success": False,
"message": f"连接失败: HTTP {response.status_code} - {error_text}",
"model": model,
"api_base": api_base
}
except httpx.ConnectError as e:
return {
"success": False,
"message": f"无法连接到API地址: {api_base}",
"model": model,
"api_base": api_base
}
except httpx.TimeoutException:
return {
"success": False,
"message": f"连接超时15秒",
"model": model,
"api_base": api_base
}
except Exception as e:
return {
"success": False,
"message": f"连接失败: {str(e)}",
"model": model,
"api_base": api_base
}
@app.post("/api/admin/config")
async def update_config(data: dict, db: Session = Depends(get_db)):
"""更新系统配置"""

View File

@@ -786,21 +786,27 @@ async def websocket_endpoint(websocket: WebSocket, user_id: str):
# 处理文件内容,添加到消息
image_contents = [] # 图片内容(用于视觉模型)
text_contents = [] # 文本文件内容
if files:
for f in files:
if f.get('type') and f['type'].startswith('image/'):
# 图片:记录 base64 数据,后续可能用于视觉模型
# 图片:记录 base64 数据,用于视觉模型
image_contents.append({
'name': f['name'],
'type': f['type'],
'data': f.get('content', '') # base64 数据
})
message += f"\n[图片: {f['name']}]"
# 不添加文件名文本,图片信息保存在 extra_data 中
elif f.get('content'):
# 文本文件:直接添加内容
message += f"\n\n文件 {f['name']} 内容:\n{f['content'][:3000]}"
# 文本文件:直接添加内容,不带文件名前缀
text_contents.append(f['content'][:3000])
if len(f['content']) > 3000:
message += "...(内容过长已截断)"
text_contents[-1] += "...(内容过长已截断)"
# 如果有文本文件内容,追加到消息后面
if text_contents:
for content in text_contents:
message += f"\n\n{content}"
# 保存图片信息到 extra_data用于历史记录
extra_data_for_msg = None

View File

@@ -0,0 +1,11 @@
@tester:matrix.tphai.com AJFVRTHLJY matrix-ed25519 4mRjLhM8xbwjkwQP2T/iB3UZJoaADgP6cCVUiB8AtSk
@tester:matrix.tphai.com ATYFRXKHEQ matrix-ed25519 WnaxV7S11wrqlojKOR3j2RDlPL7TrO17U2ablFISbnw
@tester:matrix.tphai.com BDTRXIGPBE matrix-ed25519 gjQNtLEpIEYCjmzUx5ma91G498n4UADh84KUmiReJUM
@tester:matrix.tphai.com GALBNVJOSG matrix-ed25519 /a7qD2Od76/+Xrr/naDqWEQJZ982X9XdYkCBbRmKxBU
@tester:matrix.tphai.com GVSFGGYNJL matrix-ed25519 8qV2own4G3m2nki+izFDBOrAxtbGl8RoneM3qUPkThU
@tester:matrix.tphai.com IMEQIQPXTR matrix-ed25519 6Yd4lmhP6jdkkNvh1rIw6TRK331ZUyiAt5G5hPeYqSE
@tester:matrix.tphai.com MIPPYHRVAS matrix-ed25519 s8Ol56sxLCjCOi0Gkv/Kj7LqVMp/8ZmuAJ6QA1rUi7o
@tester:matrix.tphai.com UKJGJYQQLT matrix-ed25519 opC9rhsz1nzrvQqNWMKTF5FxWIGuHTDfixx+q/Y8ea0
@tester:matrix.tphai.com UPMZGRLESG matrix-ed25519 86c6XPCIYHgesq83C2k5xhXNa0EYMnqTq4jFrTwJX8I
@huangzhuang_bro:matrix.tphai.com BQHGFLQEPR matrix-ed25519 IrEHmvqotfHKLyx1JRJp4RthUVyBT8qQX72qBifRRyQ
@huangzhuang_bro:matrix.tphai.com NTVATQQGPK matrix-ed25519 lKMDsoTFK/Lc8yXoqqHBBeuK2HPKAaFFm9KjxgQzEy0

View File

@@ -144,6 +144,197 @@
background: #f9f9f9;
}
/* AI配置专用样式 */
.ai-config-section {
background: #f8f9fa;
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
}
.ai-config-section h3 {
font-size: 18px;
color: #333;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.ai-config-section h3 i {
color: #10a37f;
}
.ai-status {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: #fff;
border-radius: 8px;
margin-bottom: 16px;
}
.ai-status.ok {
border: 1px solid #10a37f;
}
.ai-status.error {
border: 1px solid #dc3545;
}
.ai-status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.ai-status-dot.ok {
background: #10a37f;
}
.ai-status-dot.error {
background: #dc3545;
}
.ai-status-text {
flex: 1;
}
.ai-config-form {
display: grid;
gap: 16px;
}
.config-row {
display: grid;
grid-template-columns: 150px 1fr;
gap: 16px;
align-items: center;
}
.config-row label {
font-weight: 500;
color: #555;
}
.config-row input, .config-row select {
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
width: 100%;
}
.config-row input:focus, .config-row select:focus {
outline: none;
border-color: #10a37f;
}
/* 模型输入组合框 */
.model-input-wrapper {
display: flex;
position: relative;
}
.model-input-wrapper input {
flex: 1;
padding-right: 40px;
}
.btn-model-dropdown {
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
width: 32px;
height: 32px;
background: #f0f0f0;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #666;
transition: all 0.2s;
}
.btn-model-dropdown:hover {
background: #e0e0e0;
color: #333;
}
/* datalist样式提示 */
.model-input-wrapper input::-webkit-calendar-picker-indicator {
opacity: 0;
width: 32px;
height: 32px;
cursor: pointer;
}
.config-actions {
display: flex;
gap: 12px;
margin-top: 16px;
}
.btn {
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
border: none;
transition: all 0.2s;
}
.btn-primary {
background: #10a37f;
color: #fff;
}
.btn-primary:hover {
background: #0d8c6d;
}
.btn-secondary {
background: #fff;
color: #333;
border: 1px solid #ddd;
}
.btn-secondary:hover {
background: #f0f0f0;
}
.btn-test {
background: #007bff;
color: #fff;
}
.btn-test:hover {
background: #0056b3;
}
.test-result {
padding: 16px;
border-radius: 8px;
margin-top: 16px;
}
.test-result.success {
background: #d4edda;
border: 1px solid #10a37f;
color: #155724;
}
.test-result.error {
background: #f8d7da;
border: 1px solid #dc3545;
color: #721c24;
}
/* 原有配置表单样式 */
.config-form {
display: flex;
gap: 16px;
@@ -167,20 +358,6 @@
min-height: 60px;
}
.config-form button {
padding: 12px 24px;
background: #10a37f;
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
}
.config-form button:hover {
background: #0d8c6d;
}
.config-list {
margin-top: 24px;
}
@@ -246,6 +423,11 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.loading {
opacity: 0.6;
pointer-events: none;
}
</style>
</head>
<body>
@@ -273,14 +455,97 @@
</div>
<div class="tabs">
<button class="tab-btn active" onclick="switchTab('users')">用户管理</button>
<button class="tab-btn" onclick="switchTab('conversations')">对话记录</button>
<button class="tab-btn" onclick="switchTab('config')">系统配置</button>
<button class="tab-btn active" onclick="switchTab('ai')">🧠 AI配置</button>
<button class="tab-btn" onclick="switchTab('users')">👥 用户管理</button>
<button class="tab-btn" onclick="switchTab('conversations')">💬 对话记录</button>
<button class="tab-btn" onclick="switchTab('config')">⚙️ 其他配置</button>
</div>
<div class="tab-content">
<!-- AI配置 -->
<div class="tab-panel active" id="aiPanel">
<div class="ai-config-section">
<h3><i class="ri-robot-line"></i> 大模型配置</h3>
<div class="ai-status" id="aiStatus">
<div class="ai-status-dot" id="aiStatusDot"></div>
<div class="ai-status-text" id="aiStatusText">检测中...</div>
</div>
<div class="ai-config-form">
<div class="config-row">
<label>API地址</label>
<input type="text" id="aiApiBase" placeholder="http://192.168.2.17:19007/v1">
</div>
<div class="config-row">
<label>API密钥</label>
<input type="text" id="aiApiKey" placeholder="xxxx">
</div>
<div class="config-row">
<label>模型</label>
<div class="model-input-wrapper">
<input type="text" id="aiModel" list="modelList" placeholder="选择或输入模型名称">
<datalist id="modelList">
<option value="auto">auto (自动选择)</option>
<option value="qwen3.5-4b">qwen3.5-4b</option>
<option value="dsv32">dsv32</option>
<option value="glm-4">glm-4</option>
<option value="gpt-4o">gpt-4o</option>
<option value="claude-3-opus">claude-3-opus</option>
</datalist>
<button class="btn-model-dropdown" onclick="toggleModelDropdown()" title="显示预设模型">
<i class="ri-arrow-down-s-line"></i>
</button>
</div>
</div>
</div>
<div class="config-actions">
<button class="btn btn-primary" onclick="saveAIConfig()">
<i class="ri-save-line"></i> 保存配置
</button>
<button class="btn btn-test" onclick="testAIConnection()">
<i class="ri-link"></i> 测试连接
</button>
<button class="btn btn-secondary" onclick="refreshModels()">
<i class="ri-refresh-line"></i> 刷新模型列表
</button>
</div>
<div class="test-result" id="testResult" style="display: none;"></div>
</div>
<div class="ai-config-section">
<h3><i class="ri-information-line"></i> 当前状态</h3>
<table>
<tr>
<th>配置项</th>
<th>当前值</th>
<th>状态</th>
</tr>
<tr>
<td>API地址</td>
<td id="currentApiBase">-</td>
<td><span class="badge" id="apiBaseStatus">-</span></td>
</tr>
<tr>
<td>模型</td>
<td id="currentModel">-</td>
<td><span class="badge" id="modelStatus">-</span></td>
</tr>
<tr>
<td>连接状态</td>
<td id="connectionStatus">-</td>
<td><span class="badge" id="connectionBadge">检测中</span></td>
</tr>
</table>
</div>
</div>
<!-- 用户管理 -->
<div class="tab-panel active" id="usersPanel">
<div class="tab-panel" id="usersPanel">
<table>
<thead>
<tr>
@@ -318,13 +583,13 @@
</table>
</div>
<!-- 系统配置 -->
<!-- 其他配置 -->
<div class="tab-panel" id="configPanel">
<div class="config-form">
<input type="text" id="configKey" placeholder="配置键名">
<textarea id="configValue" placeholder="配置值"></textarea>
<input type="text" id="configDesc" placeholder="描述(可选)">
<button onclick="saveConfig()">保存</button>
<button class="btn btn-primary" onclick="saveConfig()">保存</button>
</div>
<div class="config-list" id="configList">
@@ -338,6 +603,7 @@
// 初始化
document.addEventListener('DOMContentLoaded', () => {
loadStats();
loadAIConfig();
loadUsers();
loadConversations();
loadConfig();
@@ -352,6 +618,208 @@
document.getElementById(`${tabName}Panel`).classList.add('active');
}
// 切换模型下拉显示
function toggleModelDropdown() {
const input = document.getElementById('aiModel');
input.focus();
// 触发datalist显示
if (input.showPicker) {
input.showPicker();
} else {
// 兼容性处理:模拟点击
input.click();
}
}
// 加载AI配置
async function loadAIConfig() {
try {
const response = await fetch('/api/admin/ai-config');
const data = await response.json();
document.getElementById('aiApiBase').value = data.api_base || '';
document.getElementById('aiApiKey').value = data.api_key || '';
document.getElementById('aiModel').value = data.model || 'auto';
// 更新当前状态显示
document.getElementById('currentApiBase').textContent = data.api_base || '-';
document.getElementById('currentModel').textContent = data.model || '-';
// 更新状态指示
if (data.use_mock) {
document.getElementById('aiStatusDot').className = 'ai-status-dot error';
document.getElementById('aiStatusText').textContent = '当前使用Mock模式未连接真实API';
document.getElementById('aiStatus').className = 'ai-status error';
document.getElementById('connectionStatus').textContent = 'Mock模式';
document.getElementById('connectionBadge').className = 'badge inactive';
document.getElementById('connectionBadge').textContent = '未连接';
} else {
document.getElementById('aiStatusDot').className = 'ai-status-dot ok';
document.getElementById('aiStatusText').textContent = '已配置真实API';
document.getElementById('aiStatus').className = 'ai-status ok';
document.getElementById('connectionStatus').textContent = '已配置';
document.getElementById('connectionBadge').className = 'badge active';
document.getElementById('connectionBadge').textContent = '待测试';
}
document.getElementById('apiBaseStatus').className = 'badge active';
document.getElementById('apiBaseStatus').textContent = '已配置';
document.getElementById('modelStatus').className = 'badge active';
document.getElementById('modelStatus').textContent = data.model || 'auto';
} catch (error) {
console.error('加载AI配置失败:', error);
}
}
// 保存AI配置
async function saveAIConfig() {
const apiBase = document.getElementById('aiApiBase').value.trim();
const apiKey = document.getElementById('aiApiKey').value.trim();
const model = document.getElementById('aiModel').value.trim();
if (!apiBase) {
alert('请填写API地址');
return;
}
try {
const btn = event.target;
btn.classList.add('loading');
btn.textContent = '保存中...';
const response = await fetch('/api/admin/ai-config', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({api_base: apiBase, api_key: apiKey, model: model})
});
const data = await response.json();
btn.classList.remove('loading');
btn.innerHTML = '<i class="ri-save-line"></i> 保存配置';
if (data.success) {
// 显示成功提示
const resultDiv = document.getElementById('testResult');
resultDiv.style.display = 'block';
resultDiv.className = 'test-result success';
resultDiv.innerHTML = `<i class="ri-check-line"></i> ${data.message}`;
// 重新加载配置
loadAIConfig();
// 3秒后隐藏提示
setTimeout(() => resultDiv.style.display = 'none', 3000);
} else {
alert('保存失败: ' + (data.message || '未知错误'));
}
} catch (error) {
console.error('保存AI配置失败:', error);
alert('保存失败: ' + error.message);
event.target.classList.remove('loading');
event.target.innerHTML = '<i class="ri-save-line"></i> 保存配置';
}
}
// 测试AI连接
async function testAIConnection() {
try {
const btn = event.target;
btn.classList.add('loading');
btn.innerHTML = '<i class="ri-loader-line"></i> 测试中...';
const response = await fetch('/api/admin/test-ai', {
method: 'POST'
});
const data = await response.json();
btn.classList.remove('loading');
btn.innerHTML = '<i class="ri-link"></i> 测试连接';
const resultDiv = document.getElementById('testResult');
resultDiv.style.display = 'block';
if (data.success) {
resultDiv.className = 'test-result success';
resultDiv.innerHTML = `<i class="ri-check-line"></i> <strong>连接成功!</strong><br>模型: ${data.model}<br>响应: ${data.message}`;
// 更新连接状态
document.getElementById('aiStatusDot').className = 'ai-status-dot ok';
document.getElementById('aiStatusText').textContent = '连接正常';
document.getElementById('aiStatus').className = 'ai-status ok';
document.getElementById('connectionStatus').textContent = '正常';
document.getElementById('connectionBadge').className = 'badge active';
document.getElementById('connectionBadge').textContent = '已连接';
} else {
resultDiv.className = 'test-result error';
resultDiv.innerHTML = `<i class="ri-close-line"></i> <strong>连接失败</strong><br>${data.message}`;
// 更新连接状态
document.getElementById('aiStatusDot').className = 'ai-status-dot error';
document.getElementById('aiStatusText').textContent = '连接失败';
document.getElementById('aiStatus').className = 'ai-status error';
document.getElementById('connectionStatus').textContent = '失败';
document.getElementById('connectionBadge').className = 'badge inactive';
document.getElementById('connectionBadge').textContent = '错误';
}
} catch (error) {
console.error('测试连接失败:', error);
event.target.classList.remove('loading');
event.target.innerHTML = '<i class="ri-link"></i> 测试连接';
const resultDiv = document.getElementById('testResult');
resultDiv.style.display = 'block';
resultDiv.className = 'test-result error';
resultDiv.innerHTML = `<i class="ri-close-line"></i> 测试失败: ${error.message}`;
}
}
// 刷新模型列表
async function refreshModels() {
try {
const btn = event.target;
btn.classList.add('loading');
btn.innerHTML = '<i class="ri-loader-line"></i> 刷新中...';
const response = await fetch('/api/admin/models');
const data = await response.json();
btn.classList.remove('loading');
btn.innerHTML = '<i class="ri-refresh-line"></i> 刷新模型列表';
// 更新datalist
const datalist = document.getElementById('modelList');
datalist.innerHTML = '';
for (const model of data.models) {
const option = document.createElement('option');
option.value = model.id;
option.textContent = model.name;
datalist.appendChild(option);
}
// 显示提示
const resultDiv = document.getElementById('testResult');
resultDiv.style.display = 'block';
resultDiv.className = 'test-result success';
resultDiv.innerHTML = `<i class="ri-check-line"></i> 获取到 ${data.models.length} 个模型,可在输入框中选择`;
if (!data.success) {
resultDiv.className = 'test-result error';
resultDiv.innerHTML = `<i class="ri-warning-line"></i> ${data.message || '使用默认模型列表'}`;
}
setTimeout(() => resultDiv.style.display = 'none', 3000);
} catch (error) {
console.error('刷新模型列表失败:', error);
event.target.classList.remove('loading');
event.target.innerHTML = '<i class="ri-refresh-line"></i> 刷新模型列表';
}
}
// 加载统计数据
async function loadStats() {
try {
@@ -423,7 +891,7 @@
}
}
// 加载配置
// 加载其他配置
async function loadConfig() {
try {
const response = await fetch('/api/admin/config');
@@ -431,12 +899,15 @@
const container = document.getElementById('configList');
if (data.configs.length === 0) {
container.innerHTML = '<p>暂无配置</p>';
// 过滤掉AI配置在AI配置面板单独显示
const otherConfigs = data.configs.filter(c => !c.key.startsWith('ai_'));
if (otherConfigs.length === 0) {
container.innerHTML = '<p>暂无其他配置</p>';
return;
}
container.innerHTML = data.configs.map(config => `
container.innerHTML = otherConfigs.map(config => `
<div class="config-item">
<div class="key">${config.key}</div>
<div class="value">${config.value}</div>
@@ -448,7 +919,7 @@
}
}
// 保存配置
// 保存其他配置
async function saveConfig() {
const key = document.getElementById('configKey').value.trim();
const value = document.getElementById('configValue').value.trim();
@@ -497,6 +968,9 @@
// 定时刷新
setInterval(() => {
loadStats();
if (document.getElementById('aiPanel').classList.contains('active')) {
loadAIConfig();
}
if (document.getElementById('usersPanel').classList.contains('active')) {
loadUsers();
}