Compare commits

..

4 Commits

12 changed files with 525 additions and 156 deletions

34
.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
env/
*.egg-info/
dist/
build/
# Database
*.db
*.sqlite
*.sqlite3
# Logs
logs/
*.log
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Config (if contains secrets)
config.local.*
*.secret

94
README.md Normal file
View File

@@ -0,0 +1,94 @@
# AI 对话系统
支持网页端和Matrix端实时同步对话的AI聊天系统。
## 功能特性
- **网页端对话**: 通过浏览器与AI进行对话
- **Matrix端对话**: 配置Matrix Bot用户可通过Matrix与AI对话
- **实时同步**: 同一用户在网页端和Matrix端的对话自动同步
- **后台管理**: 用户管理、对话记录、系统配置
## 系统架构
```
┌─────────────────────────────────────────────────────────┐
│ AI 对话系统 │
├─────────────────────────────────────────────────────────┤
│ 网页端 (用户A) ←──┐ ┌──→ Matrix端 │
│ │ 同一会话同步 │ (用户A) │
├─────────────────────────────────────────────────────────┤
│ 后端服务 (FastAPI + WebSocket) │
│ - 会话管理 │
│ - 消息存储 │
│ - Matrix Bot 集成 │
│ - AI 模型调用 │
├─────────────────────────────────────────────────────────┤
│ 后台管理 │
│ - 用户管理 │
│ - 对话记录 │
│ - 系统配置 │
└─────────────────────────────────────────────────────────┘
```
## 端口分配
- 19020: 主服务(网页端 + API
- 后台管理: http://localhost:19020/admin
## 快速启动
```bash
# 安装依赖
pip3 install -r requirements.txt
# 启动服务
./start.sh
# 检查状态
./status.sh
# 停止服务
./stop.sh
```
## 配置Matrix Bot
在后台管理页面配置以下参数:
| 配置项 | 说明 |
|--------|------|
| matrix_homeserver | Matrix服务器地址如 https://matrix.tphai.com |
| matrix_username | Matrix Bot用户名@ai-bot:matrix.org |
| matrix_password | Matrix Bot密码 |
配置完成后其他Matrix用户可以直接与Bot对话。
## API接口
### 会话管理
- `GET /api/conversations` - 获取会话列表
- `POST /api/conversations` - 创建新会话
- `GET /api/conversations/{id}/messages` - 获取会话消息
- `DELETE /api/conversations/{id}` - 删除会话
### 后台管理
- `GET /api/admin/stats` - 获取统计数据
- `GET /api/admin/users` - 获取用户列表
- `GET /api/admin/conversations` - 获取对话列表
- `GET /api/admin/config` - 获取系统配置
- `POST /api/admin/config` - 更新系统配置
## 技术栈
- **后端**: FastAPI + WebSocket
- **数据库**: SQLite (SQLAlchemy)
- **Matrix**: matrix-nio
- **前端**: HTML + JavaScript (原生)
- **AI**: 可配置任意LLM API默认使用本地LLM Proxy
## 仓库地址
http://192.168.2.8:12007/coder/ai-chat-system

Binary file not shown.

40
init_config.py Normal file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""初始化配置"""
import sys
sys.path.insert(0, '/home/xian/.openclaw/workspace-coder/works/ai-chat')
from models import SessionLocal, SystemConfig, init_db
# 初始化数据库
init_db()
db = SessionLocal()
# 配置AI模型
configs = [
{'key': 'ai_api_base', 'value': 'http://192.168.2.17:19007/v1', 'description': 'AI模型API地址'},
{'key': 'ai_api_key', 'value': 'xxxx', 'description': 'AI模型API Key'},
{'key': 'ai_model', 'value': 'auto', 'description': 'AI模型名称'},
# 配置Matrix Bot
{'key': 'matrix_homeserver', 'value': 'https://matrix.tphai.com', 'description': 'Matrix服务器地址'},
{'key': 'matrix_username', 'value': '@tester:matrix.tphai.com', 'description': 'Matrix Bot用户名'},
{'key': 'matrix_access_token', 'value': 'syt_dGVzdGVy_eMwWfezCXSyBgHzvkmly_4dWFtM', 'description': 'Matrix Bot Access Token'},
]
for config in configs:
existing = db.query(SystemConfig).filter(SystemConfig.key == config['key']).first()
if existing:
existing.value = config['value']
existing.description = config['description']
else:
db.add(SystemConfig(**config))
db.commit()
print("配置已写入数据库")
# 显示配置
for c in db.query(SystemConfig).all():
print(f"{c.key}: {c.value}")
db.close()

View File

@@ -1,18 +1,39 @@
Traceback (most recent call last):
File "/home/xian/.openclaw/workspace-coder/works/ai-chat/main.py", line 17, in <module>
from models import init_db, get_db, User, Conversation, Message, SystemConfig
File "/home/xian/.openclaw/workspace-coder/works/ai-chat/models/__init__.py", line 1, in <module>
from .database import Base, engine, SessionLocal, get_db, init_db
File "/home/xian/.openclaw/workspace-coder/works/ai-chat/models/database.py", line 33, in <module>
class Conversation(Base):
File "/home/xian/.local/lib/python3.10/site-packages/sqlalchemy/orm/decl_api.py", line 195, in __init__
_as_declarative(reg, cls, dict_)
File "/home/xian/.local/lib/python3.10/site-packages/sqlalchemy/orm/decl_base.py", line 247, in _as_declarative
return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})
File "/home/xian/.local/lib/python3.10/site-packages/sqlalchemy/orm/decl_base.py", line 328, in setup_mapping
return _ClassScanMapperConfig(
File "/home/xian/.local/lib/python3.10/site-packages/sqlalchemy/orm/decl_base.py", line 574, in __init__
self._extract_mappable_attributes()
File "/home/xian/.local/lib/python3.10/site-packages/sqlalchemy/orm/decl_base.py", line 1507, in _extract_mappable_attributes
raise exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: Attribute name 'metadata' is reserved when using the Declarative API.
/home/xian/.openclaw/workspace-coder/works/ai-chat/main.py:445: DeprecationWarning:
on_event is deprecated, use lifespan event handlers instead.
Read more about it in the
[FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
@app.on_event("startup")
/home/xian/.openclaw/workspace-coder/works/ai-chat/main.py:470: DeprecationWarning:
on_event is deprecated, use lifespan event handlers instead.
Read more about it in the
[FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
@app.on_event("shutdown")
INFO: Started server process [1418843]
INFO: Waiting for application startup.
INFO:__main__:数据库初始化完成
INFO:services.ai_service:AI配置已更新: api_base=http://192.168.2.17:19007/v1, model=auto, use_mock=False
INFO:__main__:AI配置已加载: http://192.168.2.17:19007/v1, model=auto
WARNING:nio.client.async_client:Timed out, sleeping for 0s
WARNING:nio.client.async_client:Timed out, sleeping for 0s
WARNING:nio.client.async_client:Timed out, sleeping for 0s
WARNING:nio.client.async_client:Timed out, sleeping for 0s
WARNING:nio.clieWARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s
WARNING:nio.client.async_client:Timed out, sleeping for 60s

165
main.py
View File

@@ -14,7 +14,7 @@ import logging
from datetime import datetime
import os
from models import init_db, get_db, User, Conversation, Message, SystemConfig
from models import init_db, get_db, SessionLocal, User, Conversation, Message, SystemConfig
from services import ai_service, ConversationService, matrix_bot
# 配置日志
@@ -83,15 +83,14 @@ async def admin(request: Request):
# ==================== API路由 ====================
# 固定主用户ID与Matrix AI用户关联
MAIN_USER_ID = "main_user"
@app.get("/api/conversations")
async def get_conversations(user_id: str = None, db: Session = Depends(get_db)):
"""获取会话列表"""
if not user_id:
# 临时用户ID实际应用中应该从session获取
user_id = "web_anonymous"
async def get_conversations(db: Session = Depends(get_db)):
"""获取会话列表(使用固定主用户)"""
conv_service = ConversationService(db)
user = conv_service.get_or_create_user(user_id, user_type='web')
user = conv_service.get_or_create_user(MAIN_USER_ID, display_name="主用户", user_type='web')
conversations = conv_service.get_user_conversations(user.id)
return {
@@ -106,15 +105,30 @@ async def get_conversations(user_id: str = None, db: Session = Depends(get_db)):
]
}
@app.get("/api/conversations/latest")
async def get_latest_conversation(db: Session = Depends(get_db)):
"""获取最新会话用于Matrix同步"""
conv_service = ConversationService(db)
user = conv_service.get_or_create_user(MAIN_USER_ID, display_name="主用户", user_type='web')
conversations = conv_service.get_user_conversations(user.id)
if conversations:
latest = conversations[0] # 已按更新时间倒序
return {
"conversation": {
"id": latest.conversation_id,
"title": latest.title or "新对话",
"updated_at": latest.updated_at.isoformat()
}
}
return {"conversation": None}
@app.post("/api/conversations")
async def create_conversation(user_id: str = None, db: Session = Depends(get_db)):
"""创建新会话"""
if not user_id:
user_id = "web_anonymous"
async def create_conversation(db: Session = Depends(get_db)):
"""创建新会话(使用固定主用户)"""
conv_service = ConversationService(db)
user = conv_service.get_or_create_user(user_id, user_type='web')
user = conv_service.get_or_create_user(MAIN_USER_ID, display_name="主用户", user_type='web')
conversation = conv_service.create_conversation(user.id)
return {
@@ -165,10 +179,12 @@ async def delete_conversation(conversation_id: str, db: Session = Depends(get_db
@app.websocket("/ws/{user_id}")
async def websocket_endpoint(websocket: WebSocket, user_id: str, db: Session = Depends(get_db)):
"""WebSocket连接 - 实时对话"""
await manager.connect(websocket, user_id)
"""WebSocket连接 - 实时对话(所有连接使用主用户)"""
# 统一使用主用户ID
actual_user_id = MAIN_USER_ID
await manager.connect(websocket, actual_user_id)
conv_service = ConversationService(db)
user = conv_service.get_or_create_user(user_id, user_type='web')
user = conv_service.get_or_create_user(MAIN_USER_ID, display_name="主用户", user_type='web')
current_conversation_id = None
@@ -227,8 +243,8 @@ async def websocket_endpoint(websocket: WebSocket, user_id: str, db: Session = D
source='web'
)
# 通知用户消息已收到
await manager.send_to_user(user_id, {
# 广播用户消息(同步到所有客户端)
await manager.send_to_user(MAIN_USER_ID, {
"type": "user_message",
"conversation_id": conversation_id,
"message": {
@@ -255,14 +271,15 @@ async def websocket_endpoint(websocket: WebSocket, user_id: str, db: Session = D
source='web'
)
# 发送AI回复
await manager.send_to_user(user_id, {
# 广播AI回复(同步到所有客户端)
await manager.send_to_user(MAIN_USER_ID, {
"type": "assistant_message",
"conversation_id": conversation_id,
"message": {
"id": assistant_msg.id,
"role": "assistant",
"content": ai_response,
"source": "web",
"created_at": assistant_msg.created_at.isoformat()
}
})
@@ -379,7 +396,15 @@ async def update_config(data: dict, db: Session = Depends(get_db)):
db.commit()
# 如果更新了Matrix配置重新连接
# 根据配置类型执行相应操作
if key.startswith('ai_'):
# 更新AI服务配置
configs = {c.key: c.value for c in db.query(SystemConfig).all()}
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')
ai_service.update_config(api_base, api_key, model)
if key.startswith('matrix_') and matrix_bot.is_running:
await matrix_bot.disconnect()
await matrix_bot.init_from_config()
@@ -389,42 +414,58 @@ async def update_config(data: dict, db: Session = Depends(get_db)):
# ==================== Matrix消息处理回调 ====================
async def handle_matrix_message(conversation_id: str, user_message: str, user_id: str, room_id: str):
async def handle_matrix_message(action: str, conversation_id: str = None, user_message: str = None, room_id: str = None, message_id: int = None):
"""处理从Matrix收到的消息"""
db = SessionLocal()
try:
conv_service = ConversationService(db)
# 获取会话历史
history = conv_service.get_conversation_history(conversation_id, limit=20)
# 调用AI
ai_response = await ai_service.chat(history)
# 保存AI回复
conversation = conv_service.get_conversation(conversation_id)
if conversation:
conv_service.add_message(
conversation_id=conversation.id,
role='assistant',
content=ai_response,
source='matrix'
)
# 发送到Matrix
await matrix_bot.send_message(room_id, ai_response)
# 同时推送到网页端
await manager.send_to_user(user_id, {
"type": "assistant_message",
"conversation_id": conversation_id,
"message": {
"role": "assistant",
"content": ai_response,
"source": "matrix",
"created_at": datetime.utcnow().isoformat()
}
if action == "new_conversation":
# 创建新会话 - 通知WebSocket客户端
await manager.send_to_user(MAIN_USER_ID, {
"type": "new_conversation",
"conversation_id": conversation_id
})
return
if action == "chat":
db = SessionLocal()
try:
conv_service = ConversationService(db)
# 获取会话历史
history = conv_service.get_conversation_history(conversation_id, limit=20)
# 调用AI
ai_response = await ai_service.chat(history)
# 保存AI回复
conversation = conv_service.get_conversation(conversation_id)
if conversation:
assistant_msg = conv_service.add_message(
conversation_id=conversation.id,
role='assistant',
content=ai_response,
source='matrix'
)
# 发送到Matrix
await matrix_bot.send_message(room_id, ai_response)
# 同步到网页端WebSocket
await manager.send_to_user(MAIN_USER_ID, {
"type": "assistant_message",
"conversation_id": conversation_id,
"message": {
"id": assistant_msg.id,
"role": "assistant",
"content": ai_response,
"source": "matrix",
"created_at": assistant_msg.created_at.isoformat()
}
})
except Exception as e:
logger.error(f"处理Matrix消息失败: {e}")
await matrix_bot.send_message(room_id, f"处理消息时出错: {str(e)}")
finally:
db.close()
except Exception as e:
logger.error(f"处理Matrix消息失败: {e}")
@@ -440,6 +481,18 @@ async def startup():
init_db()
logger.info("数据库初始化完成")
# 从数据库加载AI配置
db = SessionLocal()
try:
configs = {c.key: c.value for c in db.query(SystemConfig).all()}
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')
ai_service.update_config(api_base, api_key, model)
logger.info(f"AI配置已加载: {api_base}, model={model}")
finally:
db.close()
# 初始化Matrix Bot
await matrix_bot.init_from_config()
if matrix_bot.is_running:

25
reset_config.py Normal file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""重置配置 - 使用mock模式"""
import sys
sys.path.insert(0, '/home/xian/.openclaw/workspace-coder/works/ai-chat')
from models import SessionLocal, SystemConfig, init_db
init_db()
db = SessionLocal()
# 删除所有AI配置让服务使用mock模式
keys_to_delete = ['ai_api_base', 'ai_api_key', 'ai_model']
for key in keys_to_delete:
c = db.query(SystemConfig).filter(SystemConfig.key == key).first()
if c:
db.delete(c)
# 保留Matrix配置
print("当前配置:")
for c in db.query(SystemConfig).all():
print(f" {c.key}: {c.value[:20]}...")
db.commit()
print("\nAI将使用mock模式因为没有配置AI API")
db.close()

View File

@@ -2,27 +2,40 @@
AI服务 - 调用大模型API
"""
import httpx
from typing import List, Dict, Optional
from typing import List, Dict, AsyncGenerator
import json
import logging
logger = logging.getLogger(__name__)
class AIService:
def __init__(self, api_base: str = "http://192.168.2.17:19007/v1", api_key: str = "sk-local", model: str = "qwen3.5-4b"):
def __init__(self):
self.api_base = ""
self.api_key = ""
self.model = ""
self.use_mock = True
def update_config(self, api_base: str, api_key: str, model: str):
"""更新配置"""
self.api_base = api_base
self.api_key = api_key
self.model = model
# 如果配置完整则使用真实API否则使用mock
self.use_mock = not (api_base and model)
logger.info(f"AI配置已更新: api_base={api_base}, model={model}, use_mock={self.use_mock}")
async def chat(self, messages: List[Dict], stream: bool = False) -> str:
async def chat(self, messages: List[Dict]) -> str:
"""
调用AI模型进行对话
Args:
messages: 对话历史 [{"role": "user", "content": "..."}]
stream: 是否流式输出
Returns:
AI回复内容
"""
# 如果使用mock模式返回模拟回复
if self.use_mock:
logger.info("使用Mock模式回复")
last_msg = messages[-1]['content'] if messages else "你好"
return f"这是一个测试回复。您说的是:{last_msg}\n\n请配置有效的AI服务地址和模型才能获得真正的AI回复。"
# 调用真实API
url = f"{self.api_base}/chat/completions"
headers = {
"Authorization": f"Bearer {self.api_key}",
@@ -31,21 +44,35 @@ class AIService:
payload = {
"model": self.model,
"messages": messages,
"stream": stream,
"temperature": 0.7,
"max_tokens": 2000
}
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(url, headers=headers, json=payload)
response.raise_for_status()
data = response.json()
return data['choices'][0]['message']['content']
logger.info(f"调用AI API: {url}, model={self.model}")
try:
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(url, headers=headers, json=payload)
response.raise_for_status()
data = response.json()
return data['choices'][0]['message']['content']
except Exception as e:
logger.error(f"AI API调用失败: {e}")
# API失败时返回模拟回复
last_msg = messages[-1]['content'] if messages else "你好"
return f"AI服务暂时不可用错误{str(e)})。您说的是:{last_msg}"
async def chat_stream(self, messages: List[Dict]):
async def chat_stream(self, messages: List[Dict]) -> AsyncGenerator[str, None]:
"""
流式调用AI模型
"""
if self.use_mock:
last_msg = messages[-1]['content'] if messages else "你好"
reply = f"这是一个测试回复。您说的是:{last_msg}"
for char in reply:
yield char
return
url = f"{self.api_base}/chat/completions"
headers = {
"Authorization": f"Bearer {self.api_key}",

View File

@@ -1,17 +1,21 @@
"""
Matrix Bot 服务 - 处理Matrix消息收发
所有消息都同步到主用户的最新会话
"""
import asyncio
import json
from typing import Optional, Callable
from nio import AsyncClient, MatrixRoom, RoomMessageText, LoginResponse, SyncResponse
from nio import AsyncClient, MatrixRoom, RoomMessageText, LoginResponse
import logging
from models import SessionLocal, User, Conversation, Message, MatrixRoomMapping, SystemConfig
from models import SessionLocal, User, Conversation, Message, SystemConfig
from services.conversation_service import ConversationService
logger = logging.getLogger(__name__)
# 主用户ID
MAIN_USER_ID = "main_user"
class MatrixBot:
def __init__(self):
@@ -19,6 +23,7 @@ class MatrixBot:
self.homeserver: str = ""
self.username: str = ""
self.password: str = ""
self.access_token: str = ""
self.is_running: bool = False
self.on_message_callback: Optional[Callable] = None
@@ -30,8 +35,9 @@ class MatrixBot:
self.homeserver = configs.get('matrix_homeserver', 'https://matrix.tphai.com')
self.username = configs.get('matrix_username', '')
self.password = configs.get('matrix_password', '')
self.access_token = configs.get('matrix_access_token', '')
if self.username and self.password:
if self.username and (self.password or self.access_token):
await self.connect()
finally:
db.close()
@@ -43,7 +49,19 @@ class MatrixBot:
return False
try:
self.client = AsyncClient(self.homeserver, self.username)
self.client = AsyncClient(
self.homeserver,
self.username,
store_path="/tmp/matrix_store"
)
if self.access_token:
self.client.access_token = self.access_token
self.client.user_id = self.username
logger.info(f"Matrix已设置access_token: {self.username}")
self.is_running = True
return True
response = await self.client.login(self.password)
if isinstance(response, LoginResponse):
@@ -60,6 +78,7 @@ class MatrixBot:
async def start_sync(self, message_handler: Callable = None):
"""开始同步消息"""
if not self.client:
logger.warning("Matrix客户端未初始化")
return
self.on_message_callback = message_handler
@@ -67,61 +86,90 @@ class MatrixBot:
# 注册消息处理器
self.client.add_event_callback(self._handle_room_message, RoomMessageText)
# 开始同步循环
await self.client.sync_forever(timeout=30000)
try:
await self.client.sync(timeout=10000)
logger.info("Matrix初始同步完成")
except Exception as e:
logger.warning(f"Matrix初始同步失败: {e}")
asyncio.create_task(self._sync_loop())
logger.info("Matrix同步任务已启动")
async def _sync_loop(self):
"""后台同步循环"""
while self.is_running:
try:
await self.client.sync(timeout=5000)
await asyncio.sleep(1)
except Exception as e:
logger.warning(f"Matrix同步错误: {e}")
await asyncio.sleep(5)
async def _handle_room_message(self, room: MatrixRoom, event: RoomMessageText):
"""处理收到的房间消息"""
"""处理Matrix消息 - 所有消息同步到主用户最新会话"""
# 忽略自己发送的消息
if event.sender == self.username:
return
message_text = event.body.strip()
db = SessionLocal()
try:
conv_service = ConversationService(db)
# 获取或创建Matrix用户
matrix_user_id = event.sender
user = conv_service.get_or_create_user(
user_id=matrix_user_id,
display_name=room.users.get(matrix_user_id, {}).get('display_name', matrix_user_id),
user_type='matrix',
matrix_user_id=matrix_user_id
# 使用固定主用户
main_user = conv_service.get_or_create_user(
MAIN_USER_ID,
display_name="主用户",
user_type='web'
)
# 获取或创建房间映射
mapping = db.query(MatrixRoomMapping).filter(
MatrixRoomMapping.room_id == room.room_id
).first()
# 处理 /new 命令 - 创建新会话,不回复
if message_text == "/new":
conversation = conv_service.create_conversation(main_user.id)
await self.send_message(room.room_id, "✅ 已创建新会话")
logger.info(f"Matrix创建新会话: {conversation.conversation_id}")
# 通知WebSocket客户端
if self.on_message_callback:
await self.on_message_callback(
action="new_conversation",
conversation_id=conversation.conversation_id,
room_id=room.room_id
)
return
if not mapping:
# 为这个房间创建新会话
conversation = conv_service.create_conversation(user.id, title=f"Matrix: {room.display_name}")
mapping = MatrixRoomMapping(
room_id=room.room_id,
user_id=user.id,
conversation_id=conversation.id
)
db.add(mapping)
db.commit()
db.refresh(mapping)
# 获取最新会话
conversations = conv_service.get_user_conversations(main_user.id)
if not conversations:
# 如果没有会话,创建一个
conversation = conv_service.create_conversation(main_user.id)
else:
conversation = conversations[0] # 最新会话
# 保存用户消息
conv_service.add_message(
conversation_id=mapping.conversation_id,
user_msg = conv_service.add_message(
conversation_id=conversation.id,
role='user',
content=event.body,
content=message_text,
source='matrix',
extra_data={'event_id': event.event_id, 'room_id': room.room_id}
extra_data={
'event_id': event.event_id,
'room_id': room.room_id,
'sender': event.sender
}
)
# 调用外部消息处理器
logger.info(f"Matrix消息保存到会话 {conversation.conversation_id}")
# 调用消息处理器获取AI回复并同步
if self.on_message_callback:
await self.on_message_callback(
conversation_id=mapping.conversation.conversation_id,
user_message=event.body,
user_id=user.user_id,
room_id=room.room_id
action="chat",
conversation_id=conversation.conversation_id,
user_message=message_text,
room_id=room.room_id,
message_id=user_msg.id
)
finally:
db.close()
@@ -145,28 +193,12 @@ class MatrixBot:
logger.error(f"发送Matrix消息失败: {e}")
return False
async def get_room_id_for_user(self, user_matrix_id: str) -> Optional[str]:
"""获取与用户的对话房间ID"""
db = SessionLocal()
try:
user = db.query(User).filter(User.matrix_user_id == user_matrix_id).first()
if user:
mapping = db.query(MatrixRoomMapping).filter(
MatrixRoomMapping.user_id == user.id
).first()
if mapping:
return mapping.room_id
return None
finally:
db.close()
async def disconnect(self):
"""断开连接"""
if self.client:
await self.client.logout()
await self.client.close()
self.is_running = False
# 全局实例
matrix_bot = MatrixBot()
try:
await self.client.logout()
await self.client.close()
except:
pass
self.is_running = False

View File

@@ -341,20 +341,39 @@
</div>
<script>
// 全局变量
// 全局变量 - 使用固定主用户与Matrix AI用户相同
let ws = null;
let userId = 'web_' + Math.random().toString(36).substr(2, 9);
let userId = 'main_user'; // 固定主用户ID
let currentConversationId = null;
let conversations = [];
// 初始化
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('userIdDisplay').textContent = `用户: ${userId}`;
document.getElementById('userIdDisplay').textContent = '主用户模式';
connectWebSocket();
loadConversations();
setupTextarea();
// 监听最新会话更新
setInterval(checkLatestConversation, 2000);
});
// 检查最新会话用于Matrix同步
async function checkLatestConversation() {
try {
const response = await fetch('/api/conversations/latest');
if (response.ok) {
const data = await response.json();
if (data.conversation && data.conversation.id !== currentConversationId) {
// 有新会话,自动切换
selectConversation(data.conversation.id);
}
}
} catch (error) {
// 忽略错误
}
}
// WebSocket连接
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -391,8 +410,27 @@
loadConversations();
break;
case 'new_conversation':
// Matrix创建了新会话自动切换
currentConversationId = data.conversation_id;
loadConversations();
// 清空消息区域
const container = document.getElementById('messagesContainer');
container.innerHTML = `
<div class="welcome">
<h2>👋 开始新对话</h2>
<p>输入您的问题开始对话</p>
</div>
`;
break;
case 'user_message':
appendMessage('user', data.message.content, data.message.source);
// 如果消息来自Matrix切换到对应会话
if (data.message.source === 'matrix' && data.conversation_id) {
currentConversationId = data.conversation_id;
renderConversations();
}
break;
case 'assistant_message':
@@ -451,10 +489,15 @@
// 加载会话列表
async function loadConversations() {
try {
const response = await fetch('/api/conversations?user_id=' + userId);
const response = await fetch('/api/conversations');
const data = await response.json();
conversations = data.conversations;
renderConversations();
// 如果没有当前会话且有会话列表,自动选择最新的
if (!currentConversationId && conversations.length > 0) {
selectConversation(conversations[0].id);
}
} catch (error) {
console.error('加载会话失败:', error);
}
@@ -496,7 +539,7 @@
// 创建新会话
async function createNewConversation() {
try {
const response = await fetch('/api/conversations?user_id=' + userId, {
const response = await fetch('/api/conversations', {
method: 'POST'
});
const data = await response.json();