开源地址:

liuxingyvhuo/dianshan-agent: 用于查询电商信息的agent系统https://github.com/liuxingyvhuo/dianshan-agent

一、项目介绍

本系统采用lm studio本地部署qwen3-14b模型,采用langchain和flask做agent系统开发后端接口,利用bge-large-zh-v1.5中文语义理解模型做知识向量库的语义检索和SQLite做数据库,html作为用户交互的前端

二、环境介绍

langchain-community == 0.3.27
langchain-huggingface == 0.3.1
langchain-core == 0.3.72
langchain-openai == 0.3.28
langchain == 0.3.27
Flask == 3.1.0
flask-cors == 6.0.1

三、源码目录介绍和注意事项

sqlite.py用于建立SQlite数据库和知识向量库的脚本,会生成product_vector_store知识向量库的目录和ecommerce_orders.db订单数据库表

bge-large-zh-v1.5目录存放的是中文语义理解模型

ecommerce_agent目录下存放有agents目录、执行文件app.py、config.py配置文件、qian.html前端网页

agents目录下存放三个agent,分别是access_agent.py,order_agent.py,product_agent.py

access_agent.py是接入agent用于理解用户问题和协调,order_agent.py和product_agent.py协调工作

order_agent.py是订单agent用于查询SQlite数据库的订单信息

product_agent.py是商品agent用于查询知识向量库的商品信息

这里的order_agent.py和product_agent.py不是真正意义上的agent而是封装成的工具,只有access_agent.py是真正接入大模型的agent

注意!!! 在config.py要配置相对的bge-large-zh-v1.5目录地址和product_vector_store知识向量库的目录地址 在order_agent.py要配置好对应的数据库:

def query_order_with_product(order_id: str):
                """查询订单信息(含商品ID)"""
                db_path = "ecommerce_orders.db"#数据库地址
                if not os.path.exists(db_path):
                     return f"错误:未找到订单数据库文件({db_path})"

三、实现步骤

1、下载lm studio本地部署大模型

首先去lm studio官网下载lm studio的软件:LM Studio - Download and run LLMs on your computerhttps://lmstudio.ai/

下载好后调合适的设置

点击右下角的齿轮设置语言成中文,选择开发者模式,如果有独立显卡可以在Runtime选择CUDA llama.cpp(会加快大模型推理速度)

设置好后就可以进行下载模型到本地部署了,我这边下的是qwen3-14b模型,点击放大镜的图标搜索这个模型点击下载即可

下载好后点击开发者页面,选择对应的模型即可部署

部署成功如下,注意这里的qwen/qwen3-14b模型名称,和http://127.0.0.1:1234

2、配置对应参数

配置config.py文件,修改BASE_URL为lm stuido上的http://127.0.0.1:1234加上/v1,如:http://127.0.0.1:1234/v1,修改EMBEDDING_MODEL_PATH和PRODUCT_VECTOR_PATH的地址

OPENAI_API_KEY = "EMPTY"
BASE_URL = "http://127.0.0.1:1234/v1"
EMBEDDING_MODEL_PATH = "bge-large-zh-v1.5"#模型地址
PRODUCT_VECTOR_PATH = "product_vector_store"#知识向量库地址

# Flask配置
FLASK_HOST = "0.0.0.0"
FLASK_PORT = 5000

FLASK_DEBUG = False

修改app.py文件的model_name,改为lm stuido上的qwen/qwen3-14b模型名称

llm_config = {
    "api_key": OPENAI_API_KEY,
    "base_url": BASE_URL,
    "model_name": "qwen/qwen3-14b",#修改成你需要的大模型名称,在lm studio有
    "temperature": 0.0,#这个是设定大模型的生成的随机性
    "max_tokens": 4096#这个是设定大模型的最大tokens
}

3、启动后端服务

运行app.py,获取本地的ip地址和端口,如:http://192.168.2.35:5000

在修改qian.html的API_BASE_URL改为刚刚获取的本地的ip地址和端口

再用浏览器打开这个网页

即可询问相应的电商问题

四、代码介绍

1、建立SQLite数据库和知识向量库(sqlite.py)

import sqlite3
import os
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.documents import Document


# ======================
# 1. 手动创建商品知识库(15个商品)
# ======================
def create_product_knowledge_base():
    """创建商品知识库并保存到本地"""
    # 15个商品信息
    products = [
        {
            "id": "001",
            "name": "纯棉T恤",
            "description": "100%纯棉材质,透气舒适,适合夏季穿着",
            "specifications": "S/M/L/XL",
            "price": "99元",
            "activity": "满200减30,可叠加使用"
        },
        {
            "id": "002",
            "name": "牛仔裤",
            "description": "修身版型,弹力面料,经典款式",
            "specifications": "28/29/30/31/32(腰围)",
            "price": "159元",
            "activity": "第二件半价"
        },
        {
            "id": "003",
            "name": "运动鞋",
            "description": "轻便透气,缓震鞋底,适合跑步健身",
            "specifications": "39/40/41/42/43/44",
            "price": "299元",
            "activity": "会员专享8折"
        },
        {
            "id": "004",
            "name": "连衣裙",
            "description": "雪纺材质,碎花图案,优雅大方",
            "specifications": "S/M/L",
            "price": "179元",
            "activity": "满300减50"
        },
        {
            "id": "005",
            "name": "夹克外套",
            "description": "防风防水面料,春秋季适用",
            "specifications": "M/L/XL/XXL",
            "price": "259元",
            "activity": "新品上市,暂无活动"
        },
        {
            "id": "006",
            "name": "羊毛衫",
            "description": "含羊毛成分,保暖舒适",
            "specifications": "S/M/L/XL",
            "price": "199元",
            "activity": "满2件减100"
        },
        {
            "id": "007",
            "name": "休闲裤",
            "description": "棉质混纺,宽松版型,日常穿着舒适",
            "specifications": "M/L/XL",
            "price": "129元",
            "activity": "满150减20"
        },
        {
            "id": "008",
            "name": "卫衣",
            "description": "加绒加厚,连帽设计,时尚休闲",
            "specifications": "S/M/L/XL",
            "price": "149元",
            "activity": "限时折扣,直降30元"
        },
        {
            "id": "009",
            "name": "衬衫",
            "description": "免烫处理,商务休闲两用",
            "specifications": "38/39/40/41/42",
            "price": "169元",
            "activity": "满300减60"
        },
        {
            "id": "010",
            "name": "羽绒服",
            "description": "90%白鸭绒填充,轻便保暖",
            "specifications": "M/L/XL/XXL",
            "price": "499元",
            "activity": "预售优惠,定金50抵100"
        },
        {
            "id": "011",
            "name": "帆布鞋",
            "description": "经典款式,舒适百搭,适合日常穿着",
            "specifications": "35/36/37/38/39/40",
            "price": "79元",
            "activity": "买一送一"
        },
        {
            "id": "012",
            "name": "背包",
            "description": "大容量设计,防水面料,适合通勤旅行",
            "specifications": "均码(黑色/灰色/蓝色)",
            "price": "199元",
            "activity": "满200减40"
        },
        {
            "id": "013",
            "name": "帽子",
            "description": "棉质材质,防晒透气,时尚简约",
            "specifications": "均码(可调节)",
            "price": "59元",
            "activity": "3件起9折"
        },
        {
            "id": "014",
            "name": "围巾",
            "description": "羊毛混纺,柔软保暖,多种颜色可选",
            "specifications": "均码(红色/蓝色/灰色/黑色)",
            "price": "89元",
            "activity": "满100减20"
        },
        {
            "id": "015",
            "name": "手套",
            "description": "加绒加厚,触屏设计,冬季必备",
            "specifications": "M/L(黑色/棕色)",
            "price": "69元",
            "activity": "买二送一"
        }
    ]

    # 将商品信息转换为文档格式
    documents = []
    for product in products:
        content = f"商品ID:{product['id']}\n"
        content += f"名称:{product['name']}\n"
        content += f"描述:{product['description']}\n"
        content += f"规格:{product['specifications']}\n"
        content += f"价格:{product['price']}\n"
        content += f"活动:{product['activity']}"

        documents.append(Document(
            page_content=content,
            metadata={"source": f"product_{product['id']}.txt"}
        ))

    # 初始化嵌入模型(使用本地模型路径,避免下载问题)
    local_model_path = "bge-large-zh-v1.5"  # 替换为你的本地模型路径
    embeddings = HuggingFaceEmbeddings(
        model_name=local_model_path,
        model_kwargs={'device': 'cpu'},
        encode_kwargs={'normalize_embeddings': True}
    )

    # 创建并保存向量库
    vector_store = FAISS.from_documents(documents, embeddings)
    if not os.path.exists("product_vector_store"):
        vector_store.save_local("product_vector_store")
        print("商品知识库创建完成,已保存到 product_vector_store 目录")
    else:
        print("商品知识库已存在,无需重复创建")

    return vector_store


# ======================
# 2. 初始化SQLite订单表(10条测试数据)
# ======================
def init_order_database():
    """初始化订单数据库并插入测试数据"""
    # 连接数据库(如果不存在则创建)
    conn = sqlite3.connect('ecommerce_orders.db')
    cursor = conn.cursor()

    # 创建订单表(确保字段完整且与插入数据匹配)
    cursor.execute('''
                   CREATE TABLE IF NOT EXISTS orders
                   (
                       order_id
                       TEXT
                       PRIMARY
                       KEY,
                       user_id
                       TEXT,
                       product_ids
                       TEXT,
                       status
                       TEXT,
                       total_amount
                       REAL,
                       create_time
                       TEXT,
                       pay_time
                       TEXT,
                       ship_time
                       TEXT,
                       receive_time
                       TEXT,
                       logistics_info
                       TEXT
                   )
                   ''')

    # 10条测试订单数据
    test_orders = [
        ("12345", "user001", "001,003", "已签收", 398.0, "2023-10-01 09:30:00", "2023-10-01 10:15:00",
         "2023-10-02 14:20:00", "2023-10-04 16:45:00", "圆通快递: YT1234567890"),
        ("12346", "user002", "002", "已发货", 159.0, "2023-10-02 11:20:00", "2023-10-02 11:30:00",
         "2023-10-03 08:10:00", None, "中通快递: ZT0987654321"),
        ("12347", "user003", "004,006", "已付款", 378.0, "2023-10-02 15:40:00", "2023-10-02 16:05:00", None, None,
         None),
        ("12348", "user004", "005", "待付款", 259.0, "2023-10-03 09:10:00", None, None, None, None),
        ("12349", "user005", "007,008,009", "已签收", 447.0, "2023-10-03 14:30:00", "2023-10-03 15:00:00",
         "2023-10-04 09:20:00", "2023-10-06 11:30:00", "顺丰速运: SF1122334455"),
        ("12350", "user006", "010", "已取消", 499.0, "2023-10-04 10:20:00", None, None, None, None),
        ("12351", "user007", "001,008", "已发货", 248.0, "2023-10-04 16:50:00", "2023-10-04 17:10:00",
         "2023-10-05 10:30:00", None, "韵达快递: YD5566778899"),
        ("12352", "user008", "003,005", "已付款", 558.0, "2023-10-05 08:40:00", "2023-10-05 09:05:00", None, None,
         None),
        ("12353", "user009", "006,007", "已签收", 328.0, "2023-10-05 13:20:00", "2023-10-05 14:00:00",
         "2023-10-06 09:15:00", "2023-10-08 15:20:00", "圆通快递: YT9876543210"),
        ("12354", "user010", "002,009", "待付款", 328.0, "2023-10-06 11:10:00", None, None, None, None)
    ]

    # 插入测试数据
    cursor.executemany('''
                       INSERT
                       OR IGNORE INTO orders 
    (order_id, user_id, product_ids, status, total_amount, create_time, pay_time, ship_time, receive_time, logistics_info)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                       ''', test_orders)

    conn.commit()
    conn.close()
    print("订单数据库初始化完成,已创建 ecommerce_orders.db 并插入10条测试数据")


# 执行初始化
if __name__ == "__main__":
    create_product_knowledge_base()
    init_order_database()

2、接入agent、订单agent、商品agent

接入agent是真正意义上接入大模型,用于理解用户问题进行决策,协调订单agent、商品agent共同工作。

from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.memory import ConversationBufferWindowMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder


class AccessAgent:
    def __init__(self, llm_config, embedding_model_path, product_vector_path):
        # 初始化大模型
        self.llm = self._init_llm(llm_config)

        # 初始化子Agent
        from .order_agent import OrderAgent
        from .product_agent import ProductAgent

        self.order_agent = OrderAgent()
        self.product_agent = ProductAgent(
            embedding_model_path=embedding_model_path,
            product_vector_path=product_vector_path
        )

        # 初始化工具和执行器
        self.tools = self._init_tools()
        self.memory = self._init_memory()
        self.prompt = self._init_prompt()
        self.executor = self._init_executor()

    def _init_llm(self, config):
        """初始化大语言模型"""
        return ChatOpenAI(
            openai_api_key=config["api_key"],
            base_url=config["base_url"],
            model_name=config["model_name"],
            temperature=config["temperature"],
            max_tokens=config["max_tokens"]
        )

    def _init_tools(self):
        """初始化工具集合"""
        return [
            self.order_agent.get_tool()
        ] + self.product_agent.get_tools()

    def _init_memory(self):
        """初始化对话记忆"""
        return ConversationBufferWindowMemory(
            memory_key="chat_history",
            return_messages=True,
            output_key="output",
            k=10  # 保留最近10轮对话
        )

    def _init_prompt(self):
        """初始化提示词模板"""
        return ChatPromptTemplate.from_messages([
            ("system", """
            你是智能协调Agent,负责处理用户关于订单和商品的所有问题,流程如下:

            【核心能力】
            1. 理解用户问题:判断是订单问题、商品问题,还是复合问题(需同时查询订单和商品)。
            2. 工具调用规则:
               - 订单问题(含订单ID、物流、金额、签收时间等)→ 调用query_order工具,参数为order_id。
               - 商品问题:
                 * 已知商品ID,查询详情 → 调用query_product工具,参数为product_id。
                 * 未知商品ID,通过名称、类别、描述查询 → 调用search_products工具,参数为keyword。
               - 复合问题(如“订单12345中的T恤规格”)→ 先调用query_order获取商品ID,再调用query_product查询每个ID的详情,最后筛选出目标商品。
            3. 记忆利用:{chat_history} 包含历史对话,重复问题直接用记忆回答,无需重复调用工具。
            4. 结果整合:多工具调用后,需将结果汇总、筛选,用自然语言简洁回答用户。

            【工具调用格式】
            只能使用以下工具:query_order、query_product、search_products,严格用以下格式:
            {{"name": "工具名", "parameters": {{"参数名": "值"}}}}

            【注意】
            - 若用户问题缺少必要参数(如未提供订单ID),需询问用户补充。
            - 复合问题需分步处理,先获取必要信息,再逐步解决。
            """),
            MessagesPlaceholder(variable_name="chat_history"),
            ("user", "{input}"),
            MessagesPlaceholder(variable_name="agent_scratchpad")
        ])

    def _init_executor(self):
        """初始化Agent执行器"""
        agent = create_tool_calling_agent(
            llm=self.llm,
            tools=self.tools,
            prompt=self.prompt
        )
        return AgentExecutor(
            agent=agent,
            tools=self.tools,
            verbose=True,
            max_iterations=10,
            memory=self.memory,
            early_stopping_method="force"
        )
    
#其他大模型的通用输出处理
    # def handle_question(self, question: str) -> str:
        #     """处理用户问题的入口"""
        #     result = self.executor.invoke({"input": question})
        #     return result["output"]

#qwen3-14b大模型去除思考部分的输出处理
    def handle_question(self, question: str) -> str:
        """处理用户问题的入口"""
        # 添加/no_think关闭推理过程(根据模型特性调整)
        question_with_no_think = f"{question} /no_think"#
        result = self.executor.invoke({"input": question_with_no_think})
        return result["output"].replace("", "").strip()

订单agent是作为工具封装,用于调取sqlite数据库的订单信息,返回给接入agent处理

import os
import sqlite3


class OrderAgent:
    def __init__(self):
        # 初始化订单查询工具
        self.order_tool = self._create_order_tool()

    def _create_order_tool(self):
        """创建订单查询工具"""
        from langchain_core.tools import StructuredTool

        def query_order_with_product(order_id: str):
            """查询订单信息(含商品ID)"""
            db_path = "ecommerce_orders.db"
            if not os.path.exists(db_path):
                return f"错误:未找到订单数据库文件({db_path})"

            try:
                conn = sqlite3.connect(db_path)
                cursor = conn.cursor()
                cursor.execute("""
                               SELECT status, logistics_info, total_amount, create_time, product_ids, receive_time
                               FROM orders
                               WHERE order_id = ?
                               """, (order_id,))
                result = cursor.fetchone()
                conn.close()

                if not result:
                    return f"未找到订单编号为 {order_id} 的信息"
                status, logistics, amount, create_time, product_ids, receive_time = result
                response = f"订单 {order_id} 信息:\n"
                response += f"- 状态:[{status}]\n"
                response += f"- 总金额:[{amount}元]\n"
                response += f"- 创建时间:[{create_time}]\n"
                response += f"- 签收时间:[{receive_time}]\n"
                response += f"- 商品ID:[{product_ids}]\n"
                if logistics:
                    response += f"- 物流信息:[{logistics}]"
                return response
            except Exception as e:
                return f"查询失败:{str(e)}"

        return StructuredTool.from_function(
            func=query_order_with_product,
            name="query_order",
            description="查询订单详情(含商品ID),参数为order_id(订单编号,如12345)"
        )

    def get_tool(self):
        """提供给接入Agent的工具接口"""
        return self.order_tool

商品agent也是作为工具封装,用于检索知识向量库里的知识储备,放回给接入agent处理

import os
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.tools import StructuredTool


class ProductAgent:
    def __init__(self, embedding_model_path, product_vector_path):
        self.embedding_model_path = embedding_model_path
        self.product_vector_path = product_vector_path
        self.embeddings = self._init_embeddings()
        self.vector_store = self.load_vector_store()
        self.all_products = self._load_all_products()
        self.id_to_product = {p["id"]: p for p in self.all_products if "id" in p}

        # 初始化工具
        self.search_tool = self._create_search_tool()
        self.product_tool = self._create_product_tool()

    def _init_embeddings(self):
        """初始化嵌入模型"""
        return HuggingFaceEmbeddings(
            model_name=self.embedding_model_path,
            model_kwargs={'device': 'cpu'},
            encode_kwargs={'normalize_embeddings': True}
        )

    def load_vector_store(self):
        """加载向量数据库"""
        if not os.path.exists(self.product_vector_path):
            print(f"警告:商品知识库路径不存在 {self.product_vector_path}")
            return None
        try:
            return FAISS.load_local(
                self.product_vector_path,
                self.embeddings,
                allow_dangerous_deserialization=True
            )
        except Exception as e:
            print(f"加载商品知识库失败:{e}")
            return None

    def _parse_product_info(self, content):
        """解析商品信息字符串为字典"""
        product_info = {}
        for line in content.split("\n"):
            if "商品ID:" in line:
                product_info["id"] = line.split(":")[1].strip()
            if "名称:" in line:
                product_info["name"] = line.split(":")[1].strip()
            if "规格:" in line:
                product_info["spec"] = line.split(":")[1].strip()
            if "介绍:" in line:
                product_info["description"] = line.split(":")[1].strip()
            if "价格:" in line:
                product_info["price"] = line.split(":")[1].strip()
            if "活动:" in line:
                product_info["activity"] = line.split(":")[1].strip()
        return product_info

    def _load_all_products(self):
        """加载所有商品信息"""
        if not self.vector_store:
            return []
        all_docs = list(self.vector_store.docstore._dict.values())
        products = []
        for doc in all_docs:
            products.append(self._parse_product_info(doc.page_content))
        return products

    def _create_search_tool(self):
        """创建商品搜索工具"""

        def search_products_by_keyword(keyword: str):
            """通过关键词、名称或描述搜索商品"""
            if not self.vector_store:
                return "商品知识库未加载"

            # 使用向量数据库进行语义搜索
            results = self.vector_store.similarity_search(keyword, k=2)

            if not results:
                return f"未找到与 '{keyword}' 相关的商品"

            response = f"找到与 '{keyword}' 相关的商品:\n"
            for i, doc in enumerate(results, 1):
                product_info = self._parse_product_info(doc.page_content)
                response += f"{i}. 商品ID: {product_info.get('id', '未知')}\n"
                response += f"   名称: {product_info.get('name', '未知')}\n"
                response += f"   规格: {product_info.get('spec', '未知')}\n"
                response += f"   价格: {product_info.get('price', '未知')}\n"
                response += f"   活动: {product_info.get('activity', '无')}\n\n"

            return response.strip()

        return StructuredTool.from_function(
            func=search_products_by_keyword,
            name="search_products",
            description="通过关键词、名称、描述或类别搜索商品,参数为keyword(如'衬衫'、'红色连衣裙')"
        )

    def _create_product_tool(self):
        """创建商品查询工具"""

        def query_product_info(product_id: str):
            """查询商品详情(含规格)"""
            if not self.vector_store:
                return "商品知识库未加载"

            if product_id in self.id_to_product:
                p = self.id_to_product[product_id]
                return (f"商品ID {product_id} 信息:\n"
                        f"- 名称:{p.get('name', '未知')}\n"
                        f"- 规格:{p.get('spec', '未知')}\n"
                        f"- 介绍:{p.get('description', '未知')}\n"
                        f"- 价格:{p.get('price', '未知')}\n"
                        f"- 活动:{p.get('activity', '未知')}")
            return f"未找到商品ID {product_id} 的信息"

        return StructuredTool.from_function(
            func=query_product_info,
            name="query_product",
            description="查询商品详情(含规格),参数为product_id(商品ID,如001)"
        )

    def get_tools(self):
        """提供给接入Agent的工具接口"""
        return [self.search_tool, self.product_tool]

3、app.py后端执行文件

app.py是用于启动后端服务的,给前端提供ip地址和端口,使前端可以调用agent服务

from flask import Flask, request, jsonify
from flask_cors import CORS  # 新增:导入CORS
from config import (
    OPENAI_API_KEY, BASE_URL, EMBEDDING_MODEL_PATH,
    PRODUCT_VECTOR_PATH, FLASK_HOST, FLASK_PORT, FLASK_DEBUG
)
from ecommerce_agent.agents import AccessAgent

# 初始化Flask应用
app = Flask(__name__)
CORS(app)  # 新增:启用CORS,允许跨域请求

# 初始化接入Agent
llm_config = {
    "api_key": OPENAI_API_KEY,
    "base_url": BASE_URL,
    "model_name": "qwen/qwen3-14b",
    "temperature": 0.0,
    "max_tokens": 4096
}

access_agent = AccessAgent(
    llm_config=llm_config,
    embedding_model_path=EMBEDDING_MODEL_PATH,
    product_vector_path=PRODUCT_VECTOR_PATH
)


# API接口
@app.route('/api/query', methods=['POST'])
def query():
    """处理用户查询的API接口"""
    data = request.json
    if not data or "question" not in data:
        return jsonify({"error": "缺少参数: question"}), 400

    try:
        question = data["question"]
        response = access_agent.handle_question(question)
        return jsonify({
            "success": True,
            "response": response
        })
    except Exception as e:
        return jsonify({
            "success": False,
            "error": str(e)
        }), 500


@app.route('/health', methods=['GET'])
def health_check():
    """健康检查接口"""
    return jsonify({"status": "healthy"}), 200


if __name__ == '__main__':
    app.run(
        host=FLASK_HOST,
        port=FLASK_PORT,
        debug=FLASK_DEBUG
    )

4、前端代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>电商智能查询系统</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">

    <!-- 配置Tailwind自定义颜色和字体 -->
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#3B82F6',
                        secondary: '#10B981',
                        neutral: '#1F2937',
                        light: '#F3F4F6'
                    },
                    fontFamily: {
                        sans: ['Inter', 'system-ui', 'sans-serif'],
                    },
                }
            }
        }
    </script>

    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .scrollbar-hide {
                -ms-overflow-style: none;
                scrollbar-width: none;
            }
            .scrollbar-hide::-webkit-scrollbar {
                display: none;
            }
            .shadow-soft {
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
            }
        }
    </style>
</head>
<body class="bg-gray-50 min-h-screen font-sans">
    <div class="container mx-auto px-4 py-8 max-w-4xl">
        <!-- 页面标题 -->
        <header class="text-center mb-8">
            <h1 class="text-[clamp(1.8rem,5vw,2.5rem)] font-bold text-neutral mb-2">
                <i class="fa fa-shopping-bag text-primary mr-2"></i>电商智能查询系统
            </h1>
            <p class="text-gray-500">查询订单信息、商品详情,获取即时响应</p>
            <div class="mt-4 flex justify-center items-center text-sm">
                <span id="connection-status" class="flex items-center text-yellow-500">
                    <i class="fa fa-circle-o-notch fa-spin mr-1"></i> 正在连接服务器...
                </span>
            </div>
        </header>

        <!-- 聊天界面 -->
        <div class="bg-white rounded-xl shadow-soft overflow-hidden mb-6 flex flex-col h-[60vh] min-h-[400px]">
            <!-- 聊天消息区域 -->
            <div id="chat-messages" class="flex-1 p-4 overflow-y-auto scrollbar-hide space-y-4">
                <!-- 欢迎消息 -->
                <div class="flex items-start">
                    <div class="bg-primary rounded-full p-2 text-white mr-3">
                        <i class="fa fa-robot"></i>
                    </div>
                    <div class="bg-light rounded-lg rounded-tl-none px-4 py-3 max-w-[80%]">
                        <p>欢迎使用电商智能查询系统!请输入您的问题,例如:</p>
                        <ul class="list-disc list-inside mt-2 text-sm text-gray-600 space-y-1">
                            <li>查询订单12345的信息</li>
                            <li>搜索红色连衣裙</li>
                            <li>商品ID 001的规格是什么?</li>
                        </ul>
                    </div>
                </div>
            </div>

            <!-- 输入区域 -->
            <div class="border-t p-4">
                <form id="query-form" class="flex gap-2">
                    <input
                        type="text"
                        id="user-query"
                        placeholder="请输入您的查询..."
                        class="flex-1 px-4 py-3 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all"
                        autocomplete="off"
                    >
                    <button
                        type="submit"
                        class="bg-primary hover:bg-primary/90 text-white px-6 py-3 rounded-lg transition-all flex items-center justify-center"
                    >
                        <i class="fa fa-paper-plane mr-2"></i>发送
                    </button>
                </form>
            </div>
        </div>

        <!-- 状态和信息区域 -->
        <footer class="text-center text-sm text-gray-500">
            <p>后端服务地址: <span class="text-primary font-medium">http://192.168.2.35:5000</span></p>
            <p class="mt-1">© 2023 电商智能查询系统</p>
        </footer>
    </div>

    <script>
        // 后端API基础地址
        const API_BASE_URL = 'http://192.168.2.35:5000';

        // DOM元素
        const chatMessages = document.getElementById('chat-messages');
        const userQueryInput = document.getElementById('user-query');
        const queryForm = document.getElementById('query-form');
        const connectionStatus = document.getElementById('connection-status');

        // 页面加载时检查服务器连接
        checkServerConnection();

        // 表单提交事件
        queryForm.addEventListener('submit', async (e) => {
            e.preventDefault();
            const query = userQueryInput.value.trim();

            if (!query) return;

            // 添加用户消息到界面
            addMessageToChat(query, 'user');

            // 清空输入框
            userQueryInput.value = '';

            try {
                // 显示加载状态
                addLoadingIndicator();

                // 发送请求到后端
                const response = await fetch(`${API_BASE_URL}/api/query`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ question: query })
                });

                // 移除加载状态
                removeLoadingIndicator();

                const result = await response.json();

                if (result.success) {
                    // 添加AI响应到界面
                    addMessageToChat(result.response, 'ai');
                } else {
                    addMessageToChat(`错误: ${result.error}`, 'error');
                }
            } catch (error) {
                // 移除加载状态
                removeLoadingIndicator();
                addMessageToChat(`无法连接到服务器: ${error.message}`, 'error');
                updateConnectionStatus(false);
            }
        });

        // 检查服务器连接状态
        async function checkServerConnection() {
            try {
                const response = await fetch(`${API_BASE_URL}/health`);
                if (response.ok) {
                    updateConnectionStatus(true);
                } else {
                    updateConnectionStatus(false);
                }
            } catch (error) {
                updateConnectionStatus(false);
            }
        }

        // 更新连接状态显示
        function updateConnectionStatus(isConnected) {
            if (isConnected) {
                connectionStatus.innerHTML = '<i class="fa fa-check-circle mr-1"></i> 已连接到服务器';
                connectionStatus.className = 'flex items-center text-green-500';
            } else {
                connectionStatus.innerHTML = '<i class="fa fa-exclamation-triangle mr-1"></i> 无法连接到服务器';
                connectionStatus.className = 'flex items-center text-red-500';
            }
        }

        // 添加消息到聊天界面
        function addMessageToChat(content, sender) {
            const messageDiv = document.createElement('div');
            messageDiv.className = 'flex items-start animate-fadeIn';

            let avatar, messageBubble;

            switch(sender) {
                case 'user':
                    messageDiv.className = 'flex items-start justify-end';
                    avatar = `
                        <div class="bg-gray-200 rounded-full p-2 text-neutral ml-3">
                            <i class="fa fa-user"></i>
                        </div>
                    `;
                    messageBubble = `
                        <div class="bg-primary text-white rounded-lg rounded-tr-none px-4 py-3 max-w-[80%]">
                            ${formatMessage(content)}
                        </div>
                    `;
                    break;

                case 'ai':
                    avatar = `
                        <div class="bg-primary rounded-full p-2 text-white mr-3">
                            <i class="fa fa-robot"></i>
                        </div>
                    `;
                    messageBubble = `
                        <div class="bg-light rounded-lg rounded-tl-none px-4 py-3 max-w-[80%]">
                            ${formatMessage(content)}
                        </div>
                    `;
                    break;

                case 'error':
                    avatar = `
                        <div class="bg-red-500 rounded-full p-2 text-white mr-3">
                            <i class="fa fa-exclamation-circle"></i>
                        </div>
                    `;
                    messageBubble = `
                        <div class="bg-red-50 rounded-lg rounded-tl-none px-4 py-3 max-w-[80%] text-red-700">
                            ${formatMessage(content)}
                        </div>
                    `;
                    break;
            }

            messageDiv.innerHTML = sender === 'user' ? `${messageBubble}${avatar}` : `${avatar}${messageBubble}`;
            chatMessages.appendChild(messageDiv);

            // 滚动到底部
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        // 添加加载指示器
        function addLoadingIndicator() {
            const loadingDiv = document.createElement('div');
            loadingDiv.id = 'loading-indicator';
            loadingDiv.className = 'flex items-start';
            loadingDiv.innerHTML = `
                <div class="bg-primary rounded-full p-2 text-white mr-3">
                    <i class="fa fa-robot"></i>
                </div>
                <div class="bg-light rounded-lg rounded-tl-none px-4 py-3 max-w-[80%]">
                    <div class="flex space-x-2">
                        <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
                        <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
                        <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.4s"></div>
                    </div>
                </div>
            `;
            chatMessages.appendChild(loadingDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        // 移除加载指示器
        function removeLoadingIndicator() {
            const loadingDiv = document.getElementById('loading-indicator');
            if (loadingDiv) {
                loadingDiv.remove();
            }
        }

        // 格式化消息内容(处理换行等)
        function formatMessage(content) {
            // 将换行符转换为<br>
            return content.replace(/\n/g, '<br>')
                // 突出显示订单号和商品ID
                .replace(/(订单\d+)/g, '<span class="font-semibold text-blue-600">$1</span>')
                .replace(/(商品ID\s*\d+)/g, '<span class="font-semibold text-green-600">$1</span>');
        }

        // 添加动画样式
        const style = document.createElement('style');
        style.textContent = `
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(10px); }
                to { opacity: 1; transform: translateY(0); }
            }
            .animate-fadeIn {
                animation: fadeIn 0.3s ease forwards;
            }
        `;
        document.head.appendChild(style);
    </script>
</body>
</html>

前端界面如下:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐