基于lm studio + langchain + flask + sqlite + html的小型电商查询agent系统
摘要:该项目是一个基于本地部署Qwen3-14B大模型的电商信息查询Agent系统,采用LangChain和Flask框架开发。系统包含三个核心组件:接入Agent(负责问题理解与协调)、订单Agent(查询SQLite订单数据库)和商品Agent(检索FAISS向量库中的商品信息)。通过BGE-large-zh-v1.5模型实现中文语义理解,支持订单状态查询、商品搜索等功能。前端使用HTML/T
开源地址:
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>
前端界面如下:
更多推荐
所有评论(0)