diff --git a/ai_chat.db b/ai_chat.db index ce8b930..cb1f27a 100644 Binary files a/ai_chat.db and b/ai_chat.db differ diff --git a/main.py b/main.py index 654e697..a4bc0b6 100644 --- a/main.py +++ b/main.py @@ -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)): """更新系统配置""" diff --git a/main_v2.py b/main_v2.py index b03dd06..feba861 100644 --- a/main_v2.py +++ b/main_v2.py @@ -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 diff --git a/matrix_store/@tester:matrix.tphai.com_SKLVQXSRMI.blacklisted_devices b/matrix_store/@tester:matrix.tphai.com_SKLVQXSRMI.blacklisted_devices new file mode 100644 index 0000000..e69de29 diff --git a/matrix_store/@tester:matrix.tphai.com_SKLVQXSRMI.ignored_devices b/matrix_store/@tester:matrix.tphai.com_SKLVQXSRMI.ignored_devices new file mode 100644 index 0000000..e69de29 diff --git a/matrix_store/@tester:matrix.tphai.com_SKLVQXSRMI.trusted_devices b/matrix_store/@tester:matrix.tphai.com_SKLVQXSRMI.trusted_devices new file mode 100644 index 0000000..3369e07 --- /dev/null +++ b/matrix_store/@tester:matrix.tphai.com_SKLVQXSRMI.trusted_devices @@ -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 diff --git a/services/__pycache__/__init__.cpython-310.pyc b/services/__pycache__/__init__.cpython-310.pyc index 1fd9702..c543ee7 100644 Binary files a/services/__pycache__/__init__.cpython-310.pyc and b/services/__pycache__/__init__.cpython-310.pyc differ diff --git a/services/__pycache__/conversation_service.cpython-310.pyc b/services/__pycache__/conversation_service.cpython-310.pyc index d727d09..925acb0 100644 Binary files a/services/__pycache__/conversation_service.cpython-310.pyc and b/services/__pycache__/conversation_service.cpython-310.pyc differ diff --git a/templates/admin/index.html b/templates/admin/index.html index a241c32..cf2fa1e 100644 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -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; + }
@@ -273,14 +455,97 @@| 配置项 | +当前值 | +状态 | +
|---|---|---|
| API地址 | +- | +- | +
| 模型 | +- | +- | +
| 连接状态 | +- | +检测中 | +
暂无配置
'; + // 过滤掉AI配置(在AI配置面板单独显示) + const otherConfigs = data.configs.filter(c => !c.key.startsWith('ai_')); + + if (otherConfigs.length === 0) { + container.innerHTML = '暂无其他配置
'; return; } - container.innerHTML = data.configs.map(config => ` + container.innerHTML = otherConfigs.map(config => `