聊天工具优化
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# 复制此文件为 .env 并根据实际情况修改
|
||||
|
||||
# 服务端口
|
||||
PORT=5000
|
||||
PORT=15000
|
||||
|
||||
# 调试模式 (True/False)
|
||||
DEBUG=True
|
||||
@@ -10,15 +10,15 @@ DEBUG=True
|
||||
# Flask密钥(生产环境请使用随机字符串)
|
||||
SECRET_KEY=ai-assistant-web-client-secret-key
|
||||
|
||||
# API基础URL(如果需要代理到其他服务器)
|
||||
# API_BASE_URL=http://localhost:8082
|
||||
# API后端服务地址(api模块提供HTTP接口)
|
||||
# 本地开发时使用80端口(Apollo配置的默认端口)
|
||||
API_BASE_URL=http://localhost:80
|
||||
|
||||
# 认证Token(从登录获取,或使用固定token)
|
||||
# AUTH_TOKEN=your_jwt_token_here
|
||||
|
||||
# 日志级别 (DEBUG/INFO/WARNING/ERROR)
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# 默认应用ID
|
||||
DEFAULT_APP_ID=15
|
||||
|
||||
# CORS允许的源(多个用逗号分隔)
|
||||
# CORS_ORIGINS=http://localhost:3000,http://localhost:8080
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# 更新日志
|
||||
|
||||
## [2.0.0] - 2025-12-26
|
||||
|
||||
### 🎉 重大更新:对接im-api后端服务
|
||||
|
||||
#### ✨ 新增功能
|
||||
|
||||
1. **应用选择界面**
|
||||
- 新增应用列表展示页面
|
||||
- 支持应用搜索功能
|
||||
- 应用按创建时间倒排显示
|
||||
- 卡片式应用展示,包含头像、名称、描述、分类
|
||||
|
||||
2. **后端API集成**
|
||||
- 对接im-api后端聊天服务
|
||||
- 支持SSE流式对话响应
|
||||
- 支持推荐问题加载
|
||||
- 支持多应用切换
|
||||
|
||||
3. **代理层实现**
|
||||
- Flask代理层转发请求到im-api
|
||||
- SSE流式响应转发
|
||||
- 统一错误处理
|
||||
- CORS支持
|
||||
|
||||
#### 🔄 功能变更
|
||||
|
||||
1. **移除左侧面板**
|
||||
- 移除用户信息面板
|
||||
- 移除待办事项功能
|
||||
- 移除提醒设置功能
|
||||
- 简化界面,专注对话功能
|
||||
|
||||
2. **对话界面优化**
|
||||
- 添加返回按钮,可返回应用列表
|
||||
- 顶部显示当前应用信息
|
||||
- 动态加载推荐问题
|
||||
- 优化欢迎消息显示
|
||||
|
||||
3. **API接口调整**
|
||||
- `/api/applications` → `/api/ai-assistant/chatapp`
|
||||
- `/api/chat/send` → `/api/ai-assistant/chat/completions/message`
|
||||
- 新增 `/api/ai-assistant/chatapp/{appId}/getRecommendQuestion`
|
||||
|
||||
#### 🛠️ 技术改进
|
||||
|
||||
1. **前端优化**
|
||||
- 重构JavaScript代码结构
|
||||
- 添加应用选择逻辑
|
||||
- 实现SSE流式响应处理
|
||||
- 优化错误处理和提示
|
||||
|
||||
2. **后端优化**
|
||||
- 使用requests库进行HTTP代理
|
||||
- 实现SSE流式转发
|
||||
- 添加健康检查接口
|
||||
- 优化日志记录
|
||||
|
||||
3. **配置管理**
|
||||
- 新增IM_API_BASE_URL配置
|
||||
- 新增AUTH_TOKEN配置
|
||||
- 更新环境变量示例
|
||||
- 优化启动脚本
|
||||
|
||||
#### 📝 文档更新
|
||||
|
||||
1. **新增文档**
|
||||
- `INTEGRATION_GUIDE.md` - 后端集成指南
|
||||
- `CHANGELOG.md` - 更新日志
|
||||
|
||||
2. **更新文档**
|
||||
- `.env.example` - 环境配置示例
|
||||
- `start.sh` - 启动脚本
|
||||
- `README.md` - 项目说明
|
||||
|
||||
#### 🐛 Bug修复
|
||||
|
||||
- 修复消息发送后输入框未清空的问题
|
||||
- 修复打字指示器未正确移除的问题
|
||||
- 修复滚动到底部的时机问题
|
||||
|
||||
#### ⚠️ 破坏性变更
|
||||
|
||||
1. **API变更**
|
||||
- 旧的API接口已废弃
|
||||
- 需要配置im-api后端地址
|
||||
- 可能需要配置认证token
|
||||
|
||||
2. **界面变更**
|
||||
- 移除了左侧用户信息面板
|
||||
- 初始界面改为应用选择
|
||||
- 需要先选择应用才能对话
|
||||
|
||||
#### 🔧 迁移指南
|
||||
|
||||
从1.0版本升级到2.0版本:
|
||||
|
||||
1. **更新配置文件**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# 编辑.env,配置IM_API_BASE_URL
|
||||
```
|
||||
|
||||
2. **确保后端服务运行**
|
||||
```bash
|
||||
# 确保im-api服务在http://localhost:8080运行
|
||||
curl http://localhost:8080/api/ai-assistant/chatapp
|
||||
```
|
||||
|
||||
3. **重启服务**
|
||||
```bash
|
||||
./start.sh
|
||||
```
|
||||
|
||||
4. **访问新界面**
|
||||
```
|
||||
打开 http://localhost:15001
|
||||
首先选择一个应用,然后开始对话
|
||||
```
|
||||
|
||||
#### 📊 性能改进
|
||||
|
||||
- SSE流式响应,实时显示AI回复
|
||||
- 优化应用列表加载速度
|
||||
- 减少不必要的API请求
|
||||
|
||||
#### 🎯 下一步计划
|
||||
|
||||
- [ ] 集成真实的登录认证系统
|
||||
- [ ] 支持文件上传功能
|
||||
- [ ] 支持语音输入
|
||||
- [ ] 支持多会话管理
|
||||
- [ ] 支持历史记录查看
|
||||
- [ ] 添加请求缓存
|
||||
- [ ] 优化SSE重连机制
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2025-12-25
|
||||
|
||||
### 初始版本
|
||||
|
||||
- ✅ 100%还原原型设计
|
||||
- ✅ 粒子背景动画
|
||||
- ✅ 玻璃态设计
|
||||
- ✅ 消息气泡动画
|
||||
- ✅ 打字指示器
|
||||
- ✅ 快捷回复
|
||||
- ✅ 主题切换
|
||||
- ✅ 字体选择
|
||||
- ✅ 待办事项管理
|
||||
- ✅ 模拟API响应
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
# AI助手Web客户端 - 后端集成指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本Web客户端已成功对接im-api后端聊天服务,支持:
|
||||
- ✅ 应用列表加载(支持搜索、按创建时间倒排)
|
||||
- ✅ 应用选择
|
||||
- ✅ SSE流式对话
|
||||
- ✅ 推荐问题加载
|
||||
- ✅ 完整的对话交互
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### 1. 环境变量配置
|
||||
|
||||
复制 `.env.example` 为 `.env` 并修改:
|
||||
|
||||
```bash
|
||||
# im-api后端服务地址
|
||||
IM_API_BASE_URL=http://localhost:8080
|
||||
|
||||
# 认证Token(可选,如果后端需要认证)
|
||||
AUTH_TOKEN=your_jwt_token_here
|
||||
|
||||
# 服务端口
|
||||
PORT=15000
|
||||
```
|
||||
|
||||
### 2. 后端API要求
|
||||
|
||||
确保im-api后端服务已启动,并提供以下接口:
|
||||
|
||||
#### 2.1 获取应用列表
|
||||
```
|
||||
GET /api/ai-assistant/chatapp
|
||||
```
|
||||
|
||||
响应格式:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": "app_id",
|
||||
"appName": "应用名称",
|
||||
"appDescription": "应用描述",
|
||||
"appAvatar": "头像URL",
|
||||
"category": "分类",
|
||||
"sortNum": 1,
|
||||
"enableRecommendation": true,
|
||||
"conversationStarter": "欢迎语",
|
||||
"reasoningEnable": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 获取推荐问题
|
||||
```
|
||||
GET /api/ai-assistant/chatapp/{appId}/getRecommendQuestion?pageSize=3¤t=1
|
||||
```
|
||||
|
||||
响应格式:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"question": "推荐问题1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 发送消息(SSE流式)
|
||||
```
|
||||
POST /api/ai-assistant/chat/completions/message
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求体:
|
||||
```json
|
||||
{
|
||||
"chatId": "chat_id_or_null",
|
||||
"appId": "app_id",
|
||||
"equipment": "web",
|
||||
"messageTag": "AI_TAG",
|
||||
"body": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "用户消息"
|
||||
}
|
||||
],
|
||||
"channel": "web",
|
||||
"attachmentIds": [],
|
||||
"recommendQuestions": [],
|
||||
"variables": {},
|
||||
"reasoning": "false"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
SSE响应格式:
|
||||
```
|
||||
event: MESSAGE_DETAIL
|
||||
data: {
|
||||
"detailId": "detail_id",
|
||||
"chatId": "chat_id",
|
||||
"question": {
|
||||
"content": "用户问题",
|
||||
"role": "user"
|
||||
},
|
||||
"answer": {
|
||||
"content": "AI回答",
|
||||
"role": "assistant"
|
||||
},
|
||||
"status": "PROCESSING|FINISH|ERROR"
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 启动步骤
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
cd web_client
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. 配置环境变量
|
||||
|
||||
```bash
|
||||
# 复制配置文件
|
||||
cp .env.example .env
|
||||
|
||||
# 编辑配置
|
||||
vim .env
|
||||
```
|
||||
|
||||
### 3. 启动服务
|
||||
|
||||
```bash
|
||||
# 使用启动脚本
|
||||
./start.sh
|
||||
|
||||
# 或直接运行
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
### 4. 访问应用
|
||||
|
||||
打开浏览器访问:http://localhost:15000
|
||||
|
||||
## 📝 使用流程
|
||||
|
||||
1. **选择应用**
|
||||
- 页面加载后自动显示应用列表
|
||||
- 可以使用搜索框搜索应用
|
||||
- 点击应用卡片进入对话
|
||||
|
||||
2. **开始对话**
|
||||
- 进入对话界面后显示欢迎消息
|
||||
- 如果应用启用了推荐问题,会显示快捷回复按钮
|
||||
- 在输入框输入消息,按Enter或点击发送按钮
|
||||
|
||||
3. **查看回复**
|
||||
- AI回复以流式方式实时显示
|
||||
- 支持打字指示器动画
|
||||
- 自动滚动到最新消息
|
||||
|
||||
4. **返回应用列表**
|
||||
- 点击左上角返回按钮
|
||||
- 可以切换到其他应用
|
||||
|
||||
## 🔍 故障排查
|
||||
|
||||
### 问题1:应用列表加载失败
|
||||
|
||||
**原因**:后端服务未启动或地址配置错误
|
||||
|
||||
**解决**:
|
||||
1. 检查im-api服务是否启动
|
||||
2. 检查 `.env` 中的 `IM_API_BASE_URL` 配置
|
||||
3. 查看浏览器控制台和后端日志
|
||||
|
||||
### 问题2:消息发送失败
|
||||
|
||||
**原因**:认证失败或SSE连接问题
|
||||
|
||||
**解决**:
|
||||
1. 检查是否需要配置 `AUTH_TOKEN`
|
||||
2. 检查浏览器是否支持SSE
|
||||
3. 查看网络请求详情
|
||||
|
||||
### 问题3:推荐问题不显示
|
||||
|
||||
**原因**:应用未启用推荐或后端接口返回空
|
||||
|
||||
**解决**:
|
||||
1. 检查应用的 `enableRecommendation` 字段
|
||||
2. 检查后端推荐问题接口是否正常
|
||||
|
||||
## 📊 技术架构
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ 浏览器 │ ───> │ Flask代理层 │ ───> │ im-api后端 │
|
||||
│ (前端UI) │ <─── │ (web_client)│ <─── │ (聊天服务) │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
### 代理层功能
|
||||
|
||||
1. **请求转发**:将前端请求代理到im-api后端
|
||||
2. **SSE流式转发**:支持Server-Sent Events流式响应
|
||||
3. **错误处理**:统一的错误处理和日志记录
|
||||
4. **CORS支持**:解决跨域问题
|
||||
|
||||
## 🎯 下一步优化
|
||||
|
||||
1. **认证集成**
|
||||
- 集成真实的登录系统
|
||||
- 从登录获取JWT token
|
||||
- 支持token刷新
|
||||
|
||||
2. **功能增强**
|
||||
- 支持文件上传
|
||||
- 支持语音输入
|
||||
- 支持多会话管理
|
||||
- 支持历史记录
|
||||
|
||||
3. **性能优化**
|
||||
- 添加请求缓存
|
||||
- 优化SSE连接管理
|
||||
- 添加重连机制
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
如有问题,请联系:huazm@glodon.com
|
||||
|
||||
Binary file not shown.
+158
-87
@@ -1,27 +1,17 @@
|
||||
"""
|
||||
AI助手Web客户端 - Flask应用
|
||||
100%还原docs/ai-assistant.html原型设计
|
||||
对接im-api后端聊天服务
|
||||
@author huazm
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
from flask import Flask, render_template, request, jsonify, Response
|
||||
import requests
|
||||
from flask import Flask, render_template, request, jsonify, Response, stream_with_context
|
||||
from flask_cors import CORS
|
||||
|
||||
# 添加父目录到路径,以便导入api_client
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
try:
|
||||
from api_client import AIAssistantClient
|
||||
from config import APPLICATIONS, AI_ASSISTANT_CONFIG
|
||||
except ImportError:
|
||||
# 如果导入失败,使用模拟数据
|
||||
AIAssistantClient = None
|
||||
APPLICATIONS = {}
|
||||
AI_ASSISTANT_CONFIG = {}
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
@@ -34,14 +24,14 @@ app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = 'ai-assistant-web-client-secret-key'
|
||||
CORS(app) # 启用CORS
|
||||
|
||||
# 初始化API客户端
|
||||
api_client = None
|
||||
if AIAssistantClient:
|
||||
try:
|
||||
api_client = AIAssistantClient()
|
||||
logger.info("API客户端初始化成功")
|
||||
except Exception as e:
|
||||
logger.error(f"API客户端初始化失败: {e}")
|
||||
# API后端地址配置(api模块提供HTTP接口)
|
||||
API_BASE_URL = os.environ.get('API_BASE_URL', 'http://localhost:18090')
|
||||
# api模块的接口路径前缀是/api/ai-assistant
|
||||
API_PREFIX = '/api/ai-assistant'
|
||||
|
||||
# 认证token(实际使用时应该从登录获取)
|
||||
# 这里暂时使用环境变量或固定值
|
||||
AUTH_TOKEN = os.environ.get('AUTH_TOKEN', '')
|
||||
|
||||
|
||||
@app.route('/')
|
||||
@@ -50,81 +40,148 @@ def index():
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@app.route('/api/applications', methods=['GET'])
|
||||
def get_auth_headers():
|
||||
"""
|
||||
获取认证请求头
|
||||
从前端请求中获取Authorization头并传递给后端
|
||||
"""
|
||||
headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
# 优先使用前端传递的Authorization头
|
||||
auth_header = request.headers.get('Authorization')
|
||||
if auth_header:
|
||||
headers['Authorization'] = auth_header
|
||||
elif AUTH_TOKEN:
|
||||
# 如果前端没有传递,使用环境变量中的token
|
||||
headers['Authorization'] = f'Bearer {AUTH_TOKEN}'
|
||||
|
||||
return headers
|
||||
|
||||
|
||||
@app.route('/api/ai-assistant/chatapp', methods=['GET'])
|
||||
def get_applications():
|
||||
"""获取应用列表"""
|
||||
"""
|
||||
代理获取应用列表请求到api后端
|
||||
"""
|
||||
try:
|
||||
if api_client:
|
||||
apps = api_client.get_app_list()
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'message': 'success',
|
||||
'data': apps
|
||||
})
|
||||
else:
|
||||
# 返回模拟数据
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'message': 'success',
|
||||
'data': list(APPLICATIONS.values()) if APPLICATIONS else []
|
||||
})
|
||||
url = f'{API_BASE_URL}{API_PREFIX}/chatapp'
|
||||
logger.info(f"代理请求: GET {url}")
|
||||
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=get_auth_headers(),
|
||||
timeout=30
|
||||
)
|
||||
|
||||
logger.info(f"响应状态码: {response.status_code}")
|
||||
|
||||
# 直接返回后端响应
|
||||
return Response(
|
||||
response.content,
|
||||
status=response.status_code,
|
||||
content_type=response.headers.get('Content-Type', 'application/json')
|
||||
)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"代理请求失败: {e}")
|
||||
return jsonify({
|
||||
'code': 500,
|
||||
'message': f'代理请求失败: {str(e)}',
|
||||
'data': None
|
||||
}), 500
|
||||
except Exception as e:
|
||||
logger.error(f"获取应用列表失败: {e}")
|
||||
return jsonify({
|
||||
'code': 500,
|
||||
'message': str(e),
|
||||
'data': []
|
||||
'data': None
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/api/chat/send', methods=['POST'])
|
||||
@app.route('/api/ai-assistant/chatapp/<app_id>/getRecommendQuestion', methods=['GET'])
|
||||
def get_recommend_questions(app_id):
|
||||
"""
|
||||
代理获取推荐问题请求到api后端
|
||||
"""
|
||||
try:
|
||||
# 获取查询参数
|
||||
page_size = request.args.get('pageSize', '10')
|
||||
current = request.args.get('current', '1')
|
||||
|
||||
url = f'{API_BASE_URL}{API_PREFIX}/chatapp/{app_id}/getRecommendQuestion'
|
||||
params = {
|
||||
'pageSize': page_size,
|
||||
'current': current
|
||||
}
|
||||
|
||||
logger.info(f"代理请求: GET {url} with params {params}")
|
||||
|
||||
response = requests.get(
|
||||
url,
|
||||
params=params,
|
||||
headers=get_auth_headers(),
|
||||
timeout=30
|
||||
)
|
||||
|
||||
return Response(
|
||||
response.content,
|
||||
status=response.status_code,
|
||||
content_type=response.headers.get('Content-Type', 'application/json')
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取推荐问题失败: {e}")
|
||||
return jsonify({
|
||||
'code': 500,
|
||||
'message': str(e),
|
||||
'data': None
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/api/ai-assistant/chat/completions/message', methods=['POST'])
|
||||
def send_message():
|
||||
"""发送聊天消息"""
|
||||
"""
|
||||
代理发送消息请求到api后端(SSE流式响应)
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
app_id = data.get('appId')
|
||||
message = data.get('message')
|
||||
chat_id = data.get('chatId')
|
||||
stream = data.get('stream', False)
|
||||
|
||||
if not message:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '消息内容不能为空',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
if api_client and app_id:
|
||||
# 调用真实API
|
||||
response = api_client.send_message(
|
||||
app_id=app_id,
|
||||
message=message,
|
||||
chat_id=chat_id,
|
||||
stream=stream
|
||||
)
|
||||
|
||||
if stream:
|
||||
# 流式响应
|
||||
def generate():
|
||||
for chunk in response:
|
||||
yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
|
||||
yield "data: [DONE]\n\n"
|
||||
|
||||
return Response(generate(), mimetype='text/event-stream')
|
||||
else:
|
||||
# 普通响应
|
||||
return jsonify(response)
|
||||
else:
|
||||
# 返回模拟响应
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'message': 'success',
|
||||
'data': {
|
||||
'answer': f'这是对"{message}"的模拟回复。实际使用时会调用真实的AI助手API。',
|
||||
'chatId': chat_id or 'mock_chat_id_001',
|
||||
'messageId': 'mock_message_id_001'
|
||||
}
|
||||
})
|
||||
|
||||
url = f'{API_BASE_URL}{API_PREFIX}/chat/completions/message'
|
||||
logger.info(f"代理SSE请求: POST {url}")
|
||||
logger.info(f"请求体: {json.dumps(data, ensure_ascii=False)}")
|
||||
|
||||
# 发送POST请求并流式读取响应
|
||||
response = requests.post(
|
||||
url,
|
||||
json=data,
|
||||
headers=get_auth_headers(),
|
||||
stream=True,
|
||||
timeout=300 # 5分钟超时
|
||||
)
|
||||
|
||||
# 流式转发SSE响应
|
||||
def generate():
|
||||
try:
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
decoded_line = line.decode('utf-8')
|
||||
logger.debug(f"SSE数据: {decoded_line}")
|
||||
yield f"{decoded_line}\n"
|
||||
except Exception as e:
|
||||
logger.error(f"流式响应错误: {e}")
|
||||
yield f"event: error\ndata: {json.dumps({'error': str(e)})}\n\n"
|
||||
|
||||
return Response(
|
||||
stream_with_context(generate()),
|
||||
mimetype='text/event-stream',
|
||||
headers={
|
||||
'Cache-Control': 'no-cache',
|
||||
'X-Accel-Buffering': 'no'
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送消息失败: {e}")
|
||||
return jsonify({
|
||||
@@ -136,19 +193,33 @@ def send_message():
|
||||
|
||||
@app.route('/api/health', methods=['GET'])
|
||||
def health_check():
|
||||
"""健康检查"""
|
||||
"""
|
||||
健康检查
|
||||
"""
|
||||
# 检查api后端是否可用
|
||||
api_available = False
|
||||
try:
|
||||
response = requests.get(
|
||||
f'{API_BASE_URL}/actuator/health',
|
||||
timeout=5
|
||||
)
|
||||
api_available = response.status_code == 200
|
||||
except:
|
||||
pass
|
||||
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'message': 'OK',
|
||||
'data': {
|
||||
'status': 'healthy',
|
||||
'api_client_available': api_client is not None
|
||||
'api_available': api_available,
|
||||
'api_url': API_BASE_URL
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = int(os.environ.get('PORT', 5000))
|
||||
port = int(os.environ.get('PORT', 15000))
|
||||
debug = os.environ.get('DEBUG', 'True').lower() == 'true'
|
||||
|
||||
logger.info(f"启动AI助手Web客户端,端口: {port}, 调试模式: {debug}")
|
||||
|
||||
+9
-2
@@ -41,10 +41,17 @@ echo "========================================="
|
||||
echo " 启动Web服务器..."
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo "🌐 访问地址: http://localhost:5000"
|
||||
|
||||
# 读取配置
|
||||
PORT=${PORT:-15000}
|
||||
API_BASE_URL=${API_BASE_URL:-http://localhost:8080}
|
||||
|
||||
echo "🌐 访问地址: http://localhost:$PORT"
|
||||
echo "🔗 后端API: $API_BASE_URL"
|
||||
echo "📝 按 Ctrl+C 停止服务"
|
||||
echo ""
|
||||
|
||||
# 启动Flask应用
|
||||
export PORT=$PORT
|
||||
export API_BASE_URL=$API_BASE_URL
|
||||
python3 app.py
|
||||
|
||||
|
||||
+550
-113
@@ -1,15 +1,102 @@
|
||||
/**
|
||||
* AI助手Web客户端 - 前端交互逻辑
|
||||
* 100%还原原型设计的交互效果
|
||||
* 对接im-api后端聊天服务
|
||||
* @author huazm
|
||||
*/
|
||||
|
||||
// 全局变量
|
||||
let currentChatId = null;
|
||||
let currentAppId = 15; // 默认使用人岗匹配应用
|
||||
let currentAppId = null;
|
||||
let currentAppInfo = null;
|
||||
let allApps = [];
|
||||
let isTyping = false;
|
||||
let abortController = null; // 用于取消fetch请求
|
||||
let authToken = null; // 认证token
|
||||
|
||||
// API配置
|
||||
const API_BASE_URL = ''; // 使用相对路径
|
||||
const API_BASE_URL = '/api/ai-assistant'; // api后端地址
|
||||
|
||||
/**
|
||||
* Token管理功能
|
||||
*/
|
||||
// 打开Token设置模态框
|
||||
function openTokenModal() {
|
||||
const modal = document.getElementById('token-modal');
|
||||
const input = document.getElementById('token-input');
|
||||
const status = document.getElementById('token-status-modal');
|
||||
|
||||
// 加载已保存的token
|
||||
const savedToken = localStorage.getItem('authToken');
|
||||
if (savedToken) {
|
||||
input.value = savedToken;
|
||||
status.textContent = '已设置';
|
||||
status.className = 'font-medium text-green-400';
|
||||
} else {
|
||||
input.value = '';
|
||||
status.textContent = '未设置';
|
||||
status.className = 'font-medium text-slate-400';
|
||||
}
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 关闭Token设置模态框
|
||||
function closeTokenModal() {
|
||||
const modal = document.getElementById('token-modal');
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 保存Token
|
||||
function saveToken() {
|
||||
const input = document.getElementById('token-input');
|
||||
const token = input.value.trim();
|
||||
|
||||
if (token) {
|
||||
// 保存到localStorage
|
||||
localStorage.setItem('authToken', token);
|
||||
authToken = token;
|
||||
|
||||
// 更新状态显示
|
||||
const statusModal = document.getElementById('token-status-modal');
|
||||
statusModal.textContent = '已设置';
|
||||
statusModal.className = 'font-medium text-green-400';
|
||||
|
||||
console.log('Token已保存');
|
||||
|
||||
// 关闭模态框
|
||||
closeTokenModal();
|
||||
|
||||
// 重新加载应用列表
|
||||
loadApplications();
|
||||
} else {
|
||||
alert('请输入有效的Token');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取认证请求头
|
||||
function getAuthHeaders() {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// 从localStorage加载token
|
||||
const savedToken = localStorage.getItem('authToken');
|
||||
if (savedToken) {
|
||||
authToken = savedToken;
|
||||
headers['Authorization'] = `Bearer ${savedToken}`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
// 页面加载时初始化token
|
||||
function initToken() {
|
||||
const savedToken = localStorage.getItem('authToken');
|
||||
if (savedToken) {
|
||||
authToken = savedToken;
|
||||
console.log('已加载保存的Token');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动态粒子背景
|
||||
@@ -17,34 +104,279 @@ const API_BASE_URL = ''; // 使用相对路径
|
||||
function createParticles() {
|
||||
const particlesContainer = document.getElementById('particles');
|
||||
const particleCount = 50;
|
||||
|
||||
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const particle = document.createElement('div');
|
||||
particle.className = 'particle';
|
||||
|
||||
|
||||
// 随机位置和大小
|
||||
const size = Math.random() * 4 + 1;
|
||||
const posX = Math.random() * 100;
|
||||
const posY = Math.random() * 100;
|
||||
|
||||
|
||||
particle.style.width = `${size}px`;
|
||||
particle.style.height = `${size}px`;
|
||||
particle.style.left = `${posX}%`;
|
||||
particle.style.top = `${posY}%`;
|
||||
|
||||
|
||||
// 随机动画延迟
|
||||
particle.style.animationDelay = `${Math.random() * 5}s`;
|
||||
|
||||
|
||||
particlesContainer.appendChild(particle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载应用列表
|
||||
*/
|
||||
async function loadApplications() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/chatapp`, {
|
||||
method: 'GET',
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log('API返回数据:', result);
|
||||
|
||||
// 兼容两种数据格式:result.data 或 result.result
|
||||
const apps = result.data || result.result;
|
||||
|
||||
if (result.code === 200 && apps) {
|
||||
allApps = apps;
|
||||
// 按sortNum倒排
|
||||
allApps.sort((a, b) => {
|
||||
if (b.sortNum !== undefined && a.sortNum !== undefined) {
|
||||
return a.sortNum - b.sortNum; // 从小到大排序
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
renderApplications(allApps);
|
||||
} else {
|
||||
showError('加载应用列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载应用列表失败:', error);
|
||||
showError('加载应用列表失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染应用列表
|
||||
*/
|
||||
function renderApplications(apps) {
|
||||
const appList = document.getElementById('app-list');
|
||||
|
||||
if (!apps || apps.length === 0) {
|
||||
appList.innerHTML = `
|
||||
<div class="text-center text-slate-400 py-12 col-span-full">
|
||||
<i class="fas fa-inbox text-4xl mb-4"></i>
|
||||
<p>暂无可用的智能体</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
appList.innerHTML = apps.map(app => `
|
||||
<div class="app-card glass-effect bg-slate-800/50 p-6 rounded-xl border border-slate-700 hover:border-primary transition-all duration-300 cursor-pointer group"
|
||||
data-app-id="${app.id}"
|
||||
onclick="selectApplication('${app.id}')">
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="w-12 h-12 rounded-full bg-gradient-to-r from-primary to-secondary flex items-center justify-center text-white flex-shrink-0">
|
||||
${app.appAvatar ?
|
||||
`<img src="${app.appAvatar}" alt="${app.appName}" class="w-full h-full rounded-full object-cover">` :
|
||||
`<i class="fas fa-robot"></i>`
|
||||
}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-white font-semibold text-lg mb-1 truncate group-hover:text-primary transition-colors">
|
||||
${app.appName || '未命名应用'}
|
||||
</h3>
|
||||
<p class="text-slate-400 text-sm line-clamp-2 mb-2">
|
||||
${app.appDescription || app.scopeDescription || '暂无描述'}
|
||||
</p>
|
||||
${app.category ? `
|
||||
<span class="inline-block px-2 py-1 bg-primary/20 text-primary text-xs rounded-full">
|
||||
${app.category}
|
||||
</span>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索应用
|
||||
*/
|
||||
function searchApplications(keyword) {
|
||||
if (!keyword.trim()) {
|
||||
renderApplications(allApps);
|
||||
return;
|
||||
}
|
||||
|
||||
const filtered = allApps.filter(app => {
|
||||
const searchText = keyword.toLowerCase();
|
||||
return (app.appName && app.appName.toLowerCase().includes(searchText)) ||
|
||||
(app.appDescription && app.appDescription.toLowerCase().includes(searchText)) ||
|
||||
(app.scopeDescription && app.scopeDescription.toLowerCase().includes(searchText)) ||
|
||||
(app.category && app.category.toLowerCase().includes(searchText));
|
||||
});
|
||||
|
||||
renderApplications(filtered);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择应用并进入对话
|
||||
*/
|
||||
async function selectApplication(appId) {
|
||||
try {
|
||||
// 查找应用信息
|
||||
currentAppInfo = allApps.find(app => app.id === appId);
|
||||
if (!currentAppInfo) {
|
||||
showError('应用信息不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
currentAppId = appId;
|
||||
currentChatId = null; // 重置聊天ID,开始新对话
|
||||
|
||||
// 隐藏应用选择面板,显示对话面板
|
||||
document.getElementById('app-selector-panel').classList.add('hidden');
|
||||
document.getElementById('chat-panel').classList.remove('hidden');
|
||||
|
||||
// 更新顶部应用信息
|
||||
updateCurrentAppInfo();
|
||||
|
||||
// 清空对话内容
|
||||
clearChatContainer();
|
||||
|
||||
// 显示欢迎消息
|
||||
showWelcomeMessage();
|
||||
|
||||
// 加载快捷回复(如果有)
|
||||
loadQuickReplies();
|
||||
|
||||
} catch (error) {
|
||||
console.error('选择应用失败:', error);
|
||||
showError('选择应用失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新当前应用信息显示
|
||||
*/
|
||||
function updateCurrentAppInfo() {
|
||||
if (!currentAppInfo) return;
|
||||
|
||||
const appAvatar = document.getElementById('current-app-avatar');
|
||||
const appName = document.getElementById('current-app-name');
|
||||
const appDesc = document.getElementById('current-app-desc');
|
||||
|
||||
if (currentAppInfo.appAvatar) {
|
||||
appAvatar.src = currentAppInfo.appAvatar;
|
||||
appAvatar.style.display = 'block';
|
||||
} else {
|
||||
appAvatar.style.display = 'none';
|
||||
}
|
||||
|
||||
appName.textContent = currentAppInfo.appName || 'AI 助手';
|
||||
appDesc.textContent = currentAppInfo.appDescription || currentAppInfo.scopeDescription || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空对话容器
|
||||
*/
|
||||
function clearChatContainer() {
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
chatContainer.innerHTML = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示欢迎消息
|
||||
*/
|
||||
function showWelcomeMessage() {
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
const welcomeMsg = currentAppInfo.conversationStarter || '你好!我是' + (currentAppInfo.appName || 'AI助手') + ',有什么可以帮助你的吗?';
|
||||
|
||||
chatContainer.innerHTML = `
|
||||
<div class="flex justify-start slide-in">
|
||||
<div class="max-w-xs lg:max-w-md ai-bubble text-white p-4 rounded-2xl rounded-bl-md">
|
||||
<p>${welcomeMsg}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载快捷回复
|
||||
*/
|
||||
async function loadQuickReplies() {
|
||||
const quickReplyContainer = document.getElementById('quick-reply-container');
|
||||
const quickReplyButtons = document.getElementById('quick-reply-buttons');
|
||||
|
||||
// 如果应用启用了推荐问题
|
||||
if (currentAppInfo.enableRecommendation) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/chatapp/${currentAppId}/getRecommendQuestion?pageSize=3¤t=1`, {
|
||||
method: 'GET',
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200 && result.data && result.data.records && result.data.records.length > 0) {
|
||||
quickReplyButtons.innerHTML = result.data.records.map(q => `
|
||||
<button class="quick-reply-btn px-4 py-2 bg-slate-700 text-slate-300 rounded-lg text-sm hover:bg-slate-600 transition whitespace-nowrap"
|
||||
onclick="sendQuickReply('${q.question.replace(/'/g, "\\'")}')">
|
||||
<i class="fas fa-lightbulb mr-2"></i>${q.question}
|
||||
</button>
|
||||
`).join('');
|
||||
quickReplyContainer.classList.remove('hidden');
|
||||
} else {
|
||||
quickReplyContainer.classList.add('hidden');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载推荐问题失败:', error);
|
||||
quickReplyContainer.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
quickReplyContainer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回应用选择
|
||||
*/
|
||||
function backToApps() {
|
||||
// 取消正在进行的请求
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
abortController = null;
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
currentAppId = null;
|
||||
currentAppInfo = null;
|
||||
currentChatId = null;
|
||||
|
||||
// 切换面板
|
||||
document.getElementById('chat-panel').classList.add('hidden');
|
||||
document.getElementById('app-selector-panel').classList.remove('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加消息到聊天容器
|
||||
*/
|
||||
function addMessage(message, isUser = true) {
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
|
||||
|
||||
// 移除欢迎消息
|
||||
const welcomeDiv = chatContainer.querySelector('.flex.justify-center');
|
||||
if (welcomeDiv) {
|
||||
welcomeDiv.remove();
|
||||
}
|
||||
|
||||
// 移除打字指示器
|
||||
removeTypingIndicator();
|
||||
|
||||
@@ -119,62 +451,212 @@ function escapeHtml(text) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* 发送消息(使用SSE流式响应)
|
||||
*/
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById('message-input');
|
||||
const message = input.value.trim();
|
||||
|
||||
if (!message) return;
|
||||
|
||||
|
||||
if (!message || !currentAppId) return;
|
||||
|
||||
// 添加用户消息
|
||||
addMessage(message, true);
|
||||
input.value = '';
|
||||
|
||||
|
||||
// 显示打字指示器
|
||||
showTypingIndicator();
|
||||
|
||||
|
||||
try {
|
||||
// 调用API
|
||||
const response = await fetch(`${API_BASE_URL}/api/chat/send`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
appId: currentAppId,
|
||||
message: message,
|
||||
chatId: currentChatId,
|
||||
stream: false
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// 移除打字指示器
|
||||
removeTypingIndicator();
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
// 添加AI回复
|
||||
addMessage(data.data.answer, false);
|
||||
|
||||
// 更新chatId
|
||||
if (data.data.chatId) {
|
||||
currentChatId = data.data.chatId;
|
||||
// 构建请求体
|
||||
const requestBody = {
|
||||
chatId: currentChatId,
|
||||
appId: currentAppId,
|
||||
equipment: 'web',
|
||||
messageTag: 'AI_TAG',
|
||||
body: {
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: message
|
||||
}
|
||||
],
|
||||
channel: 'web',
|
||||
attachmentIds: [],
|
||||
recommendQuestions: [],
|
||||
variables: {},
|
||||
reasoning: currentAppInfo.reasoningEnable ? 'true' : 'false'
|
||||
}
|
||||
} else {
|
||||
addMessage(`错误: ${data.message || '未知错误'}`, false);
|
||||
};
|
||||
|
||||
// 使用fetch进行SSE连接(支持自定义请求头)
|
||||
const url = `${API_BASE_URL}/chat/completions/message`;
|
||||
|
||||
// 取消之前的请求
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
}
|
||||
|
||||
// 创建新的AbortController
|
||||
abortController = new AbortController();
|
||||
|
||||
// 使用fetch发送POST请求,处理流式响应
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: abortController.signal
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
let aiMessage = '';
|
||||
let messageDiv = null;
|
||||
|
||||
// 读取流式响应
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 解码数据
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
// 按行分割
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || ''; // 保留最后一个不完整的行
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.substring(6).trim();
|
||||
|
||||
if (data === '[DONE]') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
|
||||
// 处理不同类型的事件
|
||||
if (json.detailId) {
|
||||
// 保存chatId
|
||||
if (json.chatId && !currentChatId) {
|
||||
currentChatId = json.chatId;
|
||||
}
|
||||
|
||||
// 更新AI回复
|
||||
if (json.answer && json.answer.content) {
|
||||
aiMessage = json.answer.content;
|
||||
|
||||
// 移除打字指示器
|
||||
removeTypingIndicator();
|
||||
|
||||
// 更新或创建消息气泡
|
||||
if (!messageDiv) {
|
||||
messageDiv = createAIMessageBubble(aiMessage);
|
||||
} else {
|
||||
updateAIMessageBubble(messageDiv, aiMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (json.status === 'FINISH' || json.status === 'ERROR') {
|
||||
if (json.status === 'ERROR') {
|
||||
showError('AI回复出错');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('解析SSE数据失败:', parseError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保移除打字指示器
|
||||
removeTypingIndicator();
|
||||
|
||||
} catch (error) {
|
||||
console.error('发送消息失败:', error);
|
||||
removeTypingIndicator();
|
||||
addMessage('抱歉,发送消息失败,请稍后重试。', false);
|
||||
showError('发送消息失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建AI消息气泡
|
||||
*/
|
||||
function createAIMessageBubble(content) {
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = 'flex justify-start slide-in';
|
||||
messageDiv.innerHTML = `
|
||||
<div class="max-w-xs lg:max-w-md ai-bubble text-white p-4 rounded-2xl rounded-bl-md">
|
||||
<p class="ai-message-content">${escapeHtml(content)}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
chatContainer.appendChild(messageDiv);
|
||||
scrollToBottom();
|
||||
|
||||
return messageDiv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新AI消息气泡
|
||||
*/
|
||||
function updateAIMessageBubble(messageDiv, content) {
|
||||
const contentElement = messageDiv.querySelector('.ai-message-content');
|
||||
if (contentElement) {
|
||||
contentElement.innerHTML = escapeHtml(content);
|
||||
scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误消息
|
||||
*/
|
||||
function showError(message) {
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'flex justify-center my-4';
|
||||
errorDiv.innerHTML = `
|
||||
<div class="bg-red-500/20 border border-red-500 text-red-300 px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-exclamation-circle mr-2"></i>${escapeHtml(message)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
chatContainer.appendChild(errorDiv);
|
||||
scrollToBottom();
|
||||
|
||||
// 3秒后自动移除
|
||||
setTimeout(() => {
|
||||
errorDiv.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷回复按钮点击
|
||||
*/
|
||||
function sendQuickReply(text) {
|
||||
const input = document.getElementById('message-input');
|
||||
input.value = text;
|
||||
sendMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷回复填充(不发送)
|
||||
*/
|
||||
function handleQuickReply(text) {
|
||||
const input = document.getElementById('message-input');
|
||||
input.value = text;
|
||||
@@ -217,35 +699,8 @@ function changeFont(font) {
|
||||
*/
|
||||
function startNewChat() {
|
||||
currentChatId = null;
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
|
||||
// 清空聊天记录(保留欢迎消息)
|
||||
const messages = chatContainer.querySelectorAll('.slide-in');
|
||||
messages.forEach(msg => msg.remove());
|
||||
|
||||
// 添加欢迎消息
|
||||
addMessage('您好!我是AI助手,有什么可以帮助您的吗?', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 待办事项管理
|
||||
*/
|
||||
function addTodoItem() {
|
||||
const input = document.getElementById('todo-input');
|
||||
const text = input.value.trim();
|
||||
|
||||
if (!text) return;
|
||||
|
||||
const todoList = document.getElementById('todo-list');
|
||||
const todoItem = document.createElement('div');
|
||||
todoItem.className = 'flex items-center space-x-2 p-2 bg-slate-800/30 rounded-lg';
|
||||
todoItem.innerHTML = `
|
||||
<input type="checkbox" class="rounded text-primary">
|
||||
<span class="text-slate-300 text-sm">${escapeHtml(text)}</span>
|
||||
`;
|
||||
|
||||
todoList.appendChild(todoItem);
|
||||
input.value = '';
|
||||
clearChatContainer();
|
||||
showWelcomeMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,6 +712,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 创建粒子背景
|
||||
createParticles();
|
||||
|
||||
// 加载应用列表
|
||||
loadApplications();
|
||||
|
||||
// 绑定应用搜索
|
||||
const searchInput = document.getElementById('app-search-input');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function(e) {
|
||||
searchApplications(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定返回按钮
|
||||
const backButton = document.getElementById('back-to-apps-btn');
|
||||
if (backButton) {
|
||||
backButton.addEventListener('click', backToApps);
|
||||
}
|
||||
|
||||
// 绑定发送按钮事件
|
||||
const sendButton = document.getElementById('send-button');
|
||||
if (sendButton) {
|
||||
@@ -274,27 +746,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定快捷回复按钮
|
||||
const quickReplyButtons = document.querySelectorAll('.quick-reply-btn');
|
||||
quickReplyButtons.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const text = this.textContent.trim();
|
||||
// 移除图标文本
|
||||
const cleanText = text.replace(/[💡📅✅]/g, '').trim();
|
||||
handleQuickReply(cleanText);
|
||||
});
|
||||
});
|
||||
|
||||
// 绑定主题切换按钮
|
||||
const themeButtons = document.querySelectorAll('.theme-btn');
|
||||
themeButtons.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const theme = this.dataset.theme;
|
||||
changeTheme(theme);
|
||||
});
|
||||
});
|
||||
|
||||
// 绑定字体选择
|
||||
// 绑定字体切换
|
||||
const fontSelect = document.getElementById('font-select');
|
||||
if (fontSelect) {
|
||||
fontSelect.addEventListener('change', function() {
|
||||
@@ -302,23 +754,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定待办事项添加
|
||||
const todoAddButton = document.getElementById('todo-add-btn');
|
||||
const todoInput = document.getElementById('todo-input');
|
||||
if (todoAddButton && todoInput) {
|
||||
todoAddButton.addEventListener('click', addTodoItem);
|
||||
todoInput.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addTodoItem();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加欢迎消息
|
||||
setTimeout(() => {
|
||||
addMessage('您好!我是AI助手,有什么可以帮助您的吗?', false);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// 初始化Token
|
||||
initToken();
|
||||
|
||||
console.log('AI助手Web客户端初始化完成');
|
||||
});
|
||||
+108
-118
@@ -45,152 +45,100 @@
|
||||
<div class="particles" id="particles"></div>
|
||||
|
||||
<div class="flex h-screen">
|
||||
<!-- 左侧用户信息面板 -->
|
||||
<div class="w-80 bg-slate-900/80 glass-effect border-r border-slate-700 flex flex-col">
|
||||
<div class="p-6 border-b border-slate-700">
|
||||
<h2 class="text-xl font-bold text-white mb-4">用户画像</h2>
|
||||
<div class="flex items-center space-x-3 mb-4">
|
||||
<div class="w-16 h-16 rounded-full bg-gradient-to-r from-primary to-secondary flex items-center justify-center text-white text-xl font-bold">
|
||||
U
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-white font-medium">张伟明</p>
|
||||
<p class="text-slate-400 text-sm">AI 助手用户</p>
|
||||
</div>
|
||||
<!-- 应用选择面板(初始显示) -->
|
||||
<div id="app-selector-panel" class="w-full bg-slate-900/80 glass-effect flex flex-col">
|
||||
<div class="p-6 border-b border-slate-700 flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-white mb-2 font-['Pacifico']">AI 助手</h1>
|
||||
<p class="text-slate-400">选择一个智能体开始对话</p>
|
||||
</div>
|
||||
|
||||
<!-- 个性化推荐 -->
|
||||
<div class="mt-4 p-3 bg-slate-800/50 rounded-lg">
|
||||
<h3 class="text-sm font-medium text-white mb-2">个性化推荐</h3>
|
||||
<p class="text-slate-300 text-xs">根据您的使用习惯,推荐以下功能</p>
|
||||
<div class="mt-2 flex flex-wrap gap-1">
|
||||
<span class="px-2 py-1 bg-primary/20 text-primary text-xs rounded-full">智能问答</span>
|
||||
<span class="px-2 py-1 bg-secondary/20 text-secondary text-xs rounded-full">语音识别</span>
|
||||
<span class="px-2 py-1 bg-blue-500/20 text-blue-400 text-xs rounded-full">任务管理</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 待办事项 -->
|
||||
<div class="p-6 border-b border-slate-700">
|
||||
<h3 class="text-lg font-semibold text-white mb-4">待办事项</h3>
|
||||
<div class="space-y-3" id="todo-list">
|
||||
<div class="flex items-center space-x-2 p-2 bg-slate-800/30 rounded-lg">
|
||||
<input type="checkbox" class="rounded text-primary">
|
||||
<span class="text-slate-300 text-sm">完成项目报告</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 p-2 bg-slate-800/30 rounded-lg">
|
||||
<input type="checkbox" class="rounded text-primary">
|
||||
<span class="text-slate-300 text-sm">准备会议材料</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 p-2 bg-slate-800/30 rounded-lg">
|
||||
<input type="checkbox" class="rounded text-primary">
|
||||
<span class="text-slate-300 text-sm">回复客户邮件</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex">
|
||||
<input type="text" id="todo-input" placeholder="添加新任务..." class="flex-1 bg-slate-800 text-white text-sm px-3 py-2 rounded-l-lg border border-slate-600 focus:outline-none focus:border-primary">
|
||||
<button id="todo-add-btn" class="bg-primary text-white px-3 py-2 rounded-r-lg hover:bg-primary/80 transition">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提醒设置 -->
|
||||
<div class="p-6 flex-1">
|
||||
<h3 class="text-lg font-semibold text-white mb-4">提醒设置</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="p-3 bg-slate-800/30 rounded-lg">
|
||||
<p class="text-slate-300 text-sm">每日 9:00 AM</p>
|
||||
<p class="text-white text-sm">晨会准备</p>
|
||||
</div>
|
||||
<div class="p-3 bg-slate-800/30 rounded-lg">
|
||||
<p class="text-slate-300 text-sm">每周三 2:00 PM</p>
|
||||
<p class="text-white text-sm">团队同步会议</p>
|
||||
</div>
|
||||
<div class="p-3 bg-slate-800/30 rounded-lg">
|
||||
<p class="text-slate-300 text-sm">每月 15 日</p>
|
||||
<p class="text-white text-sm">月度总结报告</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="w-full mt-4 bg-slate-700 text-white py-2 rounded-lg hover:bg-slate-600 transition flex items-center justify-center space-x-2">
|
||||
<i class="fas fa-bell"></i>
|
||||
<span>设置新提醒</span>
|
||||
<!-- 设置按钮 -->
|
||||
<button
|
||||
onclick="openTokenModal()"
|
||||
class="px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white rounded-md transition-colors flex items-center gap-2"
|
||||
title="设置认证Token"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
设置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<div class="p-6 border-b border-slate-700 flex justify-center">
|
||||
<div class="relative w-[30%]">
|
||||
<input
|
||||
type="text"
|
||||
id="app-search-input"
|
||||
placeholder="搜索智能体..."
|
||||
class="w-full bg-slate-800 text-white px-4 py-3 pl-12 rounded-xl border border-slate-600 focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-300"
|
||||
>
|
||||
<i class="fas fa-search absolute left-4 top-1/2 transform -translate-y-1/2 text-slate-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 应用列表 -->
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div id="app-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<!-- 应用卡片将通过JavaScript动态加载 -->
|
||||
<div class="text-center text-slate-400 py-12 col-span-full">
|
||||
<i class="fas fa-spinner fa-spin text-4xl mb-4"></i>
|
||||
<p>正在加载智能体列表...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主对话区域 -->
|
||||
<div class="flex-1 flex flex-col">
|
||||
|
||||
<!-- 主对话区域(初始隐藏) -->
|
||||
<div id="chat-panel" class="flex-1 flex flex-col hidden">
|
||||
<!-- 顶部导航栏 -->
|
||||
<div class="h-16 bg-slate-900/80 glass-effect border-b border-slate-700 flex items-center justify-between px-6 relative">
|
||||
<div class="flex items-center space-x-4">
|
||||
<h1 class="text-xl font-bold text-white font-['Pacifico']">AI 助手</h1>
|
||||
<div class="flex space-x-2">
|
||||
<button class="theme-btn px-3 py-1 bg-slate-700 text-white rounded-lg text-sm hover:bg-slate-600 transition" data-theme="dark">深空黑</button>
|
||||
<button class="theme-btn px-3 py-1 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-500 transition" data-theme="blue">极光蓝</button>
|
||||
<button class="theme-btn px-3 py-1 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-500 transition" data-theme="purple">霓虹紫</button>
|
||||
<button class="theme-btn px-3 py-1 bg-green-600 text-white rounded-lg text-sm hover:bg-green-500 transition" data-theme="green">科技绿</button>
|
||||
<!-- 返回按钮 -->
|
||||
<button id="back-to-apps-btn" class="text-white p-2 hover:bg-slate-700 rounded-lg transition">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</button>
|
||||
<!-- 当前应用信息 -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<img id="current-app-avatar" src="" alt="" class="w-10 h-10 rounded-full bg-slate-700">
|
||||
<div>
|
||||
<h1 id="current-app-name" class="text-lg font-bold text-white">AI 助手</h1>
|
||||
<p id="current-app-desc" class="text-xs text-slate-400"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 字体选择 -->
|
||||
<!-- 右侧工具栏 -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<select id="font-select" class="bg-slate-800 text-white px-3 py-1 rounded-lg border border-slate-600 focus:outline-none focus:border-primary">
|
||||
<select id="font-select" class="bg-slate-800 text-white px-3 py-1 rounded-lg border border-slate-600 focus:outline-none focus:border-primary text-sm">
|
||||
<option value="'Inter', sans-serif">默认字体</option>
|
||||
<option value="'Source Han Sans CN', sans-serif">思源黑体</option>
|
||||
<option value="'PingFang SC', sans-serif">苹方</option>
|
||||
<option value="'Roboto', sans-serif">Roboto</option>
|
||||
</select>
|
||||
|
||||
<!-- 消息通知 -->
|
||||
<div class="relative">
|
||||
<button class="text-white p-2 hover:bg-slate-700 rounded-lg transition relative">
|
||||
<i class="fas fa-bell text-lg"></i>
|
||||
<div class="notification-badge">3</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="w-8 h-8 bg-gradient-to-r from-primary to-secondary rounded-full flex items-center justify-center text-white">
|
||||
<i class="fas fa-user text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 智能回复提示 -->
|
||||
<div class="p-4 bg-slate-800/50 border-b border-slate-700">
|
||||
<div class="flex space-x-3">
|
||||
<button class="quick-reply-btn px-4 py-2 bg-slate-700 text-slate-300 rounded-lg text-sm hover:bg-slate-600 transition whitespace-nowrap">
|
||||
<i class="fas fa-lightbulb mr-2"></i>今日天气如何?
|
||||
</button>
|
||||
<button class="quick-reply-btn px-4 py-2 bg-slate-700 text-slate-300 rounded-lg text-sm hover:bg-slate-600 transition whitespace-nowrap">
|
||||
<i class="fas fa-calendar mr-2"></i>明天的会议安排
|
||||
</button>
|
||||
<button class="quick-reply-btn px-4 py-2 bg-slate-700 text-slate-300 rounded-lg text-sm hover:bg-slate-600 transition whitespace-nowrap">
|
||||
<i class="fas fa-tasks mr-2"></i>帮我制定工作计划
|
||||
</button>
|
||||
<!-- 智能回复提示(动态加载) -->
|
||||
<div id="quick-reply-container" class="p-4 bg-slate-800/50 border-b border-slate-700 hidden">
|
||||
<div id="quick-reply-buttons" class="flex space-x-3 overflow-x-auto">
|
||||
<!-- 快捷回复按钮将通过JavaScript动态加载 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 对话内容区域 -->
|
||||
<div class="flex-1 overflow-y-auto p-6 space-y-4" id="chat-container">
|
||||
<!-- 用户消息示例 -->
|
||||
<div class="flex justify-end slide-in">
|
||||
<div class="max-w-xs lg:max-w-md user-bubble text-white p-4 rounded-2xl rounded-br-md">
|
||||
<p>你好,我想了解一下今天的工作安排。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 回复示例 -->
|
||||
<div class="flex justify-start slide-in">
|
||||
<div class="max-w-xs lg:max-w-md ai-bubble text-white p-4 rounded-2xl rounded-bl-md">
|
||||
<p>您好!根据您的日程安排,今天有以下任务:</p>
|
||||
<ul class="mt-2 space-y-1 text-sm">
|
||||
<li>• 9:00 AM - 晨会准备</li>
|
||||
<li>• 10:30 AM - 客户电话会议</li>
|
||||
<li>• 2:00 PM - 项目进度汇报</li>
|
||||
<li>• 4:00 PM - 团队协作讨论</li>
|
||||
</ul>
|
||||
<!-- 欢迎消息 -->
|
||||
<div class="flex justify-center">
|
||||
<div class="text-center text-slate-400 py-8">
|
||||
<i class="fas fa-comments text-4xl mb-4"></i>
|
||||
<p id="welcome-message">开始对话吧!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,6 +196,48 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Token设置模态框 -->
|
||||
<div id="token-modal" class="hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center">
|
||||
<div class="bg-slate-800 rounded-lg p-6 w-full max-w-md mx-4 shadow-2xl">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold text-white">设置认证Token</h2>
|
||||
<button onclick="closeTokenModal()" class="text-slate-400 hover:text-white transition">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
||||
Token(自动添加Bearer前缀)
|
||||
</label>
|
||||
<textarea
|
||||
id="token-input"
|
||||
rows="4"
|
||||
placeholder="粘贴您的认证token..."
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent resize-none"
|
||||
></textarea>
|
||||
<p class="mt-2 text-xs text-slate-400">
|
||||
当前状态: <span id="token-status-modal" class="font-medium">未设置</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
onclick="saveToken()"
|
||||
class="flex-1 px-4 py-2 bg-primary hover:bg-primary/80 text-white rounded-md transition-colors"
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
<button
|
||||
onclick="closeTokenModal()"
|
||||
class="px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white rounded-md transition-colors"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
</body>
|
||||
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试api后端连接
|
||||
@author huazm
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
import json
|
||||
|
||||
# 配置
|
||||
API_BASE_URL = os.environ.get('API_BASE_URL', 'http://localhost:8080')
|
||||
API_PREFIX = '/api/ai-assistant'
|
||||
|
||||
def test_health():
|
||||
"""测试健康检查"""
|
||||
print("=" * 60)
|
||||
print("测试1: 健康检查")
|
||||
print("=" * 60)
|
||||
try:
|
||||
url = f'{API_BASE_URL}/actuator/health'
|
||||
print(f"请求: GET {url}")
|
||||
response = requests.get(url, timeout=5)
|
||||
print(f"状态码: {response.status_code}")
|
||||
print(f"响应: {response.text}")
|
||||
return response.status_code == 200
|
||||
except Exception as e:
|
||||
print(f"❌ 错误: {e}")
|
||||
return False
|
||||
|
||||
def test_get_applications():
|
||||
"""测试获取应用列表"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试2: 获取应用列表")
|
||||
print("=" * 60)
|
||||
try:
|
||||
url = f'{API_BASE_URL}{API_PREFIX}/chatapp'
|
||||
print(f"请求: GET {url}")
|
||||
response = requests.get(url, timeout=10)
|
||||
print(f"状态码: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"响应数据结构: {json.dumps(data, ensure_ascii=False, indent=2)[:500]}...")
|
||||
|
||||
if data.get('code') == 200 and data.get('data'):
|
||||
apps = data['data']
|
||||
print(f"\n✅ 成功获取 {len(apps)} 个应用")
|
||||
if apps:
|
||||
print(f"\n第一个应用示例:")
|
||||
print(json.dumps(apps[0], ensure_ascii=False, indent=2))
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 响应格式不正确")
|
||||
return False
|
||||
else:
|
||||
print(f"❌ 请求失败: {response.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 错误: {e}")
|
||||
return False
|
||||
|
||||
def test_get_recommend_questions(app_id):
|
||||
"""测试获取推荐问题"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试3: 获取推荐问题")
|
||||
print("=" * 60)
|
||||
try:
|
||||
url = f'{API_BASE_URL}{API_PREFIX}/chatapp/{app_id}/getRecommendQuestion'
|
||||
params = {'pageSize': 3, 'current': 1}
|
||||
print(f"请求: GET {url}")
|
||||
print(f"参数: {params}")
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
print(f"状态码: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"响应: {json.dumps(data, ensure_ascii=False, indent=2)[:500]}...")
|
||||
|
||||
if data.get('code') == 200:
|
||||
records = data.get('data', {}).get('records', [])
|
||||
print(f"\n✅ 成功获取 {len(records)} 个推荐问题")
|
||||
for i, q in enumerate(records, 1):
|
||||
print(f" {i}. {q.get('question', 'N/A')}")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ 响应code不为200")
|
||||
return False
|
||||
else:
|
||||
print(f"❌ 请求失败: {response.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 错误: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("\n" + "🔍 " * 20)
|
||||
print("AI助手Web客户端 - API连接测试")
|
||||
print("🔍 " * 20)
|
||||
print(f"\n后端地址: {API_BASE_URL}")
|
||||
print(f"API前缀: {API_PREFIX}\n")
|
||||
|
||||
results = []
|
||||
|
||||
# 测试1: 健康检查
|
||||
results.append(("健康检查", test_health()))
|
||||
|
||||
# 测试2: 获取应用列表
|
||||
apps_ok = test_get_applications()
|
||||
results.append(("获取应用列表", apps_ok))
|
||||
|
||||
# 测试3: 获取推荐问题(使用第一个应用)
|
||||
if apps_ok:
|
||||
# 尝试获取第一个应用的ID
|
||||
try:
|
||||
url = f'{API_BASE_URL}{API_PREFIX}/chatapp'
|
||||
response = requests.get(url, timeout=10)
|
||||
data = response.json()
|
||||
if data.get('code') == 200 and data.get('data'):
|
||||
first_app_id = data['data'][0]['id']
|
||||
results.append(("获取推荐问题", test_get_recommend_questions(first_app_id)))
|
||||
else:
|
||||
results.append(("获取推荐问题", False))
|
||||
except:
|
||||
results.append(("获取推荐问题", False))
|
||||
else:
|
||||
results.append(("获取推荐问题", False))
|
||||
|
||||
# 打印测试结果
|
||||
print("\n" + "=" * 60)
|
||||
print("测试结果汇总")
|
||||
print("=" * 60)
|
||||
for name, success in results:
|
||||
status = "✅ 通过" if success else "❌ 失败"
|
||||
print(f"{name:20s} {status}")
|
||||
|
||||
all_passed = all(result[1] for result in results)
|
||||
print("\n" + "=" * 60)
|
||||
if all_passed:
|
||||
print("🎉 所有测试通过!Web客户端可以正常使用。")
|
||||
else:
|
||||
print("⚠️ 部分测试失败,请检查后端服务配置。")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
return 0 if all_passed else 1
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user