Python项目-智能聊天机器人开发与实现

引言

在人工智能迅速发展的今天,聊天机器人已经成为企业与用户交互的重要工具。一个智能聊天机器人不仅能够提高客户服务效率,还能为用户提供24/7的即时响应。本文将详细介绍如何使用Python开发一个智能聊天机器人,从基础概念到实际部署,全面覆盖开发过程中的关键步骤和技术要点。

聊天机器人概述

聊天机器人(Chatbot)是一种能够模拟人类对话的计算机程序。根据实现方式的不同,聊天机器人可以分为以下几类:

  • 基于规则的聊天机器人:通过预设的规则和模式匹配来响应用户输入
  • 检索式聊天机器人:从已有的回复库中选择最合适的回答
  • 生成式聊天机器人:能够生成新的、上下文相关的回复
  • 混合型聊天机器人:结合上述多种方法的优点

本项目将重点关注混合型聊天机器人的开发,以获得更好的用户体验和更高的智能水平。

技术栈选择

为了开发一个功能完善的智能聊天机器人,我们需要选择合适的技术栈:

  • 编程语言:Python(3.8+)
  • 自然语言处理:NLTK, spaCy, Transformers
  • 机器学习框架:TensorFlow/PyTorch
  • 对话管理:Rasa
  • Web框架:Flask/FastAPI
  • 数据库:MongoDB/SQLite
  • 部署:Docker, AWS/Azure

这些技术的选择基于它们在NLP领域的成熟度、社区支持以及与Python的良好集成性。

开发环境搭建

首先,我们需要搭建一个适合聊天机器人开发的环境:

# 创建虚拟环境
python -m venv chatbot_env
source chatbot_env/bin/activate  # Linux/Mac
# 或
chatbot_env\Scripts\activate  # Windows

# 安装必要的包
pip install nltk spacy transformers tensorflow flask pymongo python-dotenv

# 下载必要的语言模型
python -m spacy download zh_core_web_sm  # 中文模型
python -m nltk.downloader punkt wordnet stopwords

基础架构设计

一个完整的聊天机器人系统通常包含以下组件:

  1. 用户接口层:负责接收用户输入并展示机器人回复
  2. 自然语言理解(NLU)模块:解析用户意图和提取关键实体
  3. 对话管理系统:维护对话状态,决定下一步行动
  4. 知识库:存储机器人可以访问的信息
  5. 自然语言生成(NLG)模块:生成自然、流畅的回复

下面是一个简化的架构图:

用户 <-> 用户接口 <-> NLU模块 <-> 对话管理系统 <-> 知识库
                               |
                               v
                           NLG模块

自然语言处理实现

意图识别

意图识别是理解用户想要做什么的关键步骤。我们可以使用基于Transformer的模型来实现:

from transformers import BertTokenizer, BertForSequenceClassification
import torch

# 加载预训练模型和分词器
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=10)  # 假设有10种意图

def predict_intent(text):
    inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True)
    outputs = model(**inputs)
    predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
    return predictions.argmax().item()

实体提取

实体提取帮助我们识别用户输入中的关键信息:

import spacy

# 加载中文模型
nlp = spacy.load("zh_core_web_sm")

def extract_entities(text):
    doc = nlp(text)
    entities = {}
    for ent in doc.ents:
        entities[ent.label_] = ent.text
    return entities

对话管理系统

对话管理系统负责维护对话状态并决定下一步行动。我们可以使用状态机或基于规则的系统,也可以使用更复杂的基于机器学习的方法:

class DialogManager:
    def __init__(self):
        self.state = "greeting"
        self.context = {}
    
    def update_state(self, intent, entities):
        if self.state == "greeting":
            if intent == "query_info":
                self.state = "providing_info"
                self.context.update(entities)
            elif intent == "greeting":
                self.state = "greeting"
            else:
                self.state = "fallback"
        elif self.state == "providing_info":
            # 处理提供信息状态下的各种意图
            pass
        # 其他状态处理...
        
    def get_response(self):
        if self.state == "greeting":
            return "您好!我是智能助手,有什么可以帮您的吗?"
        elif self.state == "providing_info":
            # 根据context生成相应的信息回复
            return f"关于{self.context.get('topic', '您询问的问题')},我可以告诉您..."
        elif self.state == "fallback":
            return "抱歉,我没有理解您的意思,能否换个方式表达?"
        # 其他回复...

知识库构建

知识库是聊天机器人回答专业问题的基础。我们可以使用向量数据库来存储和检索信息:

from sentence_transformers import SentenceTransformer
import faiss
import numpy as np

class KnowledgeBase:
    def __init__(self):
        self.model = SentenceTransformer('distiluse-base-multilingual-cased')
        self.documents = []
        self.index = None
        
    def add_documents(self, documents):
        self.documents.extend(documents)
        embeddings = self.model.encode(documents)
        dimension = embeddings.shape[1]
        
        if self.index is None:
            self.index = faiss.IndexFlatL2(dimension)
        
        self.index.add(np.array(embeddings).astype('float32'))
    
    def query(self, question, top_k=3):
        question_embedding = self.model.encode([question])
        distances, indices = self.index.search(np.array(question_embedding).astype('float32'), top_k)
        
        results = []
        for idx in indices[0]:
            if idx < len(self.documents):
                results.append(self.documents[idx])
        
        return results

部署与优化

Web服务部署

使用Flask创建一个简单的API接口:

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/chat', methods=['POST'])
def chat():
    user_message = request.json.get('message', '')
    
    # 处理用户消息
    intent = predict_intent(user_message)
    entities = extract_entities(user_message)
    
    # 更新对话状态
    dialog_manager.update_state(intent, entities)
    
    # 获取回复
    response = dialog_manager.get_response()
    
    return jsonify({'response': response})

if __name__ == '__main__':
    app.run(debug=True)

Docker容器化

创建Dockerfile实现容器化部署:

FROM python:3.8-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

性能评估

评估聊天机器人性能的常用指标包括:

  • 准确率:正确回答的比例
  • 召回率:能够回答的问题比例
  • 平均响应时间:生成回复所需的平均时间
  • 用户满意度:通过用户反馈收集
  • 对话完成率:成功完成用户意图的比例

可以通过A/B测试和用户调查来收集这些指标。

未来展望

智能聊天机器人技术仍在快速发展,未来可能的发展方向包括:

  • 多模态交互:结合语音、图像等多种交互方式
  • 情感识别:理解并回应用户的情感状态
  • 个性化定制:根据用户偏好调整回复风格
  • 持续学习:从对话中不断学习和改进
  • 跨语言能力:支持多语言交互和翻译

结论

开发一个智能聊天机器人是一个复杂但有价值的项目。通过合理的架构设计、先进的NLP技术和持续的优化改进,可以构建出一个既实用又智能的对话系统。随着AI技术的不断进步,聊天机器人的能力将会越来越强大,应用场景也会更加广泛。

希望本文能为您的聊天机器人开发提供有益的指导和参考。

源代码

Directory Content Summary

Source Directory: ./chatbot_system

Directory Structure

chatbot_system/
  app.py
  app_with_kb.py
  database.py
  dialog_manager.py
  knowledge_base.py
  knowledge_base_api.py
  knowledge_base_db.py
  knowledge_base_integration.py
  README.md
  requirements.txt
  test_chatbot.py
  config/
    dialog_policies.json
    response_templates.json
  knowledge/
    knowledge_base.json
  static/
    index.html
    knowledge_base.html
    css/
      knowledge_base.css
      styles.css
    js/
      app.js
      knowledge_base.js

File Contents

app.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
聊天机器人系统主应用
提供Web API接口,连接前端、对话管理系统和NLP模块
"""

import os
import json
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

# 导入自定义模块
from dialog_manager import DialogManager
from knowledge_base import KnowledgeBase
from database import ChatbotDatabase

# 创建Flask应用
app = Flask(__name__, static_folder='static')
CORS(app)  # 启用跨域请求支持

# 初始化组件
db = ChatbotDatabase()
knowledge_base = KnowledgeBase()
dialog_manager = DialogManager(db, knowledge_base)

# 加载知识库数据
knowledge_base.load_from_directory('knowledge')

# 路由:首页
@app.route('/')
def index():
    return send_from_directory('static', 'index.html')

# 路由:聊天API
@app.route('/api/chat', methods=['POST'])
def chat():
    data = request.json
    user_id = data.get('user_id', 'default_user')
    message = data.get('message', '')
    
    if not message:
        return jsonify({
            'status': 'error',
            'message': '消息不能为空'
        }), 400
    
    # 处理用户消息并获取回复
    response = dialog_manager.process_message(user_id, message)
    
    return jsonify({
        'status': 'success',
        'response': response
    })

# 路由:获取对话历史
@app.route('/api/history/<user_id>', methods=['GET'])
def get_history(user_id):
    limit = request.args.get('limit', 20, type=int)
    history = db.get_conversation_history(user_id, limit)
    
    return jsonify({
        'status': 'success',
        'history': history
    })

# 路由:清除对话历史
@app.route('/api/history/<user_id>', methods=['DELETE'])
def clear_history(user_id):
    db.clear_conversation_history(user_id)
    
    return jsonify({
        'status': 'success',
        'message': '对话历史已清除'
    })

# 路由:添加知识条目
@app.route('/api/knowledge', methods=['POST'])
def add_knowledge():
    data = request.json
    question = data.get('question', '')
    answer = data.get('answer', '')
    
    if not question or not answer:
        return jsonify({
            'status': 'error',
            'message': '问题和答案不能为空'
        }), 400
    
    knowledge_base.add_document(question, answer)
    
    return jsonify({
        'status': 'success',
        'message': '知识条目已添加'
    })

# 路由:获取所有知识条目
@app.route('/api/knowledge', methods=['GET'])
def get_knowledge():
    documents = knowledge_base.get_all_documents()
    
    return jsonify({
        'status': 'success',
        'documents': documents
    })

# 路由:系统状态
@app.route('/api/status', methods=['GET'])
def get_status():
    return jsonify({
        'status': 'success',
        'system_status': {
            'dialog_manager': dialog_manager.get_status(),
            'knowledge_base': knowledge_base.get_status(),
            'database': db.get_status()
        }
    })

# 主函数
if __name__ == '__main__':
    # 获取端口,默认为5000
    port = int(os.environ.get('PORT', 5000))
    
    # 启动应用
    app.run(host='0.0.0.0', port=port, debug=True)

app_with_kb.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
聊天机器人系统主应用 - 集成知识库
"""

import os
import json
import logging
from datetime import datetime
from flask import Flask, request, jsonify, send_from_directory

# 导入聊天机器人组件
try:
    from nlp_module import NLPModule
    nlp_available = True
except ImportError:
    nlp_available = False
    print("警告: NLP模块导入失败,将使用简化版NLP功能")

from dialog_manager import DialogManager
from database import Database
from knowledge_base_db import KnowledgeBaseDB
from knowledge_base_integration import KnowledgeBaseIntegration
from knowledge_base_api import kb_api

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("聊天机器人系统")

# 创建Flask应用
app = Flask(__name__)

# 从环境变量获取配置
use_mongodb = os.environ.get("USE_MONGODB", "false").lower() == "true"
mongo_uri = os.environ.get("MONGO_URI")

# 初始化组件
db = Database(use_mongodb=use_mongodb, mongo_uri=mongo_uri)

# 初始化NLP模块
if nlp_available:
    nlp = NLPModule()
else:
    # 简化版NLP模块,用于测试
    class SimpleNLP:
        def __init__(self):
            self.intents = {
                "问候": ["你好", "您好", "早上好", "晚上好", "嗨", "hi", "hello"],
                "查询信息": ["什么是", "如何", "怎么", "解释", "介绍", "说明"],
                "结束对话": ["再见", "拜拜", "goodbye", "bye", "结束", "退出"]
            }
            
        def recognize_intent(self, text):
            for intent, patterns in self.intents.items():
                for pattern in patterns:
                    if pattern in text:
                        return intent, 0.8
            return "查询信息", 0.3
            
        def extract_entities(self, text):
            entities = []
            # 简单的关键词提取
            keywords = [word for word in text if len(word) > 1]
            if keywords:
                entities = [{"type": "keyword", "value": kw} for kw in keywords[:3]]
            return entities
            
        def calculate_text_similarity(self, text1, text2):
            # 简单的相似度计算
            common_words = set(text1) & set(text2)
            return len(common_words) / max(len(set(text1)), len(set(text2)), 1)
            
        def analyze_sentiment(self, text):
            # 简单的情感分析
            positive_words = ["好", "喜欢", "棒", "优秀", "感谢", "谢谢"]
            negative_words = ["不", "差", "糟", "失望", "讨厌", "烦"]
            
            pos_score = sum(1 for word in positive_words if word in text)
            neg_score = sum(1 for word in negative_words if word in text)
            
            if pos_score > neg_score:
                return "positive", (pos_score - neg_score) / (pos_score + neg_score + 1)
            elif neg_score > pos_score:
                return "negative", (neg_score - pos_score) / (pos_score + neg_score + 1)
            else:
                return "neutral", 0.5
    
    nlp = SimpleNLP()

# 初始化知识库集成
kb_integration = KnowledgeBaseIntegration(nlp_module=nlp, use_mongodb=use_mongodb, mongo_uri=mongo_uri)

# 初始化对话管理器
dialog_manager = DialogManager(nlp, kb_integration, db)

# 注册知识库API蓝图
app.register_blueprint(kb_api)

# 静态文件路由
@app.route('/')
def index():
    return send_from_directory('static', 'index.html')

@app.route('/knowledge')
def knowledge_base():
    return send_from_directory('static', 'knowledge_base.html')

# API路由
@app.route('/api/chat', methods=['POST'])
def chat():
    """聊天API"""
    try:
        data = request.json
        if not data:
            return jsonify({"success": False, "message": "请求数据为空"}), 400
        
        # 获取用户消息和ID
        message = data.get('message', '')
        user_id = data.get('user_id', 'default_user')
        
        if not message:
            return jsonify({"success": False, "message": "消息不能为空"}), 400
        
        # 获取响应
        response = dialog_manager.get_response(message, user_id)
        
        # 获取会话状态
        session = dialog_manager.get_session(user_id)
        
        return jsonify({
            "success": True,
            "response": response,
            "session": {
                "state": session.get('current_state', 'unknown'),
                "turn_count": session.get('turn_count', 0)
            }
        })
    except Exception as e:
        logger.error(f"处理聊天请求失败: {str(e)}")
        return jsonify({"success": False, "message": f"处理请求失败: {str(e)}"}), 500

@app.route('/api/history', methods=['GET'])
def get_history():
    """获取聊天历史"""
    try:
        user_id = request.args.get('user_id', 'default_user')
        limit = int(request.args.get('limit', 10))
        
        history = db.get_chat_history(user_id, limit=limit)
        
        return jsonify({
            "success": True,
            "history": history
        })
    except Exception as e:
        logger.error(f"获取聊天历史失败: {str(e)}")
        return jsonify({"success": False, "message": f"获取聊天历史失败: {str(e)}"}), 500

@app.route('/api/clear_history', methods=['POST'])
def clear_history():
    """清除聊天历史"""
    try:
        data = request.json
        if not data:
            return jsonify({"success": False, "message": "请求数据为空"}), 400
        
        user_id = data.get('user_id', 'default_user')
        
        # 清除聊天历史
        db.clear_chat_history(user_id)
        
        # 重置会话状态
        dialog_manager.reset_session(user_id)
        
        return jsonify({
            "success": True,
            "message": "聊天历史已清除"
        })
    except Exception as e:
        logger.error(f"清除聊天历史失败: {str(e)}")
        return jsonify({"success": False, "message": f"清除聊天历史失败: {str(e)}"}), 500

@app.route('/api/feedback', methods=['POST'])
def submit_feedback():
    """提交反馈"""
    try:
        data = request.json
        if not data:
            return jsonify({"success": False, "message": "请求数据为空"}), 400
        
        user_id = data.get('user_id', 'default_user')
        message_id = data.get('message_id')
        feedback = data.get('feedback')
        
        if not message_id or feedback is None:
            return jsonify({"success": False, "message": "消息ID和反馈内容是必需的"}), 400
        
        # 保存反馈
        db.save_feedback(user_id, message_id, feedback)
        
        # 如果是正面反馈,可以考虑将对话添加到知识库
        if feedback > 0:
            # 获取对话
            message = db.get_message(message_id)
            if message:
                kb_integration.learn_from_conversation(
                    question=message.get('user_message', ''),
                    answer=message.get('bot_message', ''),
                    confidence=0.9  # 用户正面反馈,置信度高
                )
        
        return jsonify({
            "success": True,
            "message": "反馈提交成功"
        })
    except Exception as e:
        logger.error(f"提交反馈失败: {str(e)}")
        return jsonify({"success": False, "message": f"提交反馈失败: {str(e)}"}), 500

# 启动应用
if __name__ == '__main__':
    # 确保目录存在
    os.makedirs('knowledge', exist_ok=True)
    os.makedirs('uploads', exist_ok=True)
    os.makedirs('exports', exist_ok=True)
    
    # 启动Flask应用
    app.run(host='0.0.0.0', port=5000, debug=True)

database.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
数据库模块
负责存储和检索聊天历史、用户信息等数据
"""

import os
import json
import time
from datetime import datetime
from pymongo import MongoClient
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

class ChatbotDatabase:
    """聊天机器人数据库类"""
    
    def __init__(self, use_mongodb=False):
        """
        初始化数据库
        
        Args:
            use_mongodb: 是否使用MongoDB,如果为False则使用文件存储
        """
        self.use_mongodb = use_mongodb
        
        # 如果使用MongoDB,尝试连接
        if self.use_mongodb:
            try:
                # 获取MongoDB连接信息
                mongo_uri = os.environ.get('MONGO_URI', 'mongodb://localhost:27017/')
                self.client = MongoClient(mongo_uri)
                self.db = self.client.chatbot_db
                self.conversations = self.db.conversations
                self.users = self.db.users
                print("已连接到MongoDB")
            except Exception as e:
                print(f"连接MongoDB失败: {e}")
                print("将使用文件存储作为替代")
                self.use_mongodb = False
        
        # 如果不使用MongoDB,初始化文件存储
        if not self.use_mongodb:
            self.data_dir = os.path.join(os.path.dirname(__file__), 'data')
            os.makedirs(self.data_dir, exist_ok=True)
            
            # 加载或创建数据文件
            self.conversations_file = os.path.join(self.data_dir, 'conversations.json')
            self.users_file = os.path.join(self.data_dir, 'users.json')
            
            self.conversations_data = self._load_json_file(self.conversations_file, [])
            self.users_data = self._load_json_file(self.users_file, {})
    
    def _load_json_file(self, file_path, default_value):
        """
        加载JSON文件,如果不存在则返回默认值
        
        Args:
            file_path: 文件路径
            default_value: 默认值
            
        Returns:
            加载的数据或默认值
        """
        if os.path.exists(file_path):
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except Exception as e:
                print(f"加载文件 {file_path} 失败: {e}")
        
        return default_value
    
    def _save_json_file(self, file_path, data):
        """
        保存数据到JSON文件
        
        Args:
            file_path: 文件路径
            data: 要保存的数据
        """
        try:
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存文件 {file_path} 失败: {e}")
    
    def save_conversation(self, user_id, message, response, intent, state, timestamp=None):
        """
        保存对话记录
        
        Args:
            user_id: 用户ID
            message: 用户消息
            response: 机器人回复
            intent: 识别的意图
            state: 对话状态
            timestamp: 时间戳,默认为当前时间
        """
        # 创建对话记录
        conversation = {
            "user_id": user_id,
            "message": message,
            "response": response,
            "intent": intent,
            "state": state,
            "timestamp": timestamp.isoformat() if timestamp else datetime.now().isoformat()
        }
        
        # 保存到数据库
        if self.use_mongodb:
            self.conversations.insert_one(conversation)
        else:
            self.conversations_data.append(conversation)
            self._save_json_file(self.conversations_file, self.conversations_data)
    
    def get_conversation_history(self, user_id, limit=20):
        """
        获取用户对话历史
        
        Args:
            user_id: 用户ID
            limit: 返回的最大记录数
            
        Returns:
            list: 对话历史记录
        """
        if self.use_mongodb:
            cursor = self.conversations.find(
                {"user_id": user_id},
                sort=[("timestamp", -1)],
                limit=limit
            )
            return list(cursor)
        else:
            # 过滤并排序对话记录
            user_conversations = [
                conv for conv in self.conversations_data
                if conv["user_id"] == user_id
            ]
            
            # 按时间戳排序(降序)
            user_conversations.sort(
                key=lambda x: x["timestamp"],
                reverse=True
            )
            
            return user_conversations[:limit]
    
    def clear_conversation_history(self, user_id):
        """
        清除用户对话历史
        
        Args:
            user_id: 用户ID
            
        Returns:
            int: 删除的记录数
        """
        if self.use_mongodb:
            result = self.conversations.delete_many({"user_id": user_id})
            return result.deleted_count
        else:
            # 过滤出不属于该用户的对话记录
            self.conversations_data = [
                conv for conv in self.conversations_data
                if conv["user_id"] != user_id
            ]
            
            # 保存更新后的数据
            self._save_json_file(self.conversations_file, self.conversations_data)
            
            return len(self.conversations_data)
    
    def save_user_info(self, user_id, info):
        """
        保存用户信息
        
        Args:
            user_id: 用户ID
            info: 用户信息字典
        """
        # 添加更新时间
        info["updated_at"] = datetime.now().isoformat()
        
        if self.use_mongodb:
            self.users.update_one(
                {"user_id": user_id},
                {"$set": info},
                upsert=True
            )
        else:
            # 更新用户信息
            if user_id in self.users_data:
                self.users_data[user_id].update(info)
            else:
                self.users_data[user_id] = info
            
            # 保存更新后的数据
            self._save_json_file(self.users_file, self.users_data)
    
    def get_user_info(self, user_id):
        """
        获取用户信息
        
        Args:
            user_id: 用户ID
            
        Returns:
            dict: 用户信息,如果不存在则返回空字典
        """
        if self.use_mongodb:
            user = self.users.find_one({"user_id": user_id})
            return user or {}
        else:
            return self.users_data.get(user_id, {})
    
    def get_all_users(self):
        """
        获取所有用户
        
        Returns:
            list: 用户列表
        """
        if self.use_mongodb:
            return list(self.users.find())
        else:
            return [
                {"user_id": user_id, **info}
                for user_id, info in self.users_data.items()
            ]
    
    def get_status(self):
        """
        获取数据库状态
        
        Returns:
            dict: 状态信息
        """
        if self.use_mongodb:
            return {
                "type": "MongoDB",
                "conversation_count": self.conversations.count_documents({}),
                "user_count": self.users.count_documents({})
            }
        else:
            return {
                "type": "File Storage",
                "conversation_count": len(self.conversations_data),
                "user_count": len(self.users_data)
            }

dialog_manager.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
对话管理系统
负责管理对话状态、决策下一步行动、生成回复
"""

import os
import json
import time
import random
from datetime import datetime

# 尝试导入NLP模块,如果不存在则使用模拟版本
try:
    import sys
    sys.path.append('..')
    from nlp_module import NLPProcessor
except ImportError:
    # 创建模拟NLP处理器
    class NLPProcessor:
        def predict_intent(self, text):
            intents = ["问候", "查询信息", "预订服务", "投诉反馈", "寻求帮助", "结束对话", "闲聊", "感谢", "确认", "拒绝"]
            intent = random.choice(intents)
            return {"intent": intent, "confidence": 0.8}
        
        def extract_entities(self, text):
            return {"person": [], "location": [], "time": [], "other": []}
        
        def analyze_sentiment(self, text):
            return {"sentiment": "积极", "score": 0.7}


class DialogManager:
    """对话管理系统类"""
    
    def __init__(self, database, knowledge_base):
        """
        初始化对话管理系统
        
        Args:
            database: 数据库实例
            knowledge_base: 知识库实例
        """
        self.database = database
        self.knowledge_base = knowledge_base
        self.nlp = NLPProcessor()
        
        # 用户会话状态
        self.sessions = {}
        
        # 加载对话策略
        self.load_dialog_policies()
        
        # 加载回复模板
        self.load_response_templates()
    
    def load_dialog_policies(self):
        """加载对话策略配置"""
        policy_file = os.path.join(os.path.dirname(__file__), 'config', 'dialog_policies.json')
        
        if os.path.exists(policy_file):
            with open(policy_file, 'r', encoding='utf-8') as f:
                self.policies = json.load(f)
        else:
            # 默认策略
            self.policies = {
                "问候": {"next_states": ["提供帮助", "闲聊"], "priority": "提供帮助"},
                "查询信息": {"next_states": ["提供信息", "请求更多信息"], "priority": "提供信息"},
                "预订服务": {"next_states": ["确认预订", "请求更多信息"], "priority": "请求更多信息"},
                "投诉反馈": {"next_states": ["道歉", "解决问题"], "priority": "道歉"},
                "寻求帮助": {"next_states": ["提供帮助", "转人工"], "priority": "提供帮助"},
                "结束对话": {"next_states": ["结束", "挽留"], "priority": "结束"},
                "闲聊": {"next_states": ["闲聊", "引导任务"], "priority": "闲聊"},
                "感谢": {"next_states": ["客气回应", "提供进一步帮助"], "priority": "客气回应"},
                "确认": {"next_states": ["执行确认操作", "提供进一步帮助"], "priority": "执行确认操作"},
                "拒绝": {"next_states": ["接受拒绝", "提供替代方案"], "priority": "提供替代方案"}
            }
    
    def load_response_templates(self):
        """加载回复模板"""
        template_file = os.path.join(os.path.dirname(__file__), 'config', 'response_templates.json')
        
        if os.path.exists(template_file):
            with open(template_file, 'r', encoding='utf-8') as f:
                self.templates = json.load(f)
        else:
            # 默认回复模板
            self.templates = {
                "问候": [
                    "您好!我是智能助手,有什么可以帮您的吗?",
                    "你好!很高兴为您服务,请问有什么需要帮助的吗?",
                    "您好,我是AI助手,请问需要什么帮助?"
                ],
                "提供帮助": [
                    "我可以帮您查询信息、预订服务或回答问题,请告诉我您需要什么帮助?",
                    "有什么我能帮到您的吗?例如查询信息、预订服务等。",
                    "请问您需要什么帮助?我可以回答问题或提供各种服务。"
                ],
                "提供信息": [
                    "根据您的查询,我找到了以下信息:{info}",
                    "以下是您查询的信息:{info}",
                    "关于您的问题,我可以告诉您:{info}"
                ],
                "请求更多信息": [
                    "为了更好地帮助您,我需要一些额外信息。请问{question}?",
                    "能否提供更多细节?比如{question}?",
                    "请告诉我{question},这样我能更准确地帮助您。"
                ],
                "确认预订": [
                    "已为您预订成功!预订详情:{details}",
                    "您的预订已确认。{details}",
                    "预订成功,详情如下:{details}"
                ],
                "道歉": [
                    "非常抱歉给您带来不便。{apology}",
                    "对此我深表歉意。{apology}",
                    "很遗憾您遇到了问题,{apology}"
                ],
                "解决问题": [
                    "针对您反馈的问题,我们可以{solution}",
                    "为解决您的问题,我建议{solution}",
                    "我们将通过以下方式解决您的问题:{solution}"
                ],
                "转人工": [
                    "您的问题可能需要人工客服协助,是否需要为您转接?",
                    "这个问题可能需要专业人员处理,要转接人工客服吗?",
                    "看起来这个问题比较复杂,需要转接人工客服吗?"
                ],
                "结束": [
                    "感谢您的使用,再见!",
                    "期待下次为您服务,再见!",
                    "再见,有需要随时找我!"
                ],
                "挽留": [
                    "在您离开前,还有其他可以帮到您的吗?",
                    "还有什么我可以帮您的吗?",
                    "您确定现在要结束对话吗?我还可以为您提供更多帮助。"
                ],
                "闲聊": [
                    "是的,{chat_response}",
                    "嗯,{chat_response}",
                    "{chat_response}"
                ],
                "引导任务": [
                    "我们可以聊聊,不过我最擅长帮您完成具体的任务,比如查询信息或预订服务。",
                    "很高兴与您聊天。另外,我还可以帮您查询信息或预订服务,需要我帮忙吗?",
                    "闲聊之余,我还可以帮您完成很多任务,比如查询信息或预订服务,有需要吗?"
                ],
                "客气回应": [
                    "不客气,这是我应该做的!",
                    "很高兴能帮到您!",
                    "您的满意是我最大的动力!"
                ],
                "提供进一步帮助": [
                    "还有什么我可以帮您的吗?",
                    "还需要其他帮助吗?",
                    "我可以为您做些什么?"
                ],
                "执行确认操作": [
                    "好的,我已经为您{action}",
                    "已完成,{action}",
                    "确认完成,{action}"
                ],
                "接受拒绝": [
                    "好的,我理解。",
                    "没问题,尊重您的选择。",
                    "好的,如有需要随时告诉我。"
                ],
                "提供替代方案": [
                    "没关系,我还可以为您提供以下替代方案:{alternatives}",
                    "理解您的考虑,以下是其他可能的选择:{alternatives}",
                    "或许这些替代方案更适合您:{alternatives}"
                ],
                "fallback": [
                    "抱歉,我没有理解您的意思。能否换个方式表达?",
                    "对不起,我可能没有理解正确。能请您重新描述一下吗?",
                    "很抱歉,我没能理解您的意思。请问您能重新表述一下吗?"
                ]
            }
    
    def get_user_session(self, user_id):
        """
        获取用户会话状态,如果不存在则创建新会话
        
        Args:
            user_id: 用户ID
            
        Returns:
            dict: 用户会话状态
        """
        if user_id not in self.sessions:
            self.sessions[user_id] = {
                "state": "初始",
                "context": {},
                "last_active": time.time(),
                "conversation_turns": 0
            }
        
        return self.sessions[user_id]
    
    def update_session_state(self, session, intent, entities, sentiment):
        """
        根据用户意图和实体更新会话状态
        
        Args:
            session: 用户会话状态
            intent: 用户意图
            entities: 提取的实体
            sentiment: 情感分析结果
            
        Returns:
            str: 新的状态
        """
        current_state = session["state"]
        
        # 更新上下文
        session["context"].update({
            "last_intent": intent,
            "entities": entities,
            "sentiment": sentiment
        })
        
        # 增加对话轮次
        session["conversation_turns"] += 1
        
        # 更新最后活跃时间
        session["last_active"] = time.time()
        
        # 根据意图和当前状态决定下一个状态
        if intent in self.policies:
            policy = self.policies[intent]
            next_states = policy["next_states"]
            priority = policy["priority"]
            
            # 如果优先状态可用,则使用优先状态
            if priority in next_states:
                new_state = priority
            # 否则随机选择一个可用状态
            elif next_states:
                new_state = random.choice(next_states)
            else:
                new_state = current_state
        else:
            # 默认保持当前状态
            new_state = current_state
        
        # 更新会话状态
        session["state"] = new_state
        
        return new_state
    
    def generate_response(self, state, context):
        """
        根据状态和上下文生成回复
        
        Args:
            state: 当前状态
            context: 上下文信息
            
        Returns:
            str: 生成的回复
        """
        # 如果状态有对应的回复模板,则使用模板
        if state in self.templates:
            templates = self.templates[state]
            template = random.choice(templates)
            
            # 填充模板中的占位符
            try:
                # 根据上下文填充模板
                if "{info}" in template and "query_result" in context:
                    template = template.replace("{info}", context["query_result"])
                
                if "{question}" in template:
                    questions = [
                        "您需要的具体信息是什么",
                        "您想了解哪方面的内容",
                        "您的具体需求是什么"
                    ]
                    template = template.replace("{question}", random.choice(questions))
                
                if "{details}" in template and "booking_details" in context:
                    template = template.replace("{details}", context["booking_details"])
                
                if "{apology}" in template:
                    apologies = [
                        "我们会尽快解决这个问题",
                        "我们会认真处理您的反馈",
                        "我们将立即着手解决"
                    ]
                    template = template.replace("{apology}", random.choice(apologies))
                
                if "{solution}" in template:
                    solutions = [
                        "为您重新安排服务",
                        "提供补偿方案",
                        "优先处理您的问题"
                    ]
                    template = template.replace("{solution}", random.choice(solutions))
                
                if "{chat_response}" in template:
                    chat_responses = [
                        "今天天气不错",
                        "人工智能正在快速发展",
                        "很高兴能和您聊天"
                    ]
                    template = template.replace("{chat_response}", random.choice(chat_responses))
                
                if "{action}" in template and "confirmed_action" in context:
                    template = template.replace("{action}", context["confirmed_action"])
                
                if "{alternatives}" in template:
                    alternatives = [
                        "更经济的方案、更高级的服务",
                        "其他时间段的预订、其他类型的服务",
                        "自助服务选项、人工服务选项"
                    ]
                    template = template.replace("{alternatives}", random.choice(alternatives))
            
            except Exception as e:
                print(f"模板填充错误: {e}")
                # 发生错误时使用默认回复
                template = "我理解您的需求,正在为您处理。"
            
            return template
        
        # 如果没有对应的模板,则使用默认回复
        else:
            return random.choice(self.templates["fallback"])
    
    def process_message(self, user_id, message):
        """
        处理用户消息并生成回复
        
        Args:
            user_id: 用户ID
            message: 用户消息
            
        Returns:
            str: 生成的回复
        """
        # 获取用户会话
        session = self.get_user_session(user_id)
        
        # NLP处理
        intent_result = self.nlp.predict_intent(message)
        intent = intent_result["intent"]
        
        entities = self.nlp.extract_entities(message)
        sentiment = self.nlp.analyze_sentiment(message)
        
        # 查询知识库
        knowledge_result = self.knowledge_base.query(message)
        
        # 更新上下文
        session["context"]["query_result"] = knowledge_result[0] if knowledge_result else "暂无相关信息"
        
        # 更新会话状态
        new_state = self.update_session_state(session, intent, entities, sentiment)
        
        # 生成回复
        response = self.generate_response(new_state, session["context"])
        
        # 保存对话记录
        self.database.save_conversation(
            user_id=user_id,
            message=message,
            response=response,
            intent=intent,
            state=new_state,
            timestamp=datetime.now()
        )
        
        return response
    
    def get_status(self):
        """
        获取对话管理系统状态
        
        Returns:
            dict: 状态信息
        """
        return {
            "active_sessions": len(self.sessions),
            "loaded_policies": len(self.policies),
            "loaded_templates": len(self.templates)
        }

knowledge_base.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
知识库模块
负责存储和检索聊天机器人的知识
"""

import os
import json
import numpy as np
from datetime import datetime

# 尝试导入向量数据库依赖
try:
    import faiss
    from sentence_transformers import SentenceTransformer
    VECTOR_DB_AVAILABLE = True
except ImportError:
    VECTOR_DB_AVAILABLE = False
    print("警告: faiss或sentence_transformers未安装,将使用简单的文本匹配作为替代")


class KnowledgeBase:
    """知识库类,负责存储和检索知识"""
    
    def __init__(self, model_name="distiluse-base-multilingual-cased"):
        """
        初始化知识库
        
        Args:
            model_name: 句子编码模型名称
        """
        self.documents = []
        self.index = None
        self.vector_db_enabled = VECTOR_DB_AVAILABLE
        
        # 如果向量数据库可用,初始化编码模型
        if self.vector_db_enabled:
            try:
                self.model = SentenceTransformer(model_name)
                print(f"已加载句子编码模型: {model_name}")
            except Exception as e:
                print(f"加载句子编码模型失败: {e}")
                self.vector_db_enabled = False
    
    def add_document(self, question, answer, metadata=None):
        """
        添加文档到知识库
        
        Args:
            question: 问题文本
            answer: 答案文本
            metadata: 元数据字典
        """
        # 创建文档
        document = {
            "id": len(self.documents),
            "question": question,
            "answer": answer,
            "metadata": metadata or {},
            "created_at": datetime.now().isoformat()
        }
        
        # 添加到文档列表
        self.documents.append(document)
        
        # 如果向量数据库可用,更新索引
        if self.vector_db_enabled:
            self._update_index()
        
        return document["id"]
    
    def _update_index(self):
        """更新向量索引"""
        if not self.vector_db_enabled:
            return
        
        # 提取所有问题文本
        questions = [doc["question"] for doc in self.documents]
        
        # 编码问题
        embeddings = self.model.encode(questions)
        dimension = embeddings.shape[1]
        
        # 创建或更新索引
        if self.index is None:
            self.index = faiss.IndexFlatL2(dimension)
        else:
            # 重置索引
            self.index = faiss.IndexFlatL2(dimension)
        
        # 添加向量到索引
        self.index.add(np.array(embeddings).astype('float32'))
    
    def query(self, query_text, top_k=3):
        """
        查询知识库
        
        Args:
            query_text: 查询文本
            top_k: 返回的最相关文档数量
            
        Returns:
            list: 相关答案列表
        """
        if not self.documents:
            return []
        
        # 如果向量数据库可用,使用语义搜索
        if self.vector_db_enabled and self.index is not None:
            # 编码查询
            query_embedding = self.model.encode([query_text])
            
            # 搜索最相似的向量
            distances, indices = self.index.search(
                np.array(query_embedding).astype('float32'), 
                min(top_k, len(self.documents))
            )
            
            # 获取对应的答案
            results = []
            for idx in indices[0]:
                if idx < len(self.documents):
                    results.append(self.documents[idx]["answer"])
            
            return results
        
        # 否则使用简单的文本匹配
        else:
            # 计算查询与每个问题的相似度(简单实现)
            similarities = []
            for doc in self.documents:
                # 计算问题中与查询相同的词的数量
                query_words = set(query_text.lower().split())
                question_words = set(doc["question"].lower().split())
                common_words = query_words.intersection(question_words)
                similarity = len(common_words) / max(len(query_words), len(question_words))
                
                similarities.append((similarity, doc["answer"]))
            
            # 按相似度排序
            similarities.sort(reverse=True)
            
            # 返回前top_k个答案
            return [item[1] for item in similarities[:top_k]]
    
    def get_document_by_id(self, doc_id):
        """
        通过ID获取文档
        
        Args:
            doc_id: 文档ID
            
        Returns:
            dict: 文档,如果不存在则返回None
        """
        for doc in self.documents:
            if doc["id"] == doc_id:
                return doc
        
        return None
    
    def remove_document(self, doc_id):
        """
        删除文档
        
        Args:
            doc_id: 文档ID
            
        Returns:
            bool: 是否成功删除
        """
        for i, doc in enumerate(self.documents):
            if doc["id"] == doc_id:
                self.documents.pop(i)
                
                # 更新索引
                if self.vector_db_enabled:
                    self._update_index()
                
                return True
        
        return False
    
    def get_all_documents(self):
        """
        获取所有文档
        
        Returns:
            list: 文档列表
        """
        return self.documents
    
    def save_to_file(self, file_path):
        """
        保存知识库到文件
        
        Args:
            file_path: 文件路径
        """
        directory = os.path.dirname(file_path)
        if directory and not os.path.exists(directory):
            os.makedirs(directory)
        
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(self.documents, f, ensure_ascii=False, indent=2)
    
    def load_from_file(self, file_path):
        """
        从文件加载知识库
        
        Args:
            file_path: 文件路径
            
        Returns:
            bool: 是否成功加载
        """
        if not os.path.exists(file_path):
            return False
        
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                self.documents = json.load(f)
            
            # 更新索引
            if self.vector_db_enabled:
                self._update_index()
            
            return True
        except Exception as e:
            print(f"加载知识库文件失败: {e}")
            return False
    
    def load_from_directory(self, directory_path):
        """
        从目录加载知识库
        
        Args:
            directory_path: 目录路径
            
        Returns:
            int: 加载的文档数量
        """
        if not os.path.exists(directory_path):
            os.makedirs(directory_path)
            return 0
        
        count = 0
        
        # 加载主知识库文件
        kb_file = os.path.join(directory_path, 'knowledge_base.json')
        if os.path.exists(kb_file):
            if self.load_from_file(kb_file):
                count = len(self.documents)
        
        # 加载其他JSON文件
        for filename in os.listdir(directory_path):
            if filename.endswith('.json') and filename != 'knowledge_base.json':
                file_path = os.path.join(directory_path, filename)
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        data = json.load(f)
                    
                    # 处理不同格式的知识文件
                    if isinstance(data, list):
                        for item in data:
                            if isinstance(item, dict) and 'question' in item and 'answer' in item:
                                self.add_document(item['question'], item['answer'], item.get('metadata'))
                                count += 1
                    elif isinstance(data, dict):
                        for question, answer in data.items():
                            self.add_document(question, answer)
                            count += 1
                
                except Exception as e:
                    print(f"加载知识文件 {filename} 失败: {e}")
        
        return count
    
    def get_status(self):
        """
        获取知识库状态
        
        Returns:
            dict: 状态信息
        """
        return {
            "document_count": len(self.documents),
            "vector_db_enabled": self.vector_db_enabled,
            "index_built": self.index is not None if self.vector_db_enabled else False
        }

knowledge_base_api.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
知识库API模块:提供知识库的RESTful API接口
"""

import os
import json
import logging
from typing import List, Dict, Any, Optional
from flask import Blueprint, request, jsonify, send_file
from werkzeug.utils import secure_filename

from knowledge_base_db import KnowledgeBaseDB

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("知识库API")

# 创建Blueprint
kb_api = Blueprint('knowledge_base_api', __name__)

# 初始化知识库数据库
# 从环境变量获取配置
use_mongodb = os.environ.get("USE_MONGODB", "false").lower() == "true"
mongo_uri = os.environ.get("MONGO_URI")
kb_db = KnowledgeBaseDB(use_mongodb=use_mongodb, mongo_uri=mongo_uri)

# 上传文件配置
UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "uploads")
ALLOWED_EXTENSIONS = {'json', 'txt', 'csv'}
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

def allowed_file(filename):
    """检查文件扩展名是否允许"""
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@kb_api.route('/api/knowledge', methods=['GET'])
def get_knowledge():
    """获取知识库条目"""
    try:
        # 获取查询参数
        knowledge_id = request.args.get('id')
        if knowledge_id:
            # 获取单个知识条目
            try:
                knowledge_id = int(knowledge_id)
                knowledge = kb_db.get_knowledge(knowledge_id)
                if knowledge:
                    return jsonify({"success": True, "data": knowledge})
                else:
                    return jsonify({"success": False, "message": "未找到指定ID的知识"}), 404
            except ValueError:
                return jsonify({"success": False, "message": "知识ID必须是整数"}), 400
        else:
            # 搜索知识库
            query = request.args.get('query', '')
            category = request.args.get('category')
            tags = request.args.getlist('tag')
            page = int(request.args.get('page', 1))
            page_size = int(request.args.get('page_size', 10))
            
            # 计算偏移量
            offset = (page - 1) * page_size
            
            # 搜索知识
            results = kb_db.search_knowledge(
                query=query, 
                category=category, 
                tags=tags, 
                limit=page_size, 
                offset=offset
            )
            
            # 获取总数
            total = kb_db.count_knowledge(query=query, category=category, tags=tags)
            
            return jsonify({
                "success": True,
                "data": results,
                "pagination": {
                    "page": page,
                    "page_size": page_size,
                    "total": total,
                    "total_pages": (total + page_size - 1) // page_size
                }
            })
    except Exception as e:
        logger.error(f"获取知识失败: {str(e)}")
        return jsonify({"success": False, "message": f"获取知识失败: {str(e)}"}), 500

@kb_api.route('/api/knowledge', methods=['POST'])
def add_knowledge():
    """添加知识到知识库"""
    try:
        data = request.json
        if not data:
            return jsonify({"success": False, "message": "请求数据为空"}), 400
        
        # 检查必要字段
        if 'question' not in data or 'answer' not in data:
            return jsonify({"success": False, "message": "问题和答案字段是必需的"}), 400
        
        # 添加知识
        knowledge = kb_db.add_knowledge(
            question=data['question'],
            answer=data['answer'],
            metadata=data.get('metadata', {})
        )
        
        return jsonify({"success": True, "data": knowledge, "message": "知识添加成功"})
    except Exception as e:
        logger.error(f"添加知识失败: {str(e)}")
        return jsonify({"success": False, "message": f"添加知识失败: {str(e)}"}), 500

@kb_api.route('/api/knowledge/<int:knowledge_id>', methods=['PUT'])
def update_knowledge(knowledge_id):
    """更新知识库条目"""
    try:
        data = request.json
        if not data:
            return jsonify({"success": False, "message": "请求数据为空"}), 400
        
        # 更新知识
        updated = kb_db.update_knowledge(
            knowledge_id=knowledge_id,
            question=data.get('question'),
            answer=data.get('answer'),
            metadata=data.get('metadata')
        )
        
        if updated:
            return jsonify({"success": True, "data": updated, "message": "知识更新成功"})
        else:
            return jsonify({"success": False, "message": "未找到指定ID的知识"}), 404
    except Exception as e:
        logger.error(f"更新知识失败: {str(e)}")
        return jsonify({"success": False, "message": f"更新知识失败: {str(e)}"}), 500

@kb_api.route('/api/knowledge/<int:knowledge_id>', methods=['DELETE'])
def delete_knowledge(knowledge_id):
    """删除知识库条目"""
    try:
        # 删除知识
        success = kb_db.delete_knowledge(knowledge_id)
        
        if success:
            return jsonify({"success": True, "message": "知识删除成功"})
        else:
            return jsonify({"success": False, "message": "未找到指定ID的知识"}), 404
    except Exception as e:
        logger.error(f"删除知识失败: {str(e)}")
        return jsonify({"success": False, "message": f"删除知识失败: {str(e)}"}), 500

@kb_api.route('/api/knowledge/categories', methods=['GET'])
def get_categories():
    """获取所有分类"""
    try:
        categories = kb_db.get_all_categories()
        return jsonify({"success": True, "data": categories})
    except Exception as e:
        logger.error(f"获取分类失败: {str(e)}")
        return jsonify({"success": False, "message": f"获取分类失败: {str(e)}"}), 500

@kb_api.route('/api/knowledge/tags', methods=['GET'])
def get_tags():
    """获取所有标签"""
    try:
        tags = kb_db.get_all_tags()
        return jsonify({"success": True, "data": tags})
    except Exception as e:
        logger.error(f"获取标签失败: {str(e)}")
        return jsonify({"success": False, "message": f"获取标签失败: {str(e)}"}), 500

@kb_api.route('/api/knowledge/import', methods=['POST'])
def import_knowledge():
    """导入知识库"""
    try:
        # 检查是否有文件上传
        if 'file' not in request.files:
            return jsonify({"success": False, "message": "未找到上传文件"}), 400
        
        file = request.files['file']
        if file.filename == '':
            return jsonify({"success": False, "message": "未选择文件"}), 400
        
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file_path = os.path.join(UPLOAD_FOLDER, filename)
            file.save(file_path)
            
            # 读取文件内容
            with open(file_path, 'r', encoding='utf-8') as f:
                try:
                    data = json.load(f)
                except json.JSONDecodeError:
                    return jsonify({"success": False, "message": "无效的JSON文件"}), 400
            
            # 导入知识
            if isinstance(data, list):
                count = kb_db.import_knowledge(data)
                return jsonify({"success": True, "message": f"成功导入 {count} 条知识"})
            else:
                return jsonify({"success": False, "message": "无效的知识库格式,应为JSON数组"}), 400
        else:
            return jsonify({"success": False, "message": f"不支持的文件类型,仅支持 {', '.join(ALLOWED_EXTENSIONS)}"}), 400
    except Exception as e:
        logger.error(f"导入知识失败: {str(e)}")
        return jsonify({"success": False, "message": f"导入知识失败: {str(e)}"}), 500

@kb_api.route('/api/knowledge/export', methods=['GET'])
def export_knowledge():
    """导出知识库"""
    try:
        # 获取查询参数
        query = request.args.get('query', '')
        category = request.args.get('category')
        tags = request.args.getlist('tag')
        
        # 导出知识
        data = kb_db.export_knowledge(query=query, category=category, tags=tags)
        
        # 生成导出文件
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        export_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "exports")
        os.makedirs(export_dir, exist_ok=True)
        export_path = os.path.join(export_dir, f"knowledge_export_{timestamp}.json")
        
        with open(export_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        
        return send_file(export_path, as_attachment=True, download_name=f"knowledge_export_{timestamp}.json")
    except Exception as e:
        logger.error(f"导出知识失败: {str(e)}")
        return jsonify({"success": False, "message": f"导出知识失败: {str(e)}"}), 500

@kb_api.route('/api/knowledge/backup', methods=['POST'])
def backup_knowledge():
    """备份知识库"""
    try:
        backup_path = kb_db.backup_knowledge()
        return jsonify({"success": True, "message": f"知识库备份成功", "backup_path": backup_path})
    except Exception as e:
        logger.error(f"备份知识库失败: {str(e)}")
        return jsonify({"success": False, "message": f"备份知识库失败: {str(e)}"}), 500

@kb_api.route('/api/knowledge/restore', methods=['POST'])
def restore_knowledge():
    """从备份恢复知识库"""
    try:
        data = request.json
        if not data or 'backup_path' not in data:
            return jsonify({"success": False, "message": "请提供备份文件路径"}), 400
        
        backup_path = data['backup_path']
        clear_existing = data.get('clear_existing', False)
        
        if not os.path.exists(backup_path):
            return jsonify({"success": False, "message": "备份文件不存在"}), 404
        
        count = kb_db.restore_knowledge(backup_path, clear_existing)
        return jsonify({"success": True, "message": f"成功恢复 {count} 条知识"})
    except Exception as e:
        logger.error(f"恢复知识库失败: {str(e)}")
        return jsonify({"success": False, "message": f"恢复知识库失败: {str(e)}"}), 500
3. Knowledge Base Frontend HTML

knowledge_base_db.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
知识库数据库模块:负责知识库的存储、检索和管理
"""

import os
import json
import time
import uuid
import logging
from typing import List, Dict, Any, Optional, Union
from datetime import datetime

try:
    import pymongo
    from pymongo import MongoClient
    MONGODB_AVAILABLE = True
except ImportError:
    MONGODB_AVAILABLE = False

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("知识库数据库")

class KnowledgeBaseDB:
    """知识库数据库类,支持文件存储和MongoDB存储"""
    
    def __init__(self, use_mongodb: bool = False, mongo_uri: str = None, 
                 db_name: str = "chatbot", collection_name: str = "knowledge_base",
                 file_path: str = "knowledge/knowledge_base.json"):
        """
        初始化知识库数据库
        
        Args:
            use_mongodb: 是否使用MongoDB存储
            mongo_uri: MongoDB连接URI
            db_name: MongoDB数据库名称
            collection_name: MongoDB集合名称
            file_path: 文件存储路径
        """
        self.use_mongodb = use_mongodb and MONGODB_AVAILABLE
        self.file_path = file_path
        
        # 确保知识库文件目录存在
        os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
        
        if self.use_mongodb:
            try:
                # 使用环境变量或参数中的MongoDB URI
                self.mongo_uri = mongo_uri or os.environ.get("MONGO_URI", "mongodb://localhost:27017/")
                self.client = MongoClient(self.mongo_uri)
                self.db = self.client[db_name]
                self.collection = self.db[collection_name]
                
                # 创建索引
                self.collection.create_index("id", unique=True)
                self.collection.create_index("question")
                self.collection.create_index([("metadata.category", pymongo.ASCENDING)])
                self.collection.create_index([("metadata.tags", pymongo.ASCENDING)])
                
                logger.info(f"已连接到MongoDB: {self.mongo_uri}")
            except Exception as e:
                logger.error(f"MongoDB连接失败: {str(e)}")
                self.use_mongodb = False
                logger.info("将使用文件存储作为备选")
        
        if not self.use_mongodb:
            logger.info(f"使用文件存储: {self.file_path}")
            # 如果文件不存在,创建空的知识库文件
            if not os.path.exists(self.file_path):
                with open(self.file_path, 'w', encoding='utf-8') as f:
                    json.dump([], f, ensure_ascii=False, indent=2)
    
    def _get_next_id(self) -> int:
        """获取下一个可用的ID"""
        if self.use_mongodb:
            # 查找最大ID
            result = self.collection.find_one(sort=[("id", pymongo.DESCENDING)])
            return (result["id"] + 1) if result else 0
        else:
            # 从文件中读取所有记录,找出最大ID
            try:
                with open(self.file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                return max([item.get("id", -1) for item in data] + [-1]) + 1
            except (json.JSONDecodeError, FileNotFoundError):
                return 0
    
    def add_knowledge(self, question: str, answer: str, 
                      metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """
        添加知识到知识库
        
        Args:
            question: 问题
            answer: 答案
            metadata: 元数据,如分类、标签等
            
        Returns:
            添加的知识条目
        """
        if not question or not answer:
            raise ValueError("问题和答案不能为空")
        
        # 准备知识条目
        knowledge_id = self._get_next_id()
        timestamp = datetime.now().isoformat()
        
        knowledge_item = {
            "id": knowledge_id,
            "question": question,
            "answer": answer,
            "metadata": metadata or {},
            "created_at": timestamp,
            "updated_at": timestamp
        }
        
        if self.use_mongodb:
            try:
                self.collection.insert_one(knowledge_item)
                logger.info(f"已添加知识到MongoDB,ID: {knowledge_id}")
            except Exception as e:
                logger.error(f"添加知识到MongoDB失败: {str(e)}")
                raise
        else:
            try:
                # 读取现有数据
                with open(self.file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # 添加新知识
                data.append(knowledge_item)
                
                # 写回文件
                with open(self.file_path, 'w', encoding='utf-8') as f:
                    json.dump(data, f, ensure_ascii=False, indent=2)
                
                logger.info(f"已添加知识到文件,ID: {knowledge_id}")
            except Exception as e:
                logger.error(f"添加知识到文件失败: {str(e)}")
                raise
        
        return knowledge_item
    
    def update_knowledge(self, knowledge_id: int, 
                         question: Optional[str] = None, 
                         answer: Optional[str] = None,
                         metadata: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
        """
        更新知识库中的条目
        
        Args:
            knowledge_id: 知识ID
            question: 新问题(可选)
            answer: 新答案(可选)
            metadata: 新元数据(可选)
            
        Returns:
            更新后的知识条目,如果未找到则返回None
        """
        # 准备更新字段
        update_fields = {}
        if question is not None:
            update_fields["question"] = question
        if answer is not None:
            update_fields["answer"] = answer
        if metadata is not None:
            update_fields["metadata"] = metadata
        
        if not update_fields:
            logger.warning("没有提供要更新的字段")
            return None
        
        # 添加更新时间
        update_fields["updated_at"] = datetime.now().isoformat()
        
        if self.use_mongodb:
            try:
                result = self.collection.find_one_and_update(
                    {"id": knowledge_id},
                    {"$set": update_fields},
                    return_document=pymongo.ReturnDocument.AFTER
                )
                if result:
                    logger.info(f"已更新知识,ID: {knowledge_id}")
                    return result
                else:
                    logger.warning(f"未找到要更新的知识,ID: {knowledge_id}")
                    return None
            except Exception as e:
                logger.error(f"更新知识失败: {str(e)}")
                raise
        else:
            try:
                # 读取现有数据
                with open(self.file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # 查找并更新知识
                for i, item in enumerate(data):
                    if item.get("id") == knowledge_id:
                        data[i].update(update_fields)
                        updated_item = data[i]
                        
                        # 写回文件
                        with open(self.file_path, 'w', encoding='utf-8') as f:
                            json.dump(data, f, ensure_ascii=False, indent=2)
                        
                        logger.info(f"已更新知识,ID: {knowledge_id}")
                        return updated_item
                
                logger.warning(f"未找到要更新的知识,ID: {knowledge_id}")
                return None
            except Exception as e:
                logger.error(f"更新知识失败: {str(e)}")
                raise
    
    def delete_knowledge(self, knowledge_id: int) -> bool:
        """
        删除知识库中的条目
        
        Args:
            knowledge_id: 知识ID
            
        Returns:
            是否成功删除
        """
        if self.use_mongodb:
            try:
                result = self.collection.delete_one({"id": knowledge_id})
                success = result.deleted_count > 0
                if success:
                    logger.info(f"已删除知识,ID: {knowledge_id}")
                else:
                    logger.warning(f"未找到要删除的知识,ID: {knowledge_id}")
                return success
            except Exception as e:
                logger.error(f"删除知识失败: {str(e)}")
                raise
        else:
            try:
                # 读取现有数据
                with open(self.file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # 查找并删除知识
                original_length = len(data)
                data = [item for item in data if item.get("id") != knowledge_id]
                success = len(data) < original_length
                
                if success:
                    # 写回文件
                    with open(self.file_path, 'w', encoding='utf-8') as f:
                        json.dump(data, f, ensure_ascii=False, indent=2)
                    
                    logger.info(f"已删除知识,ID: {knowledge_id}")
                else:
                    logger.warning(f"未找到要删除的知识,ID: {knowledge_id}")
                
                return success
            except Exception as e:
                logger.error(f"删除知识失败: {str(e)}")
                raise
    
    def get_knowledge(self, knowledge_id: int) -> Optional[Dict[str, Any]]:
        """
        获取指定ID的知识
        
        Args:
            knowledge_id: 知识ID
            
        Returns:
            知识条目,如果未找到则返回None
        """
        if self.use_mongodb:
            try:
                result = self.collection.find_one({"id": knowledge_id})
                return result
            except Exception as e:
                logger.error(f"获取知识失败: {str(e)}")
                raise
        else:
            try:
                # 读取现有数据
                with open(self.file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # 查找知识
                for item in data:
                    if item.get("id") == knowledge_id:
                        return item
                
                return None
            except Exception as e:
                logger.error(f"获取知识失败: {str(e)}")
                raise
    
    def search_knowledge(self, query: str = None, category: str = None, 
                         tags: List[str] = None, limit: int = 10, 
                         offset: int = 0) -> List[Dict[str, Any]]:
        """
        搜索知识库
        
        Args:
            query: 搜索关键词
            category: 分类
            tags: 标签列表
            limit: 返回结果数量限制
            offset: 结果偏移量
            
        Returns:
            知识条目列表
        """
        if self.use_mongodb:
            try:
                # 构建查询条件
                query_conditions = {}
                
                if query:
                    query_conditions["$or"] = [
                        {"question": {"$regex": query, "$options": "i"}},
                        {"answer": {"$regex": query, "$options": "i"}}
                    ]
                
                if category:
                    query_conditions["metadata.category"] = category
                
                if tags:
                    query_conditions["metadata.tags"] = {"$in": tags}
                
                # 执行查询
                cursor = self.collection.find(query_conditions)
                
                # 应用分页
                cursor = cursor.skip(offset).limit(limit)
                
                # 转换为列表
                results = list(cursor)
                
                return results
            except Exception as e:
                logger.error(f"搜索知识失败: {str(e)}")
                raise
        else:
            try:
                # 读取现有数据
                with open(self.file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # 过滤数据
                filtered_data = data
                
                if query:
                    query = query.lower()
                    filtered_data = [
                        item for item in filtered_data
                        if query in item.get("question", "").lower() or 
                           query in item.get("answer", "").lower()
                    ]
                
                if category:
                    filtered_data = [
                        item for item in filtered_data
                        if item.get("metadata", {}).get("category") == category
                    ]
                
                if tags:
                    filtered_data = [
                        item for item in filtered_data
                        if any(tag in item.get("metadata", {}).get("tags", []) for tag in tags)
                    ]
                
                # 应用分页
                paginated_data = filtered_data[offset:offset + limit]
                
                return paginated_data
            except Exception as e:
                logger.error(f"搜索知识失败: {str(e)}")
                raise
    
    def get_all_categories(self) -> List[str]:
        """
        获取所有分类
        
        Returns:
            分类列表
        """
        if self.use_mongodb:
            try:
                # 聚合查询获取所有不同的分类
                pipeline = [
                    {"$group": {"_id": "$metadata.category"}},
                    {"$match": {"_id": {"$ne": None}}},
                    {"$sort": {"_id": 1}}
                ]
                
                results = self.collection.aggregate(pipeline)
                categories = [result["_id"] for result in results]
                
                return categories
            except Exception as e:
                logger.error(f"获取分类失败: {str(e)}")
                raise
        else:
            try:
                # 读取现有数据
                with open(self.file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # 提取所有分类
                categories = set()
                for item in data:
                    category = item.get("metadata", {}).get("category")
                    if category:
                        categories.add(category)
                
                return sorted(list(categories))
            except Exception as e:
                logger.error(f"获取分类失败: {str(e)}")
                raise
    
    def get_all_tags(self) -> List[str]:
        """
        获取所有标签
        
        Returns:
            标签列表
        """
        if self.use_mongodb:
            try:
                # 聚合查询获取所有不同的标签
                pipeline = [
                    {"$unwind": "$metadata.tags"},
                    {"$group": {"_id": "$metadata.tags"}},
                    {"$match": {"_id": {"$ne": None}}},
                    {"$sort": {"_id": 1}}
                ]
                
                results = self.collection.aggregate(pipeline)
                tags = [result["_id"] for result in results]
                
                return tags
            except Exception as e:
                logger.error(f"获取标签失败: {str(e)}")
                raise
        else:
            try:
                # 读取现有数据
                with open(self.file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # 提取所有标签
                tags = set()
                for item in data:
                    item_tags = item.get("metadata", {}).get("tags", [])
                    if item_tags:
                        tags.update(item_tags)
                
                return sorted(list(tags))
            except Exception as e:
                logger.error(f"获取标签失败: {str(e)}")
                raise
    
    def count_knowledge(self, query: str = None, category: str = None, 
                        tags: List[str] = None) -> int:
        """
        计算知识条目数量
        
        Args:
            query: 搜索关键词
            category: 分类
            tags: 标签列表
            
        Returns:
            知识条目数量
        """
        if self.use_mongodb:
            try:
                # 构建查询条件
                query_conditions = {}
                
                if query:
                    query_conditions["$or"] = [
                        {"question": {"$regex": query, "$options": "i"}},
                        {"answer": {"$regex": query, "$options": "i"}}
                    ]
                
                if category:
                    query_conditions["metadata.category"] = category
                
                if tags:
                    query_conditions["metadata.tags"] = {"$in": tags}
                
                # 执行计数查询
                count = self.collection.count_documents(query_conditions)
                
                return count
            except Exception as e:
                logger.error(f"计数知识失败: {str(e)}")
                raise
        else:
            try:
                # 使用搜索函数获取所有匹配的结果(不分页)
                results = self.search_knowledge(query, category, tags, limit=float('inf'))
                
                return len(results)
            except Exception as e:
                logger.error(f"计数知识失败: {str(e)}")
                raise
    
    def import_knowledge(self, knowledge_list: List[Dict[str, Any]]) -> int:
        """
        批量导入知识
        
        Args:
            knowledge_list: 知识条目列表
            
        Returns:
            成功导入的条目数量
        """
        if not knowledge_list:
            return 0
        
        imported_count = 0
        
        if self.use_mongodb:
            try:
                # 获取当前最大ID
                next_id = self._get_next_id()
                
                # 准备导入数据
                timestamp = datetime.now().isoformat()
                to_import = []
                
                for i, item in enumerate(knowledge_list):
                    # 确保每个条目都有ID
                    knowledge_item = item.copy()
                    knowledge_item["id"] = next_id + i
                    
                    # 确保有创建和更新时间
                    if "created_at" not in knowledge_item:
                        knowledge_item["created_at"] = timestamp
                    if "updated_at" not in knowledge_item:
                        knowledge_item["updated_at"] = timestamp
                    
                    to_import.append(knowledge_item)
                
                # 批量插入
                if to_import:
                    result = self.collection.insert_many(to_import)
                    imported_count = len(result.inserted_ids)
                    logger.info(f"已导入 {imported_count} 条知识到MongoDB")
            except Exception as e:
                logger.error(f"导入知识到MongoDB失败: {str(e)}")
                raise
        else:
            try:
                # 读取现有数据
                with open(self.file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # 获取当前最大ID
                next_id = self._get_next_id()
                
                # 准备导入数据
                timestamp = datetime.now().isoformat()
                
                for i, item in enumerate(knowledge_list):
                    # 确保每个条目都有ID
                    knowledge_item = item.copy()
                    knowledge_item["id"] = next_id + i
                    
                    # 确保有创建和更新时间
                    if "created_at" not in knowledge_item:
                        knowledge_item["created_at"] = timestamp
                    if "updated_at" not in knowledge_item:
                        knowledge_item["updated_at"] = timestamp
                    
                    data.append(knowledge_item)
                    imported_count += 1
                
                # 写回文件
                with open(self.file_path, 'w', encoding='utf-8') as f:
                    json.dump(data, f, ensure_ascii=False, indent=2)
                
                logger.info(f"已导入 {imported_count} 条知识到文件")
            except Exception as e:
                logger.error(f"导入知识到文件失败: {str(e)}")
                raise
        
        return imported_count
    
    def export_knowledge(self, query: str = None, category: str = None, 
                         tags: List[str] = None) -> List[Dict[str, Any]]:
        """
        导出知识
        
        Args:
            query: 搜索关键词
            category: 分类
            tags: 标签列表
            
        Returns:
            知识条目列表
        """
        # 使用搜索函数获取所有匹配的结果(不分页)
        return self.search_knowledge(query, category, tags, limit=float('inf'))
    
    def backup_knowledge(self, backup_path: str = None) -> str:
        """
        备份知识库
        
        Args:
            backup_path: 备份文件路径,如果为None则自动生成
            
        Returns:
            备份文件路径
        """
        # 如果未指定备份路径,则自动生成
        if backup_path is None:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_dir = os.path.join(os.path.dirname(self.file_path), "backups")
            os.makedirs(backup_dir, exist_ok=True)
            backup_path = os.path.join(backup_dir, f"knowledge_backup_{timestamp}.json")
        
        # 导出所有知识
        all_knowledge = self.export_knowledge()
        
        # 写入备份文件
        with open(backup_path, 'w', encoding='utf-8') as f:
            json.dump(all_knowledge, f, ensure_ascii=False, indent=2)
        
        logger.info(f"已备份 {len(all_knowledge)} 条知识到 {backup_path}")
        
        return backup_path
    
    def restore_knowledge(self, backup_path: str, clear_existing: bool = False) -> int:
        """
        从备份恢复知识库
        
        Args:
            backup_path: 备份文件路径
            clear_existing: 是否清除现有数据
            
        Returns:
            恢复的条目数量
        """
        # 读取备份文件
        with open(backup_path, 'r', encoding='utf-8') as f:
            backup_data = json.load(f)
        
        # 清除现有数据
        if clear_existing:
            if self.use_mongodb:
                self.collection.delete_many({})
            else:
                with open(self.file_path, 'w', encoding='utf-8') as f:
                    json.dump([], f, ensure_ascii=False, indent=2)
        
        # 导入备份数据
        imported_count = self.import_knowledge(backup_data)
        
        logger.info(f"已从 {backup_path} 恢复 {imported_count} 条知识")
        
        return imported_count
2. Knowledge Base API Module

knowledge_base_integration.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
知识库集成模块:将知识库与聊天机器人系统集成
"""

import os
import logging
from typing import List, Dict, Any, Optional, Tuple

from knowledge_base_db import KnowledgeBaseDB

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("知识库集成")

class KnowledgeBaseIntegration:
    """知识库集成类,提供与聊天机器人系统的集成接口"""
    
    def __init__(self, nlp_module=None, use_mongodb: bool = False, 
                 mongo_uri: str = None, similarity_threshold: float = 0.6):
        """
        初始化知识库集成
        
        Args:
            nlp_module: NLP模块实例,用于计算文本相似度
            use_mongodb: 是否使用MongoDB存储
            mongo_uri: MongoDB连接URI
            similarity_threshold: 相似度阈值,低于此值的匹配将被过滤
        """
        self.nlp_module = nlp_module
        self.kb_db = KnowledgeBaseDB(use_mongodb=use_mongodb, mongo_uri=mongo_uri)
        self.similarity_threshold = similarity_threshold
        
        logger.info("知识库集成模块初始化完成")
    
    def query(self, question: str, max_results: int = 3) -> List[Dict[str, Any]]:
        """
        查询知识库
        
        Args:
            question: 用户问题
            max_results: 最大返回结果数
            
        Returns:
            匹配的知识列表,按相似度降序排序
        """
        logger.info(f"查询知识库: {question}")
        
        # 从知识库获取所有可能相关的知识
        # 这里简单使用关键词匹配,实际应用中可能需要更复杂的检索策略
        results = self.kb_db.search_knowledge(query=question, limit=10)
        
        # 如果没有NLP模块,直接返回结果
        if not self.nlp_module:
            logger.warning("NLP模块未提供,无法计算相似度,直接返回搜索结果")
            return results[:max_results]
        
        # 计算问题与知识库中问题的相似度
        scored_results = []
        for item in results:
            similarity = self.nlp_module.calculate_text_similarity(question, item['question'])
            item['score'] = similarity
            scored_results.append(item)
        
        # 按相似度降序排序
        scored_results.sort(key=lambda x: x['score'], reverse=True)
        
        # 过滤低于阈值的结果
        filtered_results = [item for item in scored_results if item['score'] >= self.similarity_threshold]
        
        logger.info(f"找到 {len(filtered_results)} 条相关知识")
        
        return filtered_results[:max_results]
    
    def get_answer(self, question: str) -> Tuple[Optional[str], float]:
        """
        获取问题的最佳答案
        
        Args:
            question: 用户问题
            
        Returns:
            (最佳答案, 相似度得分),如果没有找到答案则返回 (None, 0)
        """
        results = self.query(question, max_results=1)
        
        if results:
            best_match = results[0]
            return best_match['answer'], best_match.get('score', 0)
        
        return None, 0
    
    def add_to_knowledge_base(self, question: str, answer: str, 
                              category: str = None, tags: List[str] = None) -> Dict[str, Any]:
        """
        添加知识到知识库
        
        Args:
            question: 问题
            answer: 答案
            category: 分类
            tags: 标签列表
            
        Returns:
            添加的知识条目
        """
        metadata = {}
        if category:
            metadata['category'] = category
        if tags:
            metadata['tags'] = tags
        
        return self.kb_db.add_knowledge(question=question, answer=answer, metadata=metadata)
    
    def learn_from_conversation(self, question: str, answer: str, 
                                confidence: float = 0.8) -> Optional[Dict[str, Any]]:
        """
        从对话中学习,将高质量的问答对添加到知识库
        
        Args:
            question: 用户问题
            answer: 系统回答
            confidence: 系统对回答的置信度
            
        Returns:
            添加的知识条目,如果未添加则返回None
        """
        # 只有当置信度高于阈值时才添加到知识库
        if confidence >= 0.8:
            logger.info(f"从对话中学习新知识: {question}")
            
            # 检查是否已存在类似问题
            if self.nlp_module:
                existing = self.query(question, max_results=1)
                if existing and existing[0].get('score', 0) > 0.9:
                    logger.info(f"知识库中已存在类似问题,不添加")
                    return None
            
            # 添加到知识库
            return self.add_to_knowledge_base(
                question=question,
                answer=answer,
                category="对话学习",
                tags=["自动学习"]
            )
        
        return None
    
    def get_related_knowledge(self, context: str, max_results: int = 3) -> List[Dict[str, Any]]:
        """
        根据上下文获取相关知识
        
        Args:
            context: 对话上下文
            max_results: 最大返回结果数
            
        Returns:
            相关知识列表
        """
        # 如果有NLP模块,可以提取上下文中的关键信息
        keywords = []
        if self.nlp_module:
            entities = self.nlp_module.extract_entities(context)
            keywords = [entity['value'] for entity in entities if 'value' in entity]
        
        # 如果没有提取到关键词,使用整个上下文
        if not keywords:
            return self.query(context, max_results=max_results)
        
        # 使用关键词查询
        all_results = []
        for keyword in keywords:
            results = self.kb_db.search_knowledge(query=keyword, limit=5)
            all_results.extend(results)
        
        # 去重
        unique_results = []
        seen_ids = set()
        for item in all_results:
            if item['id'] not in seen_ids:
                seen_ids.add(item['id'])
                unique_results.append(item)
        
        # 如果有NLP模块,计算相似度并排序
        if self.nlp_module:
            for item in unique_results:
                # 使用问题和答案计算与上下文的相似度
                text = item['question'] + " " + item['answer']
                similarity = self.nlp_module.calculate_text_similarity(context, text)
                item['score'] = similarity
            
            # 按相似度降序排序
            unique_results.sort(key=lambda x: x['score'], reverse=True)
        
        return unique_results[:max_results]
7. Main App Integration

README.md

# 智能聊天机器人系统

一个完整的智能聊天机器人系统,包含自然语言处理、对话管理、知识库和用户界面等组件。

## 功能特点

- **自然语言处理**:意图识别、实体提取、情感分析和文本相似度计算
- **对话管理**:多轮对话处理、上下文管理和状态跟踪
- **知识库**:基于向量的相似度搜索和文本匹配
- **数据库集成**:支持MongoDB或文件存储
- **用户友好界面**:响应式Web界面,支持聊天、知识库管理和设置

## 系统架构

系统由以下主要组件构成:

1. **NLP模块** (`nlp_module.py`):处理自然语言理解任务
2. **对话管理器** (`dialog_manager.py`):管理对话状态和生成响应
3. **知识库** (`knowledge_base.py`):存储和检索知识
4. **数据库** (`database.py`):管理对话历史和用户信息
5. **Web应用** (`app.py`):提供RESTful API接口
6. **前端界面**:HTML/CSS/JavaScript实现的用户界面

## 安装指南

### 环境要求

- Python 3.8+
- MongoDB (可选,也支持文件存储)

### 安装步骤

1. 克隆或下载代码库

2. 安装依赖包
```bash
pip install -r requirements.txt
  1. 配置环境变量(可选)
# MongoDB连接(可选)
export MONGO_URI=mongodb://localhost:27017/

使用方法

启动服务器

python app.py

服务器默认在 http://localhost:5000 启动

使用Web界面

  1. 打开浏览器访问 http://localhost:5000
  2. 在聊天界面输入问题与机器人交互
  3. 使用知识库管理页面添加或修改知识
  4. 在设置页面配置用户偏好

API接口

  • 聊天接口: POST /api/chat

    {
      "message": "你好,请问什么是人工智能?",
      "user_id": "user123"
    }
    
  • 知识库查询: GET /api/knowledge?query=人工智能

  • 添加知识: POST /api/knowledge

    {
      "question": "什么是机器学习?",
      "answer": "机器学习是人工智能的一个子领域...",
      "metadata": {
        "category": "技术知识",
        "tags": ["机器学习", "AI"]
      }
    }
    
  • 历史记录: GET /api/history?user_id=user123

配置文件

系统使用以下配置文件:

  • config/dialog_policies.json: 对话策略配置
  • config/response_templates.json: 响应模板配置

扩展开发

添加新的意图

  1. 在NLP模块中添加新的意图识别模式
  2. 在对话策略配置中添加新意图的处理流程
  3. 在响应模板中添加相应的回复模板

自定义知识库

  1. knowledge目录下添加或修改知识库文件
  2. 使用Web界面的知识库管理功能添加新知识

故障排除

  • MongoDB连接问题:检查MongoDB服务是否运行,连接字符串是否正确
  • NLP模型加载失败:确保已安装所有依赖,并下载了必要的模型文件
  • API请求失败:检查请求格式和参数是否正确

许可证

MIT License


### requirements.txt

```text/plain
flask==2.0.1
flask-cors==3.0.10
pymongo==4.0.1
python-dotenv==0.19.1
transformers==4.12.5
torch==1.10.0
jieba==0.42.1
scikit-learn==1.0.1
numpy==1.21.4
requests==2.26.0
faiss-cpu==1.7.1
sentence-transformers==2.1.0

test_chatbot.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
测试脚本:用于测试聊天机器人系统的各个组件
"""

import os
import sys
import json
import time
import argparse
from datetime import datetime

# 添加项目根目录到系统路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# 导入聊天机器人组件
from dialog_manager import DialogManager
from knowledge_base import KnowledgeBase
from database import Database
try:
    from nlp_module import NLPModule
    nlp_available = True
except ImportError:
    print("警告: NLP模块导入失败,将使用简化版NLP功能")
    nlp_available = False
    
    # 简化版NLP模块,用于测试
    class SimpleNLP:
        def __init__(self):
            self.intents = {
                "问候": ["你好", "您好", "早上好", "晚上好", "嗨", "hi", "hello"],
                "查询信息": ["什么是", "如何", "怎么", "解释", "介绍", "说明"],
                "结束对话": ["再见", "拜拜", "goodbye", "bye", "结束", "退出"]
            }
            
        def recognize_intent(self, text):
            for intent, patterns in self.intents.items():
                for pattern in patterns:
                    if pattern in text:
                        return intent, 0.8
            return "查询信息", 0.3
            
        def extract_entities(self, text):
            entities = []
            # 简单的关键词提取
            keywords = [word for word in text if len(word) > 1]
            if keywords:
                entities = [{"type": "keyword", "value": kw} for kw in keywords[:3]]
            return entities
            
        def calculate_text_similarity(self, text1, text2):
            # 简单的相似度计算
            common_words = set(text1) & set(text2)
            return len(common_words) / max(len(set(text1)), len(set(text2)), 1)
            
        def analyze_sentiment(self, text):
            # 简单的情感分析
            positive_words = ["好", "喜欢", "棒", "优秀", "感谢", "谢谢"]
            negative_words = ["不", "差", "糟", "失望", "讨厌", "烦"]
            
            pos_score = sum(1 for word in positive_words if word in text)
            neg_score = sum(1 for word in negative_words if word in text)
            
            if pos_score > neg_score:
                return "positive", (pos_score - neg_score) / (pos_score + neg_score + 1)
            elif neg_score > pos_score:
                return "negative", (neg_score - pos_score) / (pos_score + neg_score + 1)
            else:
                return "neutral", 0.5


class ChatbotTester:
    """聊天机器人测试类"""
    
    def __init__(self, use_file_storage=True):
        """初始化测试环境"""
        print("初始化聊天机器人测试环境...")
        
        # 加载配置文件
        self.load_configs()
        
        # 初始化组件
        self.db = Database(use_mongodb=not use_file_storage)
        self.kb = KnowledgeBase(self.db)
        
        # 使用完整NLP模块或简化版
        if nlp_available:
            self.nlp = NLPModule()
        else:
            self.nlp = SimpleNLP()
            
        self.dialog_manager = DialogManager(self.nlp, self.kb, self.db)
        
        # 测试用户ID
        self.test_user_id = f"test_user_{int(time.time())}"
        
        print(f"测试环境初始化完成,测试用户ID: {self.test_user_id}")
        
    def load_configs(self):
        """加载配置文件"""
        try:
            with open("config/dialog_policies.json", "r", encoding="utf-8") as f:
                self.dialog_policies = json.load(f)
                
            with open("config/response_templates.json", "r", encoding="utf-8") as f:
                self.response_templates = json.load(f)
                
            print("配置文件加载成功")
        except Exception as e:
            print(f"加载配置文件失败: {str(e)}")
            self.dialog_policies = {}
            self.response_templates = {}
    
    def test_nlp_module(self, text):
        """测试NLP模块"""
        print("\n===== NLP模块测试 =====")
        
        # 测试意图识别
        intent, confidence = self.nlp.recognize_intent(text)
        print(f"意图识别结果: {intent} (置信度: {confidence:.2f})")
        
        # 测试实体提取
        entities = self.nlp.extract_entities(text)
        print(f"实体提取结果: {entities}")
        
        # 测试情感分析
        sentiment, score = self.nlp.analyze_sentiment(text)
        print(f"情感分析结果: {sentiment} (分数: {score:.2f})")
        
        return intent, entities, sentiment
    
    def test_knowledge_base(self, query):
        """测试知识库"""
        print("\n===== 知识库测试 =====")
        
        # 测试知识检索
        results = self.kb.search(query)
        
        if results:
            print(f"找到 {len(results)} 条相关知识:")
            for i, result in enumerate(results[:3], 1):  # 只显示前3条
                print(f"{i}. 问题: {result['question']}")
                print(f"   答案: {result['answer'][:100]}..." if len(result['answer']) > 100 else f"   答案: {result['answer']}")
                print(f"   相关度: {result.get('score', 'N/A')}")
                print()
        else:
            print("未找到相关知识")
            
        return results
    
    def test_dialog_manager(self, text):
        """测试对话管理器"""
        print("\n===== 对话管理器测试 =====")
        
        # 获取响应
        response = self.dialog_manager.get_response(text, self.test_user_id)
        
        print(f"用户输入: {text}")
        print(f"系统响应: {response}")
        
        # 获取当前对话状态
        session = self.dialog_manager.get_session(self.test_user_id)
        print(f"当前对话状态: {session.get('current_state', 'unknown')}")
        print(f"对话轮次: {session.get('turn_count', 0)}")
        
        return response, session
    
    def test_database(self):
        """测试数据库"""
        print("\n===== 数据库测试 =====")
        
        # 测试保存对话历史
        history_id = self.db.save_chat_history(
            self.test_user_id,
            "测试问题",
            "测试回答",
            {"intent": "测试意图", "entities": []}
        )
        
        print(f"保存对话历史成功,ID: {history_id}")
        
        # 测试获取对话历史
        history = self.db.get_chat_history(self.test_user_id, limit=5)
        print(f"获取到 {len(history)} 条对话历史")
        
        return history
    
    def run_interactive_test(self):
        """运行交互式测试"""
        print("\n===== 开始交互式测试 =====")
        print("输入 'exit' 或 'quit' 结束测试")
        
        while True:
            user_input = input("\n请输入测试文本: ").strip()
            
            if user_input.lower() in ['exit', 'quit', '退出', '结束']:
                break
                
            # 测试NLP模块
            intent, entities, sentiment = self.test_nlp_module(user_input)
            
            # 测试知识库
            kb_results = self.test_knowledge_base(user_input)
            
            # 测试对话管理器
            response, session = self.test_dialog_manager(user_input)
            
            # 记录测试结果
            print("\n----- 测试结果摘要 -----")
            print(f"识别意图: {intent}")
            print(f"情感倾向: {sentiment}")
            print(f"知识匹配: {'成功' if kb_results else '未找到'}")
            print(f"对话状态: {session.get('current_state', 'unknown')}")
            print(f"系统响应: {response}")
    
    def run_benchmark_test(self, test_cases=None):
        """运行基准测试"""
        if test_cases is None:
            # 默认测试用例
            test_cases = [
                "你好,请问你是谁?",
                "什么是人工智能?",
                "如何评估聊天机器人的性能?",
                "谢谢你的回答",
                "再见"
            ]
            
        print("\n===== 开始基准测试 =====")
        print(f"测试用例数量: {len(test_cases)}")
        
        results = []
        start_time = time.time()
        
        for i, test_case in enumerate(test_cases, 1):
            print(f"\n测试用例 {i}/{len(test_cases)}: {test_case}")
            
            case_start = time.time()
            
            # 测试对话管理器
            response, session = self.test_dialog_manager(test_case)
            
            case_time = time.time() - case_start
            
            results.append({
                "input": test_case,
                "response": response,
                "state": session.get("current_state", "unknown"),
                "time": case_time
            })
            
            print(f"处理时间: {case_time:.3f}秒")
            
        total_time = time.time() - start_time
        avg_time = total_time / len(test_cases)
        
        print("\n===== 基准测试结果 =====")
        print(f"总测试用例: {len(test_cases)}")
        print(f"总处理时间: {total_time:.3f}秒")
        print(f"平均处理时间: {avg_time:.3f}秒/用例")
        
        return results


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description="聊天机器人测试工具")
    parser.add_argument("--mode", choices=["interactive", "benchmark"], default="interactive",
                        help="测试模式: interactive(交互式) 或 benchmark(基准测试)")
    parser.add_argument("--storage", choices=["file", "mongo"], default="file",
                        help="存储类型: file(文件) 或 mongo(MongoDB)")
    
    args = parser.parse_args()
    
    # 创建测试实例
    tester = ChatbotTester(use_file_storage=(args.storage == "file"))
    
    # 根据模式运行测试
    if args.mode == "interactive":
        tester.run_interactive_test()
    else:
        results = tester.run_benchmark_test()
        
        # 保存基准测试结果
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        result_file = f"benchmark_results_{timestamp}.json"
        
        with open(result_file, "w", encoding="utf-8") as f:
            json.dump(results, f, ensure_ascii=False, indent=2)
            
        print(f"基准测试结果已保存到: {result_file}")


if __name__ == "__main__":
    main()

config\dialog_policies.json

{
  "问候": {
    "next_states": ["提供帮助", "闲聊"],
    "priority": "提供帮助"
  },
  "查询信息": {
    "next_states": ["提供信息", "请求更多信息"],
    "priority": "提供信息"
  },
  "预订服务": {
    "next_states": ["确认预订", "请求更多信息"],
    "priority": "请求更多信息"
  },
  "投诉反馈": {
    "next_states": ["道歉", "解决问题"],
    "priority": "道歉"
  },
  "寻求帮助": {
    "next_states": ["提供帮助", "转人工"],
    "priority": "提供帮助"
  },
  "结束对话": {
    "next_states": ["结束", "挽留"],
    "priority": "结束"
  },
  "闲聊": {
    "next_states": ["闲聊", "引导任务"],
    "priority": "闲聊"
  },
  "感谢": {
    "next_states": ["客气回应", "提供进一步帮助"],
    "priority": "客气回应"
  },
  "确认": {
    "next_states": ["执行确认操作", "提供进一步帮助"],
    "priority": "执行确认操作"
  },
  "拒绝": {
    "next_states": ["接受拒绝", "提供替代方案"],
    "priority": "提供替代方案"
  }
}

config\response_templates.json

{
  "问候": [
    "您好!我是智能助手,有什么可以帮您的吗?",
    "你好!很高兴为您服务,请问有什么需要帮助的吗?",
    "您好,我是AI助手,请问需要什么帮助?"
  ],
  "提供帮助": [
    "我可以帮您查询信息、预订服务或回答问题,请告诉我您需要什么帮助?",
    "有什么我能帮到您的吗?例如查询信息、预订服务等。",
    "请问您需要什么帮助?我可以回答问题或提供各种服务。"
  ],
  "提供信息": [
    "根据您的查询,我找到了以下信息:{info}",
    "以下是您查询的信息:{info}",
    "关于您的问题,我可以告诉您:{info}"
  ],
  "请求更多信息": [
    "为了更好地帮助您,我需要一些额外信息。请问{question}?",
    "能否提供更多细节?比如{question}?",
    "请告诉我{question},这样我能更准确地帮助您。"
  ],
  "确认预订": [
    "已为您预订成功!预订详情:{details}",
    "您的预订已确认。{details}",
    "预订成功,详情如下:{details}"
  ],
  "道歉": [
    "非常抱歉给您带来不便。{apology}",
    "对此我深表歉意。{apology}",
    "很遗憾您遇到了问题,{apology}"
  ],
  "解决问题": [
    "针对您反馈的问题,我们可以{solution}",
    "为解决您的问题,我建议{solution}",
    "我们将通过以下方式解决您的问题:{solution}"
  ],
  "转人工": [
    "您的问题可能需要人工客服协助,是否需要为您转接?",
    "这个问题可能需要专业人员处理,要转接人工客服吗?",
    "看起来这个问题比较复杂,需要转接人工客服吗?"
  ],
  "结束": [
    "感谢您的使用,再见!",
    "期待下次为您服务,再见!",
    "再见,有需要随时找我!"
  ],
  "挽留": [
    "在您离开前,还有其他可以帮到您的吗?",
    "还有什么我可以帮您的吗?",
    "您确定现在要结束对话吗?我还可以为您提供更多帮助。"
  ],
  "闲聊": [
    "是的,{chat_response}",
    "嗯,{chat_response}",
    "{chat_response}"
  ],
  "引导任务": [
    "我们可以聊聊,不过我最擅长帮您完成具体的任务,比如查询信息或预订服务。",
    "很高兴与您聊天。另外,我还可以帮您查询信息或预订服务,需要我帮忙吗?",
    "闲聊之余,我还可以帮您完成很多任务,比如查询信息或预订服务,有需要吗?"
  ],
  "客气回应": [
    "不客气,这是我应该做的!",
    "很高兴能帮到您!",
    "您的满意是我最大的动力!"
  ],
  "提供进一步帮助": [
    "还有什么我可以帮您的吗?",
    "还需要其他帮助吗?",
    "我可以为您做些什么?"
  ],
  "执行确认操作": [
    "好的,我已经为您{action}",
    "已完成,{action}",
    "确认完成,{action}"
  ],
  "接受拒绝": [
    "好的,我理解。",
    "没问题,尊重您的选择。",
    "好的,如有需要随时告诉我。"
  ],
  "提供替代方案": [
    "没关系,我还可以为您提供以下替代方案:{alternatives}",
    "理解您的考虑,以下是其他可能的选择:{alternatives}",
    "或许这些替代方案更适合您:{alternatives}"
  ],
  "fallback": [
    "抱歉,我没有理解您的意思。能否换个方式表达?",
    "对不起,我可能没有理解正确。能请您重新描述一下吗?",
    "很抱歉,我没能理解您的意思。请问您能重新表述一下吗?"
  ]
}

knowledge\knowledge_base.json

[
  {
    "id": 0,
    "question": "什么是人工智能?",
    "answer": "人工智能(Artificial Intelligence,简称AI)是计算机科学的一个分支,致力于创造能够模拟人类智能行为的机器和系统。它包括机器学习、自然语言处理、计算机视觉等多个领域,目标是使计算机能够理解、学习、推理和解决问题。",
    "metadata": {
      "category": "基础知识",
      "tags": ["AI", "人工智能", "技术"]
    },
    "created_at": "2025-03-06T15:00:00.000Z"
  },
  {
    "id": 1,
    "question": "什么是聊天机器人?",
    "answer": "聊天机器人是一种能够模拟人类对话的计算机程序,它通过文本或语音与用户进行交互。聊天机器人可以基于规则、检索或生成式方法工作,常用于客户服务、信息查询和个人助手等场景。现代聊天机器人通常结合了自然语言处理和机器学习技术,以提供更自然、更智能的对话体验。",
    "metadata": {
      "category": "基础知识",
      "tags": ["聊天机器人", "对话系统", "人机交互"]
    },
    "created_at": "2025-03-06T15:05:00.000Z"
  },
  {
    "id": 2,
    "question": "自然语言处理是什么?",
    "answer": "自然语言处理(Natural Language Processing,简称NLP)是人工智能的一个分支,专注于计算机与人类语言之间的交互。它使计算机能够理解、解释和生成人类语言,涉及文本分类、情感分析、命名实体识别、机器翻译等任务。NLP技术广泛应用于搜索引擎、聊天机器人、语音助手和文本分析等领域。",
    "metadata": {
      "category": "技术知识",
      "tags": ["NLP", "自然语言处理", "文本分析"]
    },
    "created_at": "2025-03-06T15:10:00.000Z"
  },
  {
    "id": 3,
    "question": "BERT模型是什么?",
    "answer": "BERT(Bidirectional Encoder Representations from Transformers)是由Google开发的预训练语言模型,于2018年发布。它的主要创新在于双向训练机制,能够同时考虑文本的左右上下文,从而更好地理解语言的语义。BERT在多种NLP任务上取得了突破性的成果,包括问答、情感分析和命名实体识别等,成为了现代NLP技术的重要基础。",
    "metadata": {
      "category": "技术知识",
      "tags": ["BERT", "预训练模型", "Transformer"]
    },
    "created_at": "2025-03-06T15:15:00.000Z"
  },
  {
    "id": 4,
    "question": "如何评估聊天机器人的性能?",
    "answer": "评估聊天机器人性能通常从多个维度进行:1)准确性:机器人正确理解和回答问题的能力;2)相关性:回复与用户查询的相关程度;3)连贯性:保持对话流畅和上下文一致的能力;4)多样性:生成多样化而非重复回复的能力;5)用户满意度:通过用户反馈收集的主观评价;6)任务完成率:成功帮助用户完成特定任务的比例。常用的评估方法包括人工评估、自动化指标(如BLEU、ROUGE)和A/B测试等。",
    "metadata": {
      "category": "评估方法",
      "tags": ["性能评估", "指标", "用户体验"]
    },
    "created_at": "2025-03-06T15:20:00.000Z"
  },
  {
    "id": 5,
    "question": "什么是意图识别?",
    "answer": "意图识别是自然语言处理中的一项关键任务,旨在确定用户在对话或查询中的目的或意图。例如,当用户说"今天天气怎么样"时,系统会识别出用户的意图是"查询天气"。意图识别通常使用分类算法实现,如支持向量机、神经网络或预训练语言模型。在聊天机器人和对话系统中,准确的意图识别是理解用户需求和提供相关回复的基础。",
    "metadata": {
      "category": "技术知识",
      "tags": ["意图识别", "NLU", "对话系统"]
    },
    "created_at": "2025-03-06T15:25:00.000Z"
  },
  {
    "id": 6,
    "question": "实体提取是什么?",
    "answer": "实体提取(Entity Extraction)是从非结构化文本中识别和提取特定类型信息的过程,如人名、地点、组织、日期、时间等。这是自然语言处理中的一项基础任务,也称为命名实体识别(Named Entity Recognition,NER)。在聊天机器人中,实体提取帮助系统理解用户输入中的关键信息,例如从"我想预订明天下午2点从北京到上海的机票"中提取出时间(明天下午2点)、出发地(北京)和目的地(上海)等实体。",
    "metadata": {
      "category": "技术知识",
      "tags": ["实体提取", "NER", "信息提取"]
    },
    "created_at": "2025-03-06T15:30:00.000Z"
  },
  {
    "id": 7,
    "question": "什么是对话状态跟踪?",
    "answer": "对话状态跟踪(Dialogue State Tracking,DST)是对话系统中的一个关键组件,负责维护和更新对话的当前状态。它记录用户在对话过程中表达的意图、提供的信息和系统的响应,以便系统能够基于完整的上下文做出决策。例如,在一个订票对话中,DST会跟踪用户已提供的信息(如出发地、目的地、日期)和尚未提供的信息,帮助系统决定下一步应该询问什么或执行什么操作。",
    "metadata": {
      "category": "技术知识",
      "tags": ["对话状态", "上下文管理", "对话系统"]
    },
    "created_at": "2025-03-06T15:35:00.000Z"
  },
  {
    "id": 8,
    "question": "如何处理聊天机器人中的多轮对话?",
    "answer": "处理多轮对话需要几个关键技术:1)上下文管理:保存和更新对话历史,使系统能够理解当前输入与之前交流的关系;2)指代消解:解决代词(如"它"、"这个")指向的实体;3)对话状态跟踪:维护用户意图和已提供信息的状态;4)对话策略:基于当前状态决定下一步行动;5)回复生成:生成考虑上下文的自然回复。现代方法通常使用基于Transformer的模型(如BERT、GPT)来处理整个对话历史,或使用专门的对话管理框架(如Rasa)来协调这些组件。",
    "metadata": {
      "category": "技术知识",
      "tags": ["多轮对话", "上下文理解", "对话管理"]
    },
    "created_at": "2025-03-06T15:40:00.000Z"
  },
  {
    "id": 9,
    "question": "知识图谱如何应用于聊天机器人?",
    "answer": "知识图谱在聊天机器人中的应用主要体现在:1)增强问答能力:通过结构化知识提供准确、详细的回答;2)实体链接:将用户提及的实体映射到知识图谱中的节点;3)关系推理:基于实体间的关系推断新信息;4)对话引导:根据知识图谱中的关联信息主动引导对话方向;5)个性化交互:利用用户相关的知识节点提供定制化服务。例如,一个旅游聊天机器人可以使用包含景点、酒店、交通等信息的知识图谱,帮助用户规划行程并回答相关问题。",
    "metadata": {
      "category": "技术应用",
      "tags": ["知识图谱", "语义网络", "结构化知识"]
    },
    "created_at": "2025-03-06T15:45:00.000Z"
  }
]

static\index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>智能聊天机器人</title>
    <link rel="stylesheet" href="css/styles.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
</head>
<body>
    <div class="container-fluid">
        <div class="row vh-100">
            <!-- 侧边栏 -->
            <div class="col-md-3 col-lg-2 sidebar">
                <div class="sidebar-header">
                    <h3>智能聊天助手</h3>
                </div>
                <div class="sidebar-menu">
                    <ul class="nav flex-column">
                        <li class="nav-item">
                            <a class="nav-link active" href="#" id="new-chat">
                                <i class="bi bi-plus-circle"></i> 新对话
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" id="history">
                                <i class="bi bi-clock-history"></i> 历史记录
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" id="knowledge-base">
                                <i class="bi bi-book"></i> 知识库管理
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" id="settings">
                                <i class="bi bi-gear"></i> 设置
                            </a>
                        </li>
                    </ul>
                </div>
                <div class="sidebar-footer">
                    <p>版本: 1.0.0</p>
                    <p>© 2025 智能聊天机器人</p>
                </div>
            </div>

            <!-- 主内容区 -->
            <div class="col-md-9 col-lg-10 main-content">
                <!-- 聊天界面 -->
                <div id="chat-container" class="chat-container">
                    <div class="chat-header">
                        <h4>与AI助手的对话</h4>
                        <div class="chat-actions">
                            <button class="btn btn-sm btn-outline-secondary" id="clear-chat">
                                <i class="bi bi-trash"></i> 清空对话
                            </button>
                        </div>
                    </div>
                    <div class="chat-messages" id="chat-messages">
                        <!-- 欢迎消息 -->
                        <div class="message bot-message">
                            <div class="message-avatar">
                                <i class="bi bi-robot"></i>
                            </div>
                            <div class="message-content">
                                <p>您好!我是智能助手,有什么可以帮您的吗?</p>
                            </div>
                        </div>
                    </div>
                    <div class="chat-input">
                        <form id="chat-form">
                            <div class="input-group">
                                <input type="text" id="user-input" class="form-control" placeholder="输入您的问题..." autocomplete="off">
                                <button type="submit" class="btn btn-primary">
                                    <i class="bi bi-send"></i>
                                </button>
                            </div>
                        </form>
                    </div>
                </div>

                <!-- 知识库管理界面 -->
                <div id="knowledge-container" class="knowledge-container d-none">
                    <div class="knowledge-header">
                        <h4>知识库管理</h4>
                        <div class="knowledge-actions">
                            <button class="btn btn-sm btn-primary" id="add-knowledge">
                                <i class="bi bi-plus"></i> 添加知识
                            </button>
                        </div>
                    </div>
                    <div class="knowledge-content">
                        <div class="card">
                            <div class="card-header">
                                知识条目列表
                            </div>
                            <div class="card-body">
                                <div class="table-responsive">
                                    <table class="table table-hover">
                                        <thead>
                                            <tr>
                                                <th>ID</th>
                                                <th>问题</th>
                                                <th>答案</th>
                                                <th>创建时间</th>
                                                <th>操作</th>
                                            </tr>
                                        </thead>
                                        <tbody id="knowledge-list">
                                            <!-- 知识条目将通过JavaScript动态添加 -->
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 设置界面 -->
                <div id="settings-container" class="settings-container d-none">
                    <div class="settings-header">
                        <h4>设置</h4>
                    </div>
                    <div class="settings-content">
                        <div class="card">
                            <div class="card-header">
                                系统设置
                            </div>
                            <div class="card-body">
                                <form id="settings-form">
                                    <div class="mb-3">
                                        <label for="user-id" class="form-label">用户ID</label>
                                        <input type="text" class="form-control" id="user-id" value="default_user">
                                    </div>
                                    <div class="mb-3 form-check">
                                        <input type="checkbox" class="form-check-input" id="use-mongodb">
                                        <label class="form-check-label" for="use-mongodb">使用MongoDB数据库</label>
                                    </div>
                                    <div class="mb-3">
                                        <label for="mongodb-uri" class="form-label">MongoDB URI</label>
                                        <input type="text" class="form-control" id="mongodb-uri" placeholder="mongodb://localhost:27017/">
                                    </div>
                                    <button type="submit" class="btn btn-primary">保存设置</button>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 历史记录界面 -->
                <div id="history-container" class="history-container d-none">
                    <div class="history-header">
                        <h4>对话历史</h4>
                    </div>
                    <div class="history-content">
                        <div class="card">
                            <div class="card-header">
                                历史对话列表
                            </div>
                            <div class="card-body">
                                <div class="list-group" id="history-list">
                                    <!-- 历史记录将通过JavaScript动态添加 -->
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 模态框:添加知识 -->
    <div class="modal fade" id="add-knowledge-modal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">添加知识条目</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <form id="add-knowledge-form">
                        <div class="mb-3">
                            <label for="knowledge-question" class="form-label">问题</label>
                            <input type="text" class="form-control" id="knowledge-question" required>
                        </div>
                        <div class="mb-3">
                            <label for="knowledge-answer" class="form-label">答案</label>
                            <textarea class="form-control" id="knowledge-answer" rows="3" required></textarea>
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
                    <button type="button" class="btn btn-primary" id="save-knowledge">保存</button>
                </div>
            </div>
        </div>
    </div>

    <!-- 加载脚本 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <script src="js/app.js"></script>
</body>
</html>

static\knowledge_base.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>知识库管理系统</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
    <link rel="stylesheet" href="css/knowledge_base.css">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">
                <i class="bi bi-database-fill"></i> 知识库管理系统
            </a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav me-auto">
                    <li class="nav-item">
                        <a class="nav-link active" href="knowledge_base.html">知识库</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="index.html">聊天机器人</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="container-fluid mt-3">
        <div class="row">
            <!-- 左侧边栏 -->
            <div class="col-md-3 col-lg-2 d-md-block bg-light sidebar">
                <div class="position-sticky pt-3">
                    <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
                        <span>知识库管理</span>
                    </h6>
                    <ul class="nav flex-column">
                        <li class="nav-item">
                            <a class="nav-link active" href="#" id="nav-knowledge-list">
                                <i class="bi bi-list-ul"></i> 知识列表
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" id="nav-add-knowledge">
                                <i class="bi bi-plus-circle"></i> 添加知识
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" id="nav-categories">
                                <i class="bi bi-folder"></i> 分类管理
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" id="nav-tags">
                                <i class="bi bi-tags"></i> 标签管理
                            </a>
                        </li>
                    </ul>

                    <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
                        <span>数据操作</span>
                    </h6>
                    <ul class="nav flex-column mb-2">
                        <li class="nav-item">
                            <a class="nav-link" href="#" id="nav-import">
                                <i class="bi bi-upload"></i> 导入知识
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" id="nav-export">
                                <i class="bi bi-download"></i> 导出知识
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" id="nav-backup">
                                <i class="bi bi-save"></i> 备份/恢复
                            </a>
                        </li>
                    </ul>
                </div>
            </div>

            <!-- 主内容区 -->
            <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
                <!-- 知识列表页 -->
                <div class="page" id="page-knowledge-list">
                    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
                        <h1 class="h2">知识列表</h1>
                        <div class="btn-toolbar mb-2 mb-md-0">
                            <button type="button" class="btn btn-sm btn-primary" id="btn-add-knowledge">
                                <i class="bi bi-plus"></i> 添加知识
                            </button>
                        </div>
                    </div>

                    <!-- 搜索栏 -->
                    <div class="row mb-3">
                        <div class="col-md-6">
                            <div class="input-group">
                                <input type="text" class="form-control" id="search-input" placeholder="搜索知识...">
                                <button class="btn btn-outline-secondary" type="button" id="btn-search">
                                    <i class="bi bi-search"></i>
                                </button>
                            </div>
                        </div>
                        <div class="col-md-3">
                            <select class="form-select" id="category-filter">
                                <option value="">所有分类</option>
                            </select>
                        </div>
                        <div class="col-md-3">
                            <select class="form-select" id="tag-filter">
                                <option value="">所有标签</option>
                            </select>
                        </div>
                    </div>

                    <!-- 知识列表表格 -->
                    <div class="table-responsive">
                        <table class="table table-striped table-hover">
                            <thead>
                                <tr>
                                    <th scope="col">ID</th>
                                    <th scope="col">问题</th>
                                    <th scope="col">分类</th>
                                    <th scope="col">标签</th>
                                    <th scope="col">更新时间</th>
                                    <th scope="col">操作</th>
                                </tr>
                            </thead>
                            <tbody id="knowledge-list">
                                <!-- 知识列表内容将通过JavaScript动态生成 -->
                            </tbody>
                        </table>
                    </div>

                    <!-- 分页控件 -->
                    <nav aria-label="Page navigation">
                        <ul class="pagination justify-content-center" id="pagination">
                            <!-- 分页将通过JavaScript动态生成 -->
                        </ul>
                    </nav>
                </div>

                <!-- 添加/编辑知识页 -->
                <div class="page d-none" id="page-add-knowledge">
                    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
                        <h1 class="h2" id="form-title">添加知识</h1>
                        <div class="btn-toolbar mb-2 mb-md-0">
                            <button type="button" class="btn btn-sm btn-outline-secondary" id="btn-back-to-list">
                                <i class="bi bi-arrow-left"></i> 返回列表
                            </button>
                        </div>
                    </div>

                    <form id="knowledge-form">
                        <input type="hidden" id="knowledge-id">
                        <div class="mb-3">
                            <label for="question" class="form-label">问题</label>
                            <input type="text" class="form-control" id="question" required>
                        </div>
                        <div class="mb-3">
                            <label for="answer" class="form-label">答案</label>
                            <textarea class="form-control" id="answer" rows="6" required></textarea>
                        </div>
                        <div class="row mb-3">
                            <div class="col-md-6">
                                <label for="category" class="form-label">分类</label>
                                <div class="input-group">
                                    <input type="text" class="form-control" id="category" list="category-list">
                                    <datalist id="category-list">
                                        <!-- 分类列表将通过JavaScript动态生成 -->
                                    </datalist>
                                </div>
                            </div>
                            <div class="col-md-6">
                                <label for="tags" class="form-label">标签 (用逗号分隔)</label>
                                <div class="input-group">
                                    <input type="text" class="form-control" id="tags" list="tag-list">
                                    <datalist id="tag-list">
                                        <!-- 标签列表将通过JavaScript动态生成 -->
                                    </datalist>
                                </div>
                            </div>
                        </div>
                        <div class="mb-3">
                            <button type="submit" class="btn btn-primary" id="btn-save">保存</button>
                            <button type="button" class="btn btn-outline-secondary" id="btn-cancel">取消</button>
                        </div>
                    </form>
                </div>

                <!-- 分类管理页 -->
                <div class="page d-none" id="page-categories">
                    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
                        <h1 class="h2">分类管理</h1>
                    </div>
                    <div class="row">
                        <div class="col-md-6">
                            <div class="card">
                                <div class="card-header">分类列表</div>
                                <div class="card-body">
                                    <ul class="list-group" id="category-list-items">
                                        <!-- 分类列表将通过JavaScript动态生成 -->
                                    </ul>
                                </div>
                            </div>
                        </div>
                        <div class="col-md-6">
                            <div class="card">
                                <div class="card-header">分类统计</div>
                                <div class="card-body">
                                    <canvas id="category-chart"></canvas>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签管理页 -->
                <div class="page d-none" id="page-tags">
                    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
                        <h1 class="h2">标签管理</h1>
                    </div>
                    <div class="row">
                        <div class="col-md-6">
                            <div class="card">
                                <div class="card-header">标签列表</div>
                                <div class="card-body">
                                    <div id="tag-cloud">
                                        <!-- 标签云将通过JavaScript动态生成 -->
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div class="col-md-6">
                            <div class="card">
                                <div class="card-header">标签统计</div>
                                <div class="card-body">
                                    <canvas id="tag-chart"></canvas>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 导入知识页 -->
                <div class="page d-none" id="page-import">
                    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
                        <h1 class="h2">导入知识</h1>
                    </div>
                    <div class="card">
                        <div class="card-body">
                            <form id="import-form">
                                <div class="mb-3">
                                    <label for="import-file" class="form-label">选择导入文件</label>
                                    <input class="form-control" type="file" id="import-file" accept=".json,.txt,.csv">
                                    <div class="form-text">支持的文件格式: JSON, TXT, CSV</div>
                                </div>
                                <div class="mb-3">
                                    <button type="submit" class="btn btn-primary" id="btn-import">导入</button>
                                </div>
                            </form>
                            <div class="alert alert-info" role="alert">
                                <h4 class="alert-heading">导入说明</h4>
                                <p>JSON格式要求:数组格式,每个元素包含question、answer和metadata字段。</p>
                                <p>示例:</p>
                                <pre><code>[
  {
    "question": "什么是人工智能?",
    "answer": "人工智能是...",
    "metadata": {
      "category": "技术",
      "tags": ["AI", "技术"]
    }
  }
]</code></pre>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 导出知识页 -->
                <div class="page d-none" id="page-export">
                    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
                        <h1 class="h2">导出知识</h1>
                    </div>
                    <div class="card">
                        <div class="card-body">
                            <form id="export-form">
                                <div class="mb-3">
                                    <label class="form-label">导出范围</label>
                                    <div class="form-check">
                                        <input class="form-check-input" type="radio" name="export-scope" id="export-all" value="all" checked>
                                        <label class="form-check-label" for="export-all">
                                            导出全部知识
                                        </label>
                                    </div>
                                    <div class="form-check">
                                        <input class="form-check-input" type="radio" name="export-scope" id="export-filtered" value="filtered">
                                        <label class="form-check-label" for="export-filtered">
                                            导出筛选后的知识
                                        </label>
                                    </div>
                                </div>
                                <div class="mb-3" id="export-filter-options" style="display: none;">
                                    <div class="row">
                                        <div class="col-md-4">
                                            <input type="text" class="form-control" id="export-query" placeholder="搜索关键词">
                                        </div>
                                        <div class="col-md-4">
                                            <select class="form-select" id="export-category">
                                                <option value="">所有分类</option>
                                            </select>
                                        </div>
                                        <div class="col-md-4">
                                            <select class="form-select" id="export-tag">
                                                <option value="">所有标签</option>
                                            </select>
                                        </div>
                                    </div>
                                </div>
                                <div class="mb-3">
                                    <button type="submit" class="btn btn-primary" id="btn-export">导出</button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>

                <!-- 备份/恢复页 -->
                <div class="page d-none" id="page-backup">
                    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
                        <h1 class="h2">备份/恢复</h1>
                    </div>
                    <div class="row">
                        <div class="col-md-6">
                            <div class="card mb-4">
                                <div class="card-header">创建备份</div>
                                <div class="card-body">
                                    <p>创建知识库的完整备份,以便将来恢复。</p>
                                    <button type="button" class="btn btn-primary" id="btn-create-backup">创建备份</button>
                                </div>
                            </div>
                        </div>
                        <div class="col-md-6">
                            <div class="card mb-4">
                                <div class="card-header">恢复备份</div>
                                <div class="card-body">
                                    <form id="restore-form">
                                        <div class="mb-3">
                                            <label for="backup-file" class="form-label">选择备份文件</label>
                                            <input class="form-control" type="file" id="backup-file" accept=".json">
                                        </div>
                                        <div class="mb-3 form-check">
                                            <input type="checkbox" class="form-check-input" id="clear-existing">
                                            <label class="form-check-label" for="clear-existing">清除现有数据</label>
                                        </div>
                                        <div class="mb-3">
                                            <button type="submit" class="btn btn-warning" id="btn-restore">恢复备份</button>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="card">
                        <div class="card-header">备份历史</div>
                        <div class="card-body">
                            <div class="table-responsive">
                                <table class="table table-striped">
                                    <thead>
                                        <tr>
                                            <th>备份文件</th>
                                            <th>创建时间</th>
                                            <th>操作</th>
                                        </tr>
                                    </thead>
                                    <tbody id="backup-list">
                                        <!-- 备份列表将通过JavaScript动态生成 -->
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                </div>
            </main>
        </div>
    </div>

    <!-- 查看知识详情模态框 -->
    <div class="modal fade" id="view-knowledge-modal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog modal-lg">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="view-knowledge-title">知识详情</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <div class="mb-3">
                        <h5>问题</h5>
                        <p id="view-question"></p>
                    </div>
                    <div class="mb-3">
                        <h5>答案</h5>
                        <div id="view-answer"></div>
                    </div>
                    <div class="row mb-3">
                        <div class="col-md-6">
                            <h5>分类</h5>
                            <p id="view-category"></p>
                        </div>
                        <div class="col-md-6">
                            <h5>标签</h5>
                            <div id="view-tags"></div>
                        </div>
                    </div>
                    <div class="row mb-3">
                        <div class="col-md-6">
                            <h5>创建时间</h5>
                            <p id="view-created-at"></p>
                        </div>
                        <div class="col-md-6">
                            <h5>更新时间</h5>
                            <p id="view-updated-at"></p>
                        </div>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
                    <button type="button" class="btn btn-primary" id="btn-edit-knowledge">编辑</button>
                </div>
            </div>
        </div>
    </div>

    <!-- 确认删除模态框 -->
    <div class="modal fade" id="confirm-delete-modal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">确认删除</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <p>确定要删除这条知识吗?此操作不可撤销。</p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
                    <button type="button" class="btn btn-danger" id="btn-confirm-delete">删除</button>
                </div>
            </div>
        </div>
    </div>

    <!-- 提示模态框 -->
    <div class="modal fade" id="alert-modal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="alert-title">提示</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <p id="alert-message"></p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-primary" data-bs-dismiss="modal">确定</button>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
    <script src="js/knowledge_base.js"></script>
</body>
</html>
4. Knowledge Base CSS

static\css\knowledge_base.css

/* 知识库管理系统样式 */

/* 侧边栏样式 */
.sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 100;
    padding: 48px 0 0;
    box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}

@media (max-width: 767.98px) {
    .sidebar {
        position: static;
        padding-top: 1.5rem;
    }
}

.sidebar-sticky {
    position: relative;
    top: 0;
    height: calc(100vh - 48px);
    padding-top: .5rem;
    overflow-x: hidden;
    overflow-y: auto;
}

.sidebar .nav-link {
    font-weight: 500;
    color: #333;
}

.sidebar .nav-link.active {
    color: #2470dc;
}

.sidebar .nav-link:hover {
    color: #0d6efd;
}

.sidebar-heading {
    font-size: .75rem;
    text-transform: uppercase;
}

/* 主内容区样式 */
main {
    padding-top: 1.5rem;
}

@media (min-width: 768px) {
    main {
        padding-left: 2rem;
        padding-right: 2rem;
    }
}

/* 表格样式 */
.table th {
    background-color: #f8f9fa;
}

.table td {
    vertical-align: middle;
}

.table .question-cell {
    max-width: 300px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 标签样式 */
.badge-tag {
    background-color: #e9ecef;
    color: #495057;
    margin-right: 5px;
    margin-bottom: 5px;
    display: inline-block;
}

/* 标签云样式 */
#tag-cloud {
    text-align: center;
    padding: 15px;
}

.tag-item {
    display: inline-block;
    margin: 5px;
    padding: 5px 10px;
    background-color: #f8f9fa;
    border-radius: 15px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.tag-item:hover {
    background-color: #0d6efd;
    color: white;
}

/* 分页样式 */
.pagination {
    margin-top: 20px;
}

/* 表单样式 */
.form-label {
    font-weight: 500;
}

textarea.form-control {
    min-height: 150px;
}

/* 卡片样式 */
.card {
    margin-bottom: 20px;
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}

.card-header {
    background-color: #f8f9fa;
    font-weight: 500;
}

/* 模态框样式 */
.modal-body h5 {
    color: #6c757d;
    font-size: 1rem;
    margin-bottom: 0.5rem;
}

#view-answer {
    background-color: #f8f9fa;
    padding: 10px;
    border-radius: 5px;
    white-space: pre-line;
}

#view-tags .badge {
    margin-right: 5px;
}

/* 导入/导出页面样式 */
pre {
    background-color: #f8f9fa;
    padding: 10px;
    border-radius: 5px;
    font-size: 0.875rem;
}

code {
    color: #d63384;
}

/* 动画效果 */
.fade-in {
    animation: fadeIn 0.5s;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}
5. Knowledge Base JavaScript

static\css\styles.css

/* 全局样式 */
:root {
    --primary-color: #4a6baf;
    --secondary-color: #6c757d;
    --accent-color: #3a5795;
    --light-color: #f8f9fa;
    --dark-color: #343a40;
    --success-color: #28a745;
    --info-color: #17a2b8;
    --warning-color: #ffc107;
    --danger-color: #dc3545;
    --font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
    font-family: var(--font-family);
    background-color: #f5f7fb;
    color: #333;
    margin: 0;
    padding: 0;
    overflow-x: hidden;
}

/* 侧边栏样式 */
.sidebar {
    background-color: var(--primary-color);
    color: white;
    height: 100vh;
    padding: 0;
    display: flex;
    flex-direction: column;
    box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
}

.sidebar-header {
    padding: 20px 15px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.sidebar-header h3 {
    margin: 0;
    font-size: 1.5rem;
}

.sidebar-menu {
    flex-grow: 1;
    padding: 15px 0;
}

.sidebar-menu .nav-link {
    color: rgba(255, 255, 255, 0.8);
    padding: 10px 15px;
    transition: all 0.3s ease;
}

.sidebar-menu .nav-link:hover,
.sidebar-menu .nav-link.active {
    color: white;
    background-color: rgba(255, 255, 255, 0.1);
}

.sidebar-menu .nav-link i {
    margin-right: 10px;
}

.sidebar-footer {
    padding: 15px;
    font-size: 0.8rem;
    color: rgba(255, 255, 255, 0.6);
    border-top: 1px solid rgba(255, 255, 255, 0.1);
}

/* 主内容区样式 */
.main-content {
    height: 100vh;
    padding: 0;
    position: relative;
}

/* 聊天界面样式 */
.chat-container {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.chat-header {
    padding: 15px 20px;
    background-color: white;
    border-bottom: 1px solid #e9ecef;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.chat-header h4 {
    margin: 0;
}

.chat-messages {
    flex-grow: 1;
    padding: 20px;
    overflow-y: auto;
    background-color: #f5f7fb;
}

.message {
    display: flex;
    margin-bottom: 20px;
    max-width: 80%;
}

.bot-message {
    align-self: flex-start;
}

.user-message {
    align-self: flex-end;
    flex-direction: row-reverse;
    margin-left: auto;
}

.message-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background-color: var(--primary-color);
    color: white;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-right: 10px;
}

.user-message .message-avatar {
    background-color: var(--accent-color);
    margin-right: 0;
    margin-left: 10px;
}

.message-content {
    background-color: white;
    padding: 10px 15px;
    border-radius: 10px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
    max-width: calc(100% - 50px);
}

.user-message .message-content {
    background-color: var(--primary-color);
    color: white;
}

.message-content p {
    margin: 0;
}

.chat-input {
    padding: 15px 20px;
    background-color: white;
    border-top: 1px solid #e9ecef;
}

.chat-input .form-control {
    border-radius: 20px;
    padding-left: 15px;
}

.chat-input .btn {
    border-radius: 20px;
    margin-left: 10px;
}

/* 知识库界面样式 */
.knowledge-container,
.settings-container,
.history-container {
    height: 100%;
    padding: 20px;
    overflow-y: auto;
}

.knowledge-header,
.settings-header,
.history-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}

.knowledge-content,
.settings-content,
.history-content {
    background-color: white;
    border-radius: 5px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* 响应式调整 */
@media (max-width: 768px) {
    .sidebar {
        position: fixed;
        z-index: 1000;
        width: 250px;
        left: -250px;
        transition: left 0.3s ease;
    }
    
    .sidebar.show {
        left: 0;
    }
    
    .main-content {
        width: 100%;
    }
    
    .message {
        max-width: 90%;
    }
}

/* 动画效果 */
.fade-in {
    animation: fadeIn 0.3s ease-in-out;
}

@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* 打字动画效果 */
.typing-indicator {
    display: flex;
    padding: 10px;
}

.typing-indicator span {
    height: 8px;
    width: 8px;
    background-color: #bbb;
    border-radius: 50%;
    margin: 0 2px;
    display: inline-block;
    animation: typing 1.4s infinite ease-in-out both;
}

.typing-indicator span:nth-child(1) {
    animation-delay: 0s;
}

.typing-indicator span:nth-child(2) {
    animation-delay: 0.2s;
}

.typing-indicator span:nth-child(3) {
    animation-delay: 0.4s;
}

@keyframes typing {
    0% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.5);
    }
    100% {
        transform: scale(1);
    }
}

static\js\app.js

/**
 * 聊天机器人前端应用
 * 负责处理用户界面交互和与后端API通信
 */

// 全局变量
const API_BASE_URL = '/api';
let userId = localStorage.getItem('userId') || 'default_user';
let currentView = 'chat'; // 当前视图:chat, knowledge, settings, history

// DOM元素
const chatForm = document.getElementById('chat-form');
const userInput = document.getElementById('user-input');
const chatMessages = document.getElementById('chat-messages');
const clearChatBtn = document.getElementById('clear-chat');
const newChatBtn = document.getElementById('new-chat');
const historyBtn = document.getElementById('history');
const knowledgeBaseBtn = document.getElementById('knowledge-base');
const settingsBtn = document.getElementById('settings');
const addKnowledgeBtn = document.getElementById('add-knowledge');
const saveKnowledgeBtn = document.getElementById('save-knowledge');
const settingsForm = document.getElementById('settings-form');

// 容器
const chatContainer = document.getElementById('chat-container');
const knowledgeContainer = document.getElementById('knowledge-container');
const settingsContainer = document.getElementById('settings-container');
const historyContainer = document.getElementById('history-container');

// 模态框
const addKnowledgeModal = new bootstrap.Modal(document.getElementById('add-knowledge-modal'));

// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
    // 加载用户设置
    loadUserSettings();
    
    // 加载聊天历史
    loadChatHistory();
    
    // 注册事件监听器
    registerEventListeners();
});

/**
 * 加载用户设置
 */
function loadUserSettings() {
    // 从localStorage加载设置
    document.getElementById('user-id').value = userId;
    document.getElementById('use-mongodb').checked = localStorage.getItem('useMongoDb') === 'true';
    document.getElementById('mongodb-uri').value = localStorage.getItem('mongoDbUri') || 'mongodb://localhost:27017/';
}

/**
 * 注册事件监听器
 */
function registerEventListeners() {
    // 发送消息
    chatForm.addEventListener('submit', handleChatSubmit);
    
    // 清空聊天
    clearChatBtn.addEventListener('click', clearChat);
    
    // 导航菜单
    newChatBtn.addEventListener('click', () => switchView('chat'));
    historyBtn.addEventListener('click', () => switchView('history'));
    knowledgeBaseBtn.addEventListener('click', () => {
        switchView('knowledge');
        loadKnowledgeBase();
    });
    settingsBtn.addEventListener('click', () => switchView('settings'));
    
    // 知识库管理
    addKnowledgeBtn.addEventListener('click', () => addKnowledgeModal.show());
    saveKnowledgeBtn.addEventListener('click', saveKnowledge);
    
    // 设置表单
    settingsForm.addEventListener('submit', saveSettings);
}

/**
 * 切换视图
 * @param {string} view - 视图名称
 */
function switchView(view) {
    // 更新当前视图
    currentView = view;
    
    // 隐藏所有容器
    chatContainer.classList.add('d-none');
    knowledgeContainer.classList.add('d-none');
    settingsContainer.classList.add('d-none');
    historyContainer.classList.add('d-none');
    
    // 显示选定的容器
    switch (view) {
        case 'chat':
            chatContainer.classList.remove('d-none');
            break;
        case 'knowledge':
            knowledgeContainer.classList.remove('d-none');
            break;
        case 'settings':
            settingsContainer.classList.remove('d-none');
            break;
        case 'history':
            historyContainer.classList.remove('d-none');
            loadChatHistory();
            break;
    }
    
    // 更新导航菜单活动项
    document.querySelectorAll('.sidebar-menu .nav-link').forEach(link => {
        link.classList.remove('active');
    });
    
    // 设置当前活动项
    let activeLink;
    switch (view) {
        case 'chat':
            activeLink = newChatBtn;
            break;
        case 'knowledge':
            activeLink = knowledgeBaseBtn;
            break;
        case 'settings':
            activeLink = settingsBtn;
            break;
        case 'history':
            activeLink = historyBtn;
            break;
    }
    
    if (activeLink) {
        activeLink.classList.add('active');
    }
}

/**
 * 处理聊天表单提交
 * @param {Event} event - 表单提交事件
 */
async function handleChatSubmit(event) {
    event.preventDefault();
    
    const message = userInput.value.trim();
    if (!message) return;
    
    // 清空输入框
    userInput.value = '';
    
    // 添加用户消息到聊天界面
    addMessage(message, 'user');
    
    // 显示机器人正在输入的指示器
    showTypingIndicator();
    
    try {
        // 发送消息到后端
        const response = await sendMessage(message);
        
        // 移除输入指示器
        removeTypingIndicator();
        
        // 添加机器人回复到聊天界面
        if (response && response.status === 'success') {
            addMessage(response.response, 'bot');
        } else {
            addMessage('抱歉,处理您的消息时出现了问题。请稍后再试。', 'bot');
        }
    } catch (error) {
        console.error('发送消息失败:', error);
        
        // 移除输入指示器
        removeTypingIndicator();
        
        // 显示错误消息
        addMessage('抱歉,连接服务器时出现了问题。请检查您的网络连接或稍后再试。', 'bot');
    }
}

/**
 * 发送消息到后端
 * @param {string} message - 用户消息
 * @returns {Promise<Object>} - 响应对象
 */
async function sendMessage(message) {
    const response = await fetch(`${API_BASE_URL}/chat`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            user_id: userId,
            message: message
        })
    });
    
    return await response.json();
}

/**
 * 添加消息到聊天界面
 * @param {string} text - 消息文本
 * @param {string} sender - 发送者('user' 或 'bot')
 */
function addMessage(text, sender) {
    const messageDiv = document.createElement('div');
    messageDiv.className = `message ${sender}-message fade-in`;
    
    const avatarDiv = document.createElement('div');
    avatarDiv.className = 'message-avatar';
    
    const icon = document.createElement('i');
    icon.className = sender === 'user' ? 'bi bi-person' : 'bi bi-robot';
    avatarDiv.appendChild(icon);
    
    const contentDiv = document.createElement('div');
    contentDiv.className = 'message-content';
    
    const paragraph = document.createElement('p');
    paragraph.textContent = text;
    contentDiv.appendChild(paragraph);
    
    messageDiv.appendChild(avatarDiv);
    messageDiv.appendChild(contentDiv);
    
    chatMessages.appendChild(messageDiv);
    
    // 滚动到底部
    chatMessages.scrollTop = chatMessages.scrollHeight;
}

/**
 * 显示机器人正在输入的指示器
 */
function showTypingIndicator() {
    const indicatorDiv = document.createElement('div');
    indicatorDiv.className = 'message bot-message fade-in';
    indicatorDiv.id = 'typing-indicator';
    
    const avatarDiv = document.createElement('div');
    avatarDiv.className = 'message-avatar';
    
    const icon = document.createElement('i');
    icon.className = 'bi bi-robot';
    avatarDiv.appendChild(icon);
    
    const contentDiv = document.createElement('div');
    contentDiv.className = 'message-content';
    
    const typingDiv = document.createElement('div');
    typingDiv.className = 'typing-indicator';
    
    for (let i = 0; i < 3; i++) {
        const dot = document.createElement('span');
        typingDiv.appendChild(dot);
    }
    
    contentDiv.appendChild(typingDiv);
    
    indicatorDiv.appendChild(avatarDiv);
    indicatorDiv.appendChild(contentDiv);
    
    chatMessages.appendChild(indicatorDiv);
    
    // 滚动到底部
    chatMessages.scrollTop = chatMessages.scrollHeight;
}

/**
 * 移除机器人正在输入的指示器
 */
function removeTypingIndicator() {
    const indicator = document.getElementById('typing-indicator');
    if (indicator) {
        indicator.remove();
    }
}

/**
 * 清空聊天记录
 */
async function clearChat() {
    if (confirm('确定要清空当前对话吗?')) {
        try {
            // 调用后端API清除历史
            const response = await fetch(`${API_BASE_URL}/history/${userId}`, {
                method: 'DELETE'
            });
            
            const result = await response.json();
            
            if (result.status === 'success') {
                // 清空聊天界面
                chatMessages.innerHTML = '';
                
                // 添加欢迎消息
                addMessage('您好!我是智能助手,有什么可以帮您的吗?', 'bot');
            } else {
                alert('清空对话失败');
            }
        } catch (error) {
            console.error('清空对话失败:', error);
            alert('清空对话失败,请检查网络连接');
        }
    }
}

/**
 * 加载聊天历史
 */
async function loadChatHistory() {
    if (currentView !== 'history') return;
    
    const historyList = document.getElementById('history-list');
    historyList.innerHTML = '<div class="text-center"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div>';
    
    try {
        const response = await fetch(`${API_BASE_URL}/history/${userId}`);
        const result = await response.json();
        
        if (result.status === 'success') {
            historyList.innerHTML = '';
            
            if (result.history && result.history.length > 0) {
                // 按日期分组
                const groupedHistory = groupHistoryByDate(result.history);
                
                // 渲染分组历史
                for (const [date, messages] of Object.entries(groupedHistory)) {
                    const dateHeader = document.createElement('h6');
                    dateHeader.className = 'mt-3 mb-2 text-muted';
                    dateHeader.textContent = date;
                    historyList.appendChild(dateHeader);
                    
                    for (const message of messages) {
                        const item = document.createElement('div');
                        item.className = 'list-group-item list-group-item-action';
                        
                        const header = document.createElement('div');
                        header.className = 'd-flex justify-content-between align-items-center';
                        
                        const time = new Date(message.timestamp).toLocaleTimeString();
                        
                        header.innerHTML = `
                            <small class="text-muted">${time}</small>
                            <span class="badge bg-primary">${message.intent}</span>
                        `;
                        
                        const userMsg = document.createElement('p');
                        userMsg.className = 'mb-1 mt-2';
                        userMsg.innerHTML = `<strong>用户:</strong> ${message.message}`;
                        
                        const botMsg = document.createElement('p');
                        botMsg.className = 'mb-0 text-muted';
                        botMsg.innerHTML = `<strong>机器人:</strong> ${message.response}`;
                        
                        item.appendChild(header);
                        item.appendChild(userMsg);
                        item.appendChild(botMsg);
                        
                        historyList.appendChild(item);
                    }
                }
            } else {
                historyList.innerHTML = '<div class="text-center p-3 text-muted">暂无对话历史</div>';
            }
        } else {
            historyList.innerHTML = '<div class="text-center p-3 text-danger">加载历史失败</div>';
        }
    } catch (error) {
        console.error('加载历史失败:', error);
        historyList.innerHTML = '<div class="text-center p-3 text-danger">加载历史失败,请检查网络连接</div>';
    }
}

/**
 * 按日期分组历史记录
 * @param {Array} history - 历史记录数组
 * @returns {Object} - 按日期分组的历史记录
 */
function groupHistoryByDate(history) {
    const grouped = {};
    
    for (const message of history) {
        const date = new Date(message.timestamp).toLocaleDateString();
        
        if (!grouped[date]) {
            grouped[date] = [];
        }
        
        grouped[date].push(message);
    }
    
    return grouped;
}

/**
 * 加载知识库
 */
async function loadKnowledgeBase() {
    const knowledgeList = document.getElementById('knowledge-list');
    knowledgeList.innerHTML = '<tr><td colspan="5" class="text-center"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></td></tr>';
    
    try {
        const response = await fetch(`${API_BASE_URL}/knowledge`);
        const result = await response.json();
        
        if (result.status === 'success') {
            knowledgeList.innerHTML = '';
            
            if (result.documents && result.documents.length > 0) {
                for (const doc of result.documents) {
                    const row = document.createElement('tr');
                    
                    // 截断长文本
                    const truncateText = (text, maxLength = 50) => {
                        return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
                    };
                    
                    row.innerHTML = `
                        <td>${doc.id}</td>
                        <td>${truncateText(doc.question)}</td>
                        <td>${truncateText(doc.answer)}</td>
                        <td>${new Date(doc.created_at).toLocaleString()}</td>
                        <td>
                            <button class="btn btn-sm btn-outline-primary view-knowledge" data-id="${doc.id}">
                                <i class="bi bi-eye"></i>
                            </button>
                            <button class="btn btn-sm btn-outline-danger delete-knowledge" data-id="${doc.id}">
                                <i class="bi bi-trash"></i>
                            </button>
                        </td>
                    `;
                    
                    knowledgeList.appendChild(row);
                }
                
                // 添加查看和删除事件监听器
                document.querySelectorAll('.view-knowledge').forEach(btn => {
                    btn.addEventListener('click', () => viewKnowledge(btn.dataset.id));
                });
                
                document.querySelectorAll('.delete-knowledge').forEach(btn => {
                    btn.addEventListener('click', () => deleteKnowledge(btn.dataset.id));
                });
            } else {
                knowledgeList.innerHTML = '<tr><td colspan="5" class="text-center">知识库为空</td></tr>';
            }
        } else {
            knowledgeList.innerHTML = '<tr><td colspan="5" class="text-center text-danger">加载知识库失败</td></tr>';
        }
    } catch (error) {
        console.error('加载知识库失败:', error);
        knowledgeList.innerHTML = '<tr><td colspan="5" class="text-center text-danger">加载知识库失败,请检查网络连接</td></tr>';
    }
}

/**
 * 保存知识条目
 */
async function saveKnowledge() {
    const question = document.getElementById('knowledge-question').value.trim();
    const answer = document.getElementById('knowledge-answer').value.trim();
    
    if (!question || !answer) {
        alert('问题和答案不能为空');
        return;
    }
    
    try {
        const response = await fetch(`${API_BASE_URL}/knowledge`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                question: question,
                answer: answer
            })
        });
        
        const result = await response.json();
        
        if (result.status === 'success') {
            // 关闭模态框
            addKnowledgeModal.hide();
            
            // 清空表单
            document.getElementById('knowledge-question').value = '';
            document.getElementById('knowledge-answer').value = '';
            
            // 重新加载知识库
            loadKnowledgeBase();
            
            // 显示成功消息
            alert('知识条目已添加');
        } else {
            alert('添加知识条目失败');
        }
    } catch (error) {
        console.error('添加知识条目失败:', error);
        alert('添加知识条目失败,请检查网络连接');
    }
}

/**
 * 查看知识条目
 * @param {string} id - 知识条目ID
 */
function viewKnowledge(id) {
    // TODO: 实现查看知识条目详情
    alert(`查看知识条目 ${id} 的详情`);
}

/**
 * 删除知识条目
 * @param {string} id - 知识条目ID
 */
function deleteKnowledge(id) {
    // TODO: 实现删除知识条目
    if (confirm(`确定要删除知识条目 ${id} 吗?`)) {
        alert(`删除知识条目 ${id}`);
    }
}

/**
 * 保存用户设置
 * @param {Event} event - 表单提交事件
 */
function saveSettings(event) {
    event.preventDefault();
    
    // 获取设置值
    const newUserId = document.getElementById('user-id').value.trim();
    const useMongoDb = document.getElementById('use-mongodb').checked;
    const mongoDbUri = document.getElementById('mongodb-uri').value.trim();
    
    // 验证用户ID
    if (!newUserId) {
        alert('用户ID不能为空');
        return;
    }
    
    // 保存到localStorage
    localStorage.setItem('userId', newUserId);
    localStorage.setItem('useMongoDb', useMongoDb);
    localStorage.setItem('mongoDbUri', mongoDbUri);
    
    // 更新全局变量
    userId = newUserId;
    
    // 显示成功消息
    alert('设置已保存');
}

/**
 * 处理API错误
 * @param {Response} response - Fetch API响应对象
 */
async function handleApiError(response) {
    if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
    }
    return response;
}

static\js\knowledge_base.js

/**
 * 知识库管理系统前端JavaScript
 */

// 全局变量
let currentPage = 1;
let pageSize = 10;
let totalPages = 1;
let currentKnowledge = null;
let deleteKnowledgeId = null;
let allCategories = [];
let allTags = [];
let categoryChart = null;
let tagChart = null;

// DOM加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
    // 初始化页面
    initPage();
    
    // 加载知识列表
    loadKnowledgeList();
    
    // 加载分类和标签
    loadCategories();
    loadTags();
    
    // 注册事件监听器
    registerEventListeners();
});

/**
 * 初始化页面
 */
function initPage() {
    // 默认显示知识列表页
    showPage('page-knowledge-list');
    
    // 激活导航项
    document.getElementById('nav-knowledge-list').classList.add('active');
}

/**
 * 注册事件监听器
 */
function registerEventListeners() {
    // 导航菜单点击事件
    document.getElementById('nav-knowledge-list').addEventListener('click', function(e) {
        e.preventDefault();
        activateNavItem(this);
        showPage('page-knowledge-list');
        loadKnowledgeList();
    });
    
    document.getElementById('nav-add-knowledge').addEventListener('click', function(e) {
        e.preventDefault();
        activateNavItem(this);
        showPage('page-add-knowledge');
        resetKnowledgeForm();
        document.getElementById('form-title').textContent = '添加知识';
    });
    
    document.getElementById('nav-categories').addEventListener('click', function(e) {
        e.preventDefault();
        activateNavItem(this);
        showPage('page-categories');
        loadCategoryList();
        renderCategoryChart();
    });
    
    document.getElementById('nav-tags').addEventListener('click', function(e) {
        e.preventDefault();
        activateNavItem(this);
        showPage('page-tags');
        renderTagCloud();
        renderTagChart();
    });
    
    document.getElementById('nav-import').addEventListener('click', function(e) {
        e.preventDefault();
        activateNavItem(this);
        showPage('page-import');
    });
    
    document.getElementById('nav-export').addEventListener('click', function(e) {
        e.preventDefault();
        activateNavItem(this);
        showPage('page-export');
        loadExportOptions();
    });
    
    document.getElementById('nav-backup').addEventListener('click', function(e) {
        e.preventDefault();
        activateNavItem(this);
        showPage('page-backup');
        loadBackupList();
    });
    
    // 添加知识按钮点击事件
    document.getElementById('btn-add-knowledge').addEventListener('click', function() {
        showPage('page-add-knowledge');
        resetKnowledgeForm();
        document.getElementById('form-title').textContent = '添加知识';
        activateNavItem(document.getElementById('nav-add-knowledge'));
    });
    
    // 返回列表按钮点击事件
    document.getElementById('btn-back-to-list').addEventListener('click', function() {
        showPage('page-knowledge-list');
        activateNavItem(document.getElementById('nav-knowledge-list'));
    });
    
    // 知识表单提交事件
    document.getElementById('knowledge-form').addEventListener('submit', function(e) {
        e.preventDefault();
        saveKnowledge();
    });
    
    // 取消按钮点击事件
    document.getElementById('btn-cancel').addEventListener('click', function() {
        showPage('page-knowledge-list');
        activateNavItem(document.getElementById('nav-knowledge-list'));
    });
    
    // 搜索按钮点击事件
    document.getElementById('btn-search').addEventListener('click', function() {
        currentPage = 1;
        loadKnowledgeList();
    });
    
    // 搜索框回车事件
    document.getElementById('search-input').addEventListener('keypress', function(e) {
        if (e.key === 'Enter') {
            currentPage = 1;
            loadKnowledgeList();
        }
    });
    
    // 分类筛选器变化事件
    document.getElementById('category-filter').addEventListener('change', function() {
        currentPage = 1;
        loadKnowledgeList();
    });
    
    // 标签筛选器变化事件
    document.getElementById('tag-filter').addEventListener('change', function() {
        currentPage = 1;
        loadKnowledgeList();
    });
    
    // 编辑知识按钮点击事件(在模态框中)
    document.getElementById('btn-edit-knowledge').addEventListener('click', function() {
        const modal = bootstrap.Modal.getInstance(document.getElementById('view-knowledge-modal'));
        modal.hide();
        
        showPage('page-add-knowledge');
        activateNavItem(document.getElementById('nav-add-knowledge'));
        fillKnowledgeForm(currentKnowledge);
        document.getElementById('form-title').textContent = '编辑知识';
    });
    
    // 确认删除按钮点击事件
    document.getElementById('btn-confirm-delete').addEventListener('click', function() {
        deleteKnowledge(deleteKnowledgeId);
    });
    
    // 导出范围选择事件
    document.querySelectorAll('input[name="export-scope"]').forEach(function(radio) {
        radio.addEventListener('change', function() {
            const filterOptions = document.getElementById('export-filter-options');
            if (this.value === 'filtered') {
                filterOptions.style.display = 'block';
            } else {
                filterOptions.style.display = 'none';
            }
        });
    });
    
    // 导出表单提交事件
    document.getElementById('export-form').addEventListener('submit', function(e) {
        e.preventDefault();
        exportKnowledge();
    });
    
    // 导入表单提交事件
    document.getElementById('import-form').addEventListener('submit', function(e) {
        e.preventDefault();
        importKnowledge();
    });
    
    // 创建备份按钮点击事件
    document.getElementById('btn-create-backup').addEventListener('click', function() {
        createBackup();
    });
    
    // 恢复备份表单提交事件
    document.getElementById('restore-form').addEventListener('submit', function(e) {
        e.preventDefault();
        restoreBackup();
    });
}

/**
 * 激活导航项
 * @param {HTMLElement} navItem - 导航项元素
 */
function activateNavItem(navItem) {
    // 移除所有导航项的激活状态
    document.querySelectorAll('.nav-link').forEach(function(item) {
        item.classList.remove('active');
    });
    
    // 激活当前导航项
    navItem.classList.add('active');
}

/**
 * 显示指定页面
 * @param {string} pageId - 页面ID
 */
function showPage(pageId) {
    // 隐藏所有页面
    document.querySelectorAll('.page').forEach(function(page) {
        page.classList.add('d-none');
    });
    
    // 显示指定页面
    document.getElementById(pageId).classList.remove('d-none');
}

/**
 * 加载知识列表
 */
function loadKnowledgeList() {
    // 获取筛选条件
    const query = document.getElementById('search-input').value;
    const category = document.getElementById('category-filter').value;
    const tag = document.getElementById('tag-filter').value;
    
    // 构建API URL
    let url = `/api/knowledge?page=${currentPage}&page_size=${pageSize}`;
    if (query) url += `&query=${encodeURIComponent(query)}`;
    if (category) url += `&category=${encodeURIComponent(category)}`;
    if (tag) url += `&tag=${encodeURIComponent(tag)}`;
    
    // 发送请求
    fetch(url)
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                // 渲染知识列表
                renderKnowledgeList(data.data);
                
                // 更新分页
                if (data.pagination) {
                    totalPages = data.pagination.total_pages;
                    renderPagination(data.pagination);
                }
            } else {
                showAlert('错误', data.message || '加载知识列表失败');
            }
        })
        .catch(error => {
            console.error('加载知识列表失败:', error);
            showAlert('错误', '加载知识列表失败,请检查网络连接');
        });
}

/**
 * 渲染知识列表
 * @param {Array} knowledgeList - 知识列表数据
 */
function renderKnowledgeList(knowledgeList) {
    const tableBody = document.getElementById('knowledge-list');
    tableBody.innerHTML = '';
    
    if (knowledgeList.length === 0) {
        // 没有数据
        const row = document.createElement('tr');
        row.innerHTML = `<td colspan="6" class="text-center">没有找到知识条目</td>`;
        tableBody.appendChild(row);
        return;
    }
    
    // 渲染每一行
    knowledgeList.forEach(knowledge => {
        const row = document.createElement('tr');
        
        // 格式化更新时间
        const updatedAt = new Date(knowledge.updated_at);
        const formattedDate = updatedAt.toLocaleDateString() + ' ' + updatedAt.toLocaleTimeString();
        
        // 获取分类
        const category = knowledge.metadata && knowledge.metadata.category ? knowledge.metadata.category : '-';
        
        // 获取标签
        let tagsHtml = '-';
        if (knowledge.metadata && knowledge.metadata.tags && knowledge.metadata.tags.length > 0) {
            tagsHtml = knowledge.metadata.tags.map(tag => 
                `<span class="badge bg-light text-dark badge-tag">${tag}</span>`
            ).join('');
        }
        
        row.innerHTML = `
            <td>${knowledge.id}</td>
            <td class="question-cell">${knowledge.question}</td>
            <td>${category}</td>
            <td>${tagsHtml}</td>
            <td>${formattedDate}</td>
            <td>
                <button class="btn btn-sm btn-outline-primary btn-view" data-id="${knowledge.id}">
                    <i class="bi bi-eye"></i>
                </button>
                <button class="btn btn-sm btn-outline-secondary btn-edit" data-id="${knowledge.id}">
                    <i class="bi bi-pencil"></i>
                </button>
                <button class="btn btn-sm btn-outline-danger btn-delete" data-id="${knowledge.id}">
                    <i class="bi bi-trash"></i>
                </button>
            </td>
        `;
        
        tableBody.appendChild(row);
    });
    
    // 添加查看按钮点击事件
    document.querySelectorAll('.btn-view').forEach(button => {
        button.addEventListener('click', function() {
            const id = parseInt(this.getAttribute('data-id'));
            viewKnowledge(id);
        });
    });
    
    // 添加编辑按钮点击事件
    document.querySelectorAll('.btn-edit').forEach(button => {
        button.addEventListener('click', function() {
            const id = parseInt(this.getAttribute('data-id'));
            editKnowledge(id);
        });
    });
    
    // 添加删除按钮点击事件
    document.querySelectorAll('.btn-delete').forEach(button => {
        button.addEventListener('click', function() {
            const id = parseInt(this.getAttribute('data-id'));
            confirmDeleteKnowledge(id);
        });
    });
}

/**
 * 渲染分页控件
 * @param {Object} pagination - 分页信息
 */
function renderPagination(pagination) {
    const paginationElement = document.getElementById('pagination');
    paginationElement.innerHTML = '';
    
    // 如果只有一页,不显示分页
    if (pagination.total_pages <= 1) {
        return;
    }
    
    // 上一页按钮
    const prevLi = document.createElement('li');
    prevLi.className = `page-item ${pagination.page <= 1 ? 'disabled' : ''}`;
    prevLi.innerHTML = `<a class="page-link" href="#" data-page="${pagination.page - 1}">上一页</a>`;
    paginationElement.appendChild(prevLi);
    
    // 页码按钮
    let startPage = Math.max(1, pagination.page - 2);
    let endPage = Math.min(pagination.total_pages, startPage + 4);
    
    if (endPage - startPage < 4) {
        startPage = Math.max(1, endPage - 4);
    }
    
    for (let i = startPage; i <= endPage; i++) {
        const li = document.createElement('li');
        li.className = `page-item ${i === pagination.page ? 'active' : ''}`;
        li.innerHTML = `<a class="page-link" href="#" data-page="${i}">${i}</a>`;
        paginationElement.appendChild(li);
    }
    
    // 下一页按钮
    const nextLi = document.createElement('li');
    nextLi.className = `page-item ${pagination.page >= pagination.total_pages ? 'disabled' : ''}`;
    nextLi.innerHTML = `<a class="page-link" href="#" data-page="${pagination.page + 1}">下一页</a>`;
    paginationElement.appendChild(nextLi);
    
    // 添加页码点击事件
    document.querySelectorAll('.page-link').forEach(link => {
        link.addEventListener('click', function(e) {
            e.preventDefault();
            const page = parseInt(this.getAttribute('data-page'));
            if (page >= 1 && page <= pagination.total_pages) {
                currentPage = page;
                loadKnowledgeList();
            }
        });
    });
}

/**
 * 加载分类列表
 */
function loadCategories() {
    fetch('/api/knowledge/categories')
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                allCategories = data.data;
                
                // 更新分类筛选器
                const categoryFilter = document.getElementById('category-filter');
                categoryFilter.innerHTML = '<option value="">所有分类</option>';
                
                allCategories.forEach(category => {
                    const option = document.createElement('option');
                    option.value = category;
                    option.textContent = category;
                    categoryFilter.appendChild(option);
                });
                
                // 更新分类数据列表
                const categoryList = document.getElementById('category-list');
                categoryList.innerHTML = '';
                
                allCategories.forEach(category => {
                    const option = document.createElement('option');
                    option.value = category;
                    categoryList.appendChild(option);
                });
                
                // 更新导出页面的分类选择器
                const exportCategory = document.getElementById('export-category');
                exportCategory.innerHTML = '<option value="">所有分类</option>';
                
                allCategories.forEach(category => {
                    const option = document.createElement('option');
                    option.value = category;
                    option.textContent = category;
                    exportCategory.appendChild(option);
                });
            }
        })
        .catch(error => {
            console.error('加载分类失败:', error);
        });
}

/**
 * 加载标签列表
 */
function loadTags() {
    fetch('/api/knowledge/tags')
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                allTags = data.data;
                
                // 更新标签筛选器
                const tagFilter = document.getElementById('tag-filter');
                tagFilter.innerHTML = '<option value="">所有标签</option>';
                
                allTags.forEach(tag => {
                    const option = document.createElement('option');
                    option.value = tag;
                    option.textContent = tag;
                    tagFilter.appendChild(option);
                });
                
                // 更新标签数据列表
                const tagList = document.getElementById('tag-list');
                tagList.innerHTML = '';
                
                allTags.forEach(tag => {
                    const option = document.createElement('option');
                    option.value = tag;
                    tagList.appendChild(option);
                });
                
                // 更新导出页面的标签选择器
                const exportTag = document.getElementById('export-tag');
                exportTag.innerHTML = '<option value="">所有标签</option>';
                
                allTags.forEach(tag => {
                    const option = document.createElement('option');
                    option.value = tag;
                    option.textContent = tag;
                    exportTag.appendChild(option);
                });
            }
        })
        .catch(error => {
            console.error('加载标签失败:', error);
        });
}

/**
 * 查看知识详情
 * @param {number} id - 知识ID
 */
function viewKnowledge(id) {
    fetch(`/api/knowledge?id=${id}`)
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                currentKnowledge = data.data;
                
                // 填充模态框内容
                document.getElementById('view-question').textContent = currentKnowledge.question;
                document.getElementById('view-answer').textContent = currentKnowledge.answer;
                
                // 显示分类
                const category = currentKnowledge.metadata && currentKnowledge.metadata.category 
                    ? currentKnowledge.metadata.category 
                    : '-';
                document.getElementById('view-category').textContent = category;
                
                // 显示标签
                const tagsElement = document.getElementById('view-tags');
                tagsElement.innerHTML = '';
                
                if (currentKnowledge.metadata && currentKnowledge.metadata.tags && currentKnowledge.metadata.tags.length > 0) {
                    currentKnowledge.metadata.tags.forEach(tag => {
                        const badge = document.createElement('span');
                        badge.className = 'badge bg-light text-dark me-1';
                        badge.textContent = tag;
                        tagsElement.appendChild(badge);
                    });
                } else {
                    tagsElement.textContent = '-';
                }
                
                // 显示创建和更新时间
                const createdAt = new Date(currentKnowledge.created_at);
                const updatedAt = new Date(currentKnowledge.updated_at);
                
                document.getElementById('view-created-at').textContent = createdAt.toLocaleString();
                document.getElementById('view-updated-at').textContent = updatedAt.toLocaleString();
                
                // 显示模态框
                const modal = new bootstrap.Modal(document.getElementById('view-knowledge-modal'));
                modal.show();
            } else {
                showAlert('错误', data.message || '获取知识详情失败');
            }
        })
        .catch(error => {
            console.error('获取知识详情失败:', error);
            showAlert('错误', '获取知识详情失败,请检查网络连接');
        });
}

/**
 * 编辑知识
 * @param {number} id - 知识ID
 */
function editKnowledge(id) {
    fetch(`/api/knowledge?id=${id}`)
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                // 切换到编辑页面
                showPage('page-add-knowledge');
                activateNavItem(document.getElementById('nav-add-knowledge'));
                
                // 填充表单
                fillKnowledgeForm(data.data);
                document.getElementById('form-title').textContent = '编辑知识';
            } else {
                showAlert('错误', data.message || '获取知识详情失败');
            }
        })
        .catch(error => {
            console.error('获取知识详情失败:', error);
            showAlert('错误', '获取知识详情失败,请检查网络连接');
        });
}

/**
 * 填充知识表单
 * @param {Object} knowledge - 知识对象
 */
function fillKnowledgeForm(knowledge) {
    document.getElementById('knowledge-id').value = knowledge.id;
    document.getElementById('question').value = knowledge.question;
    document.getElementById('answer').value = knowledge.answer;
    
    // 填充分类
    if (knowledge.metadata && knowledge.metadata.category) {
        document.getElementById('category').value = knowledge.metadata.category;
    } else {
        document.getElementById('category').value = '';
    }
    
    // 填充标签
    if (knowledge.metadata && knowledge.metadata.tags && knowledge.metadata.tags.length > 0) {
        document.getElementById('tags').value = knowledge.metadata.tags.join(', ');
    } else {
        document.getElementById('tags').value = '';
    }
}

/**
 * 重置知识表单
 */
function resetKnowledgeForm() {
    document.getElementById('knowledge-form').reset();
    document.getElementById('knowledge-id').value = '';
}

/**
 * 保存知识
 */
function saveKnowledge() {
    // 获取表单数据
    const id = document.getElementById('knowledge-id').value;
    const question = document.getElementById('question').value;
    const answer = document.getElementById('answer').value;
    const category = document.getElementById('category').value;
    const tagsString = document.getElementById('tags').value;
    
    // 解析标签
    const tags = tagsString.split(',')
        .map(tag => tag.trim())
        .filter(tag => tag.length > 0);
    
    // 构建知识对象
    const knowledge = {
        question: question,
        answer: answer,
        metadata: {
            category: category,
            tags: tags
        }
    };
    
    // 确定是添加还是更新
    const isUpdate = id !== '';
    const url = isUpdate ? `/api/knowledge/${id}` : '/api/knowledge';
    const method = isUpdate ? 'PUT' : 'POST';
    
    // 发送请求
    fetch(url, {
        method: method,
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(knowledge)
    })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                showAlert('成功', isUpdate ? '知识更新成功' : '知识添加成功');
                
                // 返回列表页并刷新
                showPage('page-knowledge-list');
                activateNavItem(document.getElementById('nav-knowledge-list'));
                loadKnowledgeList();
            } else {
                showAlert('错误', data.message || (isUpdate ? '更新知识失败' : '添加知识失败'));
            }
        })
        .catch(error => {
            console.error(isUpdate ? '更新知识失败:' : '添加知识失败:', error);
            showAlert('错误', (isUpdate ? '更新知识失败' : '添加知识失败') + ',请检查网络连接');
        });
}

/**
 * 确认删除知识
 * @param {number} id - 知识ID
 */
function confirmDeleteKnowledge(id) {
    deleteKnowledgeId = id;
    const modal = new bootstrap.Modal(document.getElementById('confirm-delete-modal'));
    modal.show();
}

/**
 * 删除知识
 * @param {number} id - 知识ID
 */
function deleteKnowledge(id) {
    fetch(`/api/knowledge/${id}`, {
        method: 'DELETE'
    })
        .then(response => response.json())
        .then(data => {
            // 关闭确认模态框
            const modal = bootstrap.Modal.getInstance(document.getElementById('confirm-delete-modal'));
            modal.hide();
            
            if (data.success) {
                showAlert('成功', '知识删除成功');
                loadKnowledgeList();
            } else {
                showAlert('错误', data.message || '删除知识失败');
            }
        })
        .catch(error => {
            console.error('删除知识失败:', error);
            showAlert('错误', '删除知识失败,请检查网络连接');
            
            // 关闭确认模态框
            const modal = bootstrap.Modal.getInstance(document.getElementById('confirm-delete-modal'));
            modal.hide();
        });
}

/**
 * 加载分类列表页面
 */
function loadCategoryList() {
    const categoryListElement = document.getElementById('category-list-items');
    categoryListElement.innerHTML = '';
    
    if (allCategories.length === 0) {
        categoryListElement.innerHTML = '<li class="list-group-item">没有分类数据</li>';
        return;
    }
    
    // 为每个分类创建一个列表项
    allCategories.forEach(category => {
        const li = document.createElement('li');
        li.className = 'list-group-item d-flex justify-content-between align-items-center';
        
        // 获取该分类的知识数量
        fetch(`/api/knowledge?category=${encodeURIComponent(category)}&page=1&page_size=1`)
            .then(response => response.json())
            .then(data => {
                const count = data.pagination ? data.pagination.total : 0;
                
                li.innerHTML = `
                    ${category}
                    <span class="badge bg-primary rounded-pill">${count}</span>
                `;
            })
            .catch(error => {
                console.error('获取分类数量失败:', error);
                li.innerHTML = `
                    ${category}
                    <span class="badge bg-secondary rounded-pill">?</span>
                `;
            });
        
        categoryListElement.appendChild(li);
    });
}

/**
 * 渲染分类图表
 */
function renderCategoryChart() {
    const ctx = document.getElementById('category-chart').getContext('2d');
    
    // 如果已存在图表,先销毁
    if (categoryChart) {
        categoryChart.destroy();
    }
    
    // 准备数据
    const categories = [];
    const counts = [];
    
    // 获取每个分类的知识数量
    const promises = allCategories.map(category => {
        return fetch(`/api/knowledge?category=${encodeURIComponent(category)}&page=1&page_size=1`)
            .then(response => response.json())
            .then(data => {
                const count = data.pagination ? data.pagination.total : 0;
                categories.push(category);
                counts.push(count);
            });
    });
    
    Promise.all(promises)
        .then(() => {
            // 创建图表
            categoryChart = new Chart(ctx, {
                type: 'bar',
                data: {
                    labels: categories,
                    datasets: [{
                        label: '知识数量',
                        data: counts,
                        backgroundColor: 'rgba(13, 110, 253, 0.7)',
                        borderColor: 'rgba(13, 110, 253, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    plugins: {
                        legend: {
                            position: 'top',
                        },
                        title: {
                            display: true,
                            text: '各分类知识数量统计'
                        }
                    },
                    scales: {
                        y: {
                            beginAtZero: true,
                            ticks: {
                                precision: 0
                            }
                        }
                    }
                }
            });
        })
        .catch(error => {
            console.error('渲染分类图表失败:', error);
        });
}

/**
 * 渲染标签云
 */
function renderTagCloud() {
    const tagCloudElement = document.getElementById('tag-cloud');
    tagCloudElement.innerHTML = '';
    
    if (allTags.length === 0) {
        tagCloudElement.innerHTML = '<p class="text-center">没有标签数据</p>';
        return;
    }
    
    // 获取每个标签的知识数量
    const promises = allTags.map(tag => {
        return fetch(`/api/knowledge?tag=${encodeURIComponent(tag)}&page=1&page_size=1`)
            .then(response => response.json())
            .then(data => {
                const count = data.pagination ? data.pagination.total : 0;
                
                // 根据数量确定字体大小
                let fontSize = 14;
                if (count > 10) fontSize = 24;
                else if (count > 5) fontSize = 20;
                else if (count > 2) fontSize = 16;
                
                // 创建标签元素
                const tagElement = document.createElement('div');
                tagElement.className = 'tag-item';
                tagElement.textContent = tag;
                tagElement.style.fontSize = `${fontSize}px`;
                
                // 点击标签时筛选知识列表
                tagElement.addEventListener('click', function() {
                    document.getElementById('tag-filter').value = tag;
                    showPage('page-knowledge-list');
                    activateNavItem(document.getElementById('nav-knowledge-list'));
                    currentPage = 1;
                    loadKnowledgeList();
                });
                
                tagCloudElement.appendChild(tagElement);
            });
    });
    
    Promise.all(promises)
        .catch(error => {
            console.error('渲染标签云失败:', error);
            tagCloudElement.innerHTML = '<p class="text-center text-danger">加载标签数据失败</p>';
        });
}

/**
 * 渲染标签图表
 */
function renderTagChart() {
    const ctx = document.getElementById('tag-chart').getContext('2d');
    
    // 如果已存在图表,先销毁
    if (tagChart) {
        tagChart.destroy();
    }
    
    // 准备数据
    const tags = [];
    const counts = [];
    
    // 获取每个标签的知识数量
    const promises = allTags.map(tag => {
        return fetch(`/api/knowledge?tag=${encodeURIComponent(tag)}&page=1&page_size=1`)
            .then(response => response.json())
            .then(data => {
                const count = data.pagination ? data.pagination.total : 0;
                tags.push(tag);
                counts.push(count);
            });
    });
    
    Promise.all(promises)
        .then(() => {
            // 创建图表
            tagChart = new Chart(ctx, {
                type: 'pie',
                data: {
                    labels: tags,
                    datasets: [{
                        data: counts,
                        backgroundColor: [
                            'rgba(255, 99, 132, 0.7)',
                            'rgba(54, 162, 235, 0.7)',
                            'rgba(255, 206, 86, 0.7)',
                            'rgba(75, 192, 192, 0.7)',
                            'rgba(153, 102, 255, 0.7)',
                            'rgba(255, 159, 64, 0.7)',
                            'rgba(199, 199, 199, 0.7)',
                            'rgba(83, 102, 255, 0.7)',
                            'rgba(40, 159, 64, 0.7)',
                            'rgba(210, 199, 199, 0.7)'
                        ],
                        borderColor: [
                            'rgba(255, 99, 132, 1)',
                            'rgba(54, 162, 235, 1)',
                            'rgba(255, 206, 86, 1)',
                            'rgba(75, 192, 192, 1)',
                            'rgba(153, 102, 255, 1)',
                            'rgba(255, 159, 64, 1)',
                            'rgba(199, 199, 199, 1)',
                            'rgba(83, 102, 255, 1)',
                            'rgba(40, 159, 64, 1)',
                            'rgba(210, 199, 199, 1)'
                        ],
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    plugins: {
                        legend: {
                            position: 'right',
                        },
                        title: {
                            display: true,
                            text: '各标签知识数量统计'
                        }
                    }
                }
            });
        })
        .catch(error => {
            console.error('渲染标签图表失败:', error);
        });
}

/**
 * 导入知识
 */
function importKnowledge() {
    const fileInput = document.getElementById('import-file');
    
    if (!fileInput.files || fileInput.files.length === 0) {
        showAlert('错误', '请选择要导入的文件');
        return;
    }
    
    const file = fileInput.files[0];
    
    // 检查文件类型
    if (!file.name.endsWith('.json') && !file.name.endsWith('.txt') && !file.name.endsWith('.csv')) {
        showAlert('错误', '不支持的文件类型,请选择 JSON、TXT 或 CSV 文件');
        return;
    }
    
    // 创建表单数据
    const formData = new FormData();
    formData.append('file', file);
    
    // 发送请求
    fetch('/api/knowledge/import', {
        method: 'POST',
        body: formData
    })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                showAlert('成功', data.message || '知识导入成功');
                fileInput.value = '';
            } else {
                showAlert('错误', data.message || '知识导入失败');
            }
        })
        .catch(error => {
            console.error('导入知识失败:', error);
            showAlert('错误', '导入知识失败,请检查网络连接');
        });
}

/**
 * 加载导出选项
 */
function loadExportOptions() {
    // 已在加载分类和标签时填充了选择器
}

/**
 * 导出知识
 */
function exportKnowledge() {
    // 获取导出范围
    const scope = document.querySelector('input[name="export-scope"]:checked').value;
    
    // 构建URL
    let url = '/api/knowledge/export';
    
    if (scope === 'filtered') {
        const query = document.getElementById('export-query').value;
        const category = document.getElementById('export-category').value;
        const tag = document.getElementById('export-tag').value;
        
        if (query) url += `?query=${encodeURIComponent(query)}`;
        if (category) url += `${url.includes('?') ? '&' : '?'}category=${encodeURIComponent(category)}`;
        if (tag) url += `${url.includes('?') ? '&' : '?'}tag=${encodeURIComponent(tag)}`;
    }
    
    // 触发下载
    window.location.href = url;
}

/**
 * 加载备份列表
 */
function loadBackupList() {
    // 这个功能需要后端提供API支持
    // 由于简化版本中没有实现备份列表API,这里仅显示一个提示
    document.getElementById('backup-list').innerHTML = `
        <tr>
            <td colspan="3" class="text-center">请使用创建备份功能生成新的备份</td>
        </tr>
    `;
}

/**
 * 创建备份
 */
function createBackup() {
    fetch('/api/knowledge/backup', {
        method: 'POST'
    })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                showAlert('成功', `知识库备份成功,备份文件:${data.backup_path}`);
            } else {
                showAlert('错误', data.message || '创建备份失败');
            }
        })
        .catch(error => {
            console.error('创建备份失败:', error);
            showAlert('错误', '创建备份失败,请检查网络连接');
        });
}

/**
 * 恢复备份
 */
function restoreBackup() {
    const fileInput = document.getElementById('backup-file');
    
    if (!fileInput.files || fileInput.files.length === 0) {
        showAlert('错误', '请选择备份文件');
        return;
    }
    
    const file = fileInput.files[0];
    
    // 检查文件类型
    if (!file.name.endsWith('.json')) {
        showAlert('错误', '不支持的文件类型,请选择 JSON 备份文件');
        return;
    }
    
    // 读取文件内容
    const reader = new FileReader();
    reader.onload = function(e) {
        try {
            // 解析JSON
            const data = JSON.parse(e.target.result);
            
            // 确认是否清除现有数据
            const clearExisting = document.getElementById('clear-existing').checked;
            
            // 发送恢复请求
            fetch('/api/knowledge/restore', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    backup_data: data,
                    clear_existing: clearExisting
                })
            })
                .then(response => response.json())
                .then(result => {
                    if (result.success) {
                        showAlert('成功', result.message || '知识库恢复成功');
                        fileInput.value = '';
                    } else {
                        showAlert('错误', result.message || '知识库恢复失败');
                    }
                })
                .catch(error => {
                    console.error('恢复备份失败:', error);
                    showAlert('错误', '恢复备份失败,请检查网络连接');
                });
        } catch (error) {
            console.error('解析备份文件失败:', error);
            showAlert('错误', '无效的备份文件格式');
        }
    };
    
    reader.readAsText(file);
}

/**
 * 显示提示框
 * @param {string} title - 标题
 * @param {string} message - 消息内容
 */
function showAlert(title, message) {
    const alertTitle = document.getElementById('alert-title');
    const alertMessage = document.getElementById('alert-message');
    
    alertTitle.textContent = title;
    alertMessage.textContent = message;
    
    const modal = new bootstrap.Modal(document.getElementById('alert-modal'));
    modal.show();
}

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值