diff --git a/app/api/__init__.py b/app/api/__init__.py index 30df3db..8707704 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -1,13 +1,11 @@ from fastapi import APIRouter from .chat import chat_router -from .upload import upload_api_router from .v1 import v1_router api_router = APIRouter() api_router.include_router(v1_router, prefix="/v1") api_router.include_router(chat_router, prefix="") -api_router.include_router(upload_api_router, prefix="") __all__ = ["api_router"] diff --git a/app/api/chat/ai/chat_service.py b/app/api/chat/ai/chat_service.py index 48ee766..cfb95d5 100644 --- a/app/api/chat/ai/chat_service.py +++ b/app/api/chat/ai/chat_service.py @@ -1,4 +1,6 @@ import os, openai, aiomysql +import random + from openai import OpenAI from dotenv import load_dotenv import redis.asyncio as redis @@ -83,20 +85,73 @@ EXTRACT_PROMPT = """你是一名景区名称精准匹配助手。用户的问题 大慈阁""" # 客流查询后回答的提示词 -ANSWER_PROMPT = """生成一份人性化的出行指南。输出结构要求如下,使用生活化语言表达,注意不可依据想象生成未经查询的客流数据: - 首行为标题(如:**景区名称**游览建议,景区名称要显示正确、完整), - **实时客流**(展示在园人数,=进入人数-离开人数,若为负取绝对值,超出最大承载量时不显示具体人数,仅显示舒适度等级;展示舒适度等级,进入和离开人数、瞬时承载量不展示,承载率仅以“舒适度等级”汉字形式体现,不展示百分比,并用简短1-2句话描述舒适度等级代表的游览体验,如游客较少,游览体验轻松。根据承载率范围输出等级,并用 HTML 字体颜色标签展示,舒适度等级:<30% 为舒适(绿色)(热门景点基本不排队,通道畅通,拍照无遮挡),30%-50% 为较舒适(蓝色)(热门景点排队 5-10 分钟,通道不挤,拍照等待短),50%-70% 为一般(黄色)(热门景点排队 15-30 分钟,部分通道挤,拍照需等),70%-90% 为较拥挤(橙色)**(热门景点排队 30-60 分钟,通道拥挤,拍照需错峰,座位紧张),>90% 为拥挤(红色)(热门景点排队超 60 分钟,通道拥堵,拍照困难,可能限流),如 舒适 较舒适 一般 较拥挤 拥挤,不要加括号或冗余说明,仅展示彩色词语), -**周边停车指南**(展示停车场名称,距景区距离,步行所需时间,当前余位,收费标准;每项数据为一行,收费标准需提示“具体以现场公示为准”), - **出行与游览建议**(是否前往:强烈推荐/推荐/谨慎考虑/暂缓前往,用生活化建议替代机械的等级描述,如:强烈推荐可表述为现在正是游览的好时机!馆内人少舒适,无需排队等候,可以随到随游,享受宁静的观展氛围;当较拥挤或拥挤时显示错峰建议:如当日16:00后或次日8:30前;交通方式:停车场充足时推荐自驾,紧张建议打车或备用停车场,已满建议使用公共交通如XX路公交;路线建议:当较拥挤或拥挤时则推荐反常规路线或人少区域;建议游览时长:较舒适多10%,一般多20%,较拥挤/拥挤多30%-50%;不要直接显示百分比,需提出具体时长,如1小时以上;替代方案:当较拥挤或拥挤时推荐1-2个周边承载率低的景区并说明优势), -**温馨提示**(如安全提醒、天气影响等影响游客游览的内容),语言风格亲切如朋友交谈,用"您""记得""推荐"等暖心词汇,禁用机械术语,用**加粗**突出关键建议,加入表情符号增加亲和力,不展示原始字段数据,结尾不输出数据时间,仅加提示“动态信息请以现场为准,祝您旅途愉快!”。 -若未找到景区的信息时,生成景区通用游览建议,使用以下输出结构, -首行为标题(如:**景区名称** 游览建议,景区名称要显示正确、完整), -**实时客流**(仅显示提示“暂时无法获取实时客流数据,您仍可参考以下出行建议。”), -**出行与游览建议**(交通方式:列出几种常规交通方式,并推荐大众选择较多的出行方式,简单描述优势和注意事项; -游览路线:描述游览景区常规路线和游览重点,突出表现景区特色; -游览时间:显示常规参观时间,旺季需多预留30%,不要直接显示百分比,需提出具体时长,如1小时以上; -附近景区推荐:推荐1-2个周边景区并说明景区特点), -**温馨提示**(如安全提醒、天气影响等影响游客游览的内容),语言风格亲切如朋友交谈,用"您""记得""推荐"等暖心词汇,禁用机械术语,用**加粗**突出关键建议,加入表情符号增加亲和力,不展示原始字段数据,结尾不输出数据时间,仅加提示“动态信息请以现场为准,祝您旅途愉快!”。""" +ANSWER_PROMPT = """ +**景区游览指南生成要求** + +输入数据格式: +- 用户问题:{msg} +- 查询到的数据:{data}(包含景区名称、在园人数、最大承载量、进出人数等) +- 输出语言:{language} + +输出要求: + +1. 标题格式: +**完整景区名称**游览建议 + +2. 实时客流展示: +- 计算当前在园人数(进入人数-离开人数的绝对值) +- 计算承载率(在园人数/最大承载量) +- 按以下标准显示舒适度等级: + <30%:舒适 + 30%-50%:较舒适 + 50%-70%:一般 + 70%-90%:较拥挤 + >90%:拥挤 +- 附加简短体验描述(1-2句) + +3. 周边停车指南: +- 停车场名称 +- 距离(米) +- 步行时间(分钟) +- 当前余位/总车位 +- 收费标准(注明"具体以现场公示为准") + +4. 出行与游览建议: +- 推荐程度(强烈推荐/推荐/谨慎考虑/暂缓前往) +- 具体建议(包含错峰时间、交通方式、路线建议) +- 建议游览时长(根据舒适度调整) +- 替代景区推荐(当拥挤时) + +5. 温馨提示: +- 安全提醒 +- 天气影响 +- 其他注意事项 + +6. 数据缺失时的通用建议: +- 标题:**景区名称**游览建议 +- 实时客流提示 +- 常规交通建议 +- 游览路线说明 +- 附近景区推荐 + +语言风格要求: +- 使用生动形象的语言描述,适当添加emoji表情符号增强表现力 +- 采用"您""记得""推荐"等暖心词汇 +- 禁用专业术语 +- 使用**加粗**强调关键信息 +- 结尾提示:"动态信息请以现场为准,祝您旅途愉快!" + +数据要求: +- 仅使用查询到的数据 +- 不虚构未提供的信息 +- 数字数据仅展示计算后的在园人数,不显示例如进入人数、离开人数、承载量和承载率等原始数据字段 +- 不输出数据更新时间 +- 标题字体大小为 18–20px,加粗显示 +- 正文内容字体大小为 15–16px +- 行间距为 1.6–1.8 倍字体大小 +- 段落与模块之间上下边距应为10–16px +- 不能使用小于 14px 的字体 +""" async def classify(msg: str) -> str: print(f"Starting classification for message: {msg}") @@ -113,10 +168,24 @@ async def classify(msg: str) -> str: raise async def ai_chat_stream(inp: ChatIn, conversation_history: list) -> AsyncGenerator[str, None]: - chat_prompt = ( - f"你是一个河北省保定市的旅游助手,为游客提供保定市文旅相关问答服务,包括但不限于行程规划、" - f"景点推荐等,不提供其他任何非保定文旅相关信息。输出语言为:{inp.language}" - ) + chat_prompt = f""" + 你是一个专门服务河北省保定市旅游的AI助手,主要提供以下精准服务: + 1. 行程规划:根据游客的停留天数(1-7天)、预算范围(经济型/中档/豪华)、兴趣偏好(历史文化/自然风光/美食体验)提供定制化行程方案 + 2. 景点推荐:详细介绍保定市3A级以上旅游景区(如白洋淀、野三坡、清西陵等)的开放时间、门票价格、最佳游览季节和交通方式 + 3. 特色推荐:提供保定驴肉火烧、槐茂酱菜等地方特色美食的具体店铺地址和人均消费 + 4. 实用信息:提供保定市区及周边县市的公共交通线路、出租车参考价格、天气情况等实用旅行信息 + + **服务要求**: + - 使用生动形象的语言描述,适当添加emoji表情符号增强表现力 + - 采用Markdown语法组织内容,合理使用**加粗**、*斜体*等格式 + - 分点说明时使用清晰的列表格式 + - 重要信息使用高亮标记 + + **服务限制**: + - 地理范围:仅限保定市行政区划内(含下辖县市)的文旅信息 + - 语言输出:严格使用用户指定的{inp.language}语言回复 + - 问题边界:对非保定文旅相关问题统一回复"我是您的保定旅行助手,专注于解答有关保定市的旅行问题哦~" + """ messages = [{"role": "system", "content": chat_prompt}] + conversation_history messages.append({"role": "user", "content": inp.message}) @@ -150,9 +219,12 @@ async def ai_chat_stream(inp: ChatIn, conversation_history: list) -> AsyncGenera conversation_history.append({"role": "assistant", "content": full_response}) print("AI chat stream finished.") +def get_formatted_prompt(user_language,msg,data): + # 使用format方法替换占位符 + return ANSWER_PROMPT.format(language=user_language, msg=msg, data=data) async def gen_markdown_stream(msg: str, data: str, language: str, conversation_history: list) -> AsyncGenerator[str, None]: - prompt = f"""你是一位智能旅游助手,结合用户问题:{msg} 和查询内容:{data},"""+ANSWER_PROMPT + f"""输出语言为:{language}""" + prompt = get_formatted_prompt(language,msg,data) # 将变量替换到提示词中 messages = conversation_history + [{"role": "user", "content": prompt}] @@ -235,8 +307,11 @@ async def query_flow(request: Request, spot: str) -> str: print(f"No data found for spot: {spot}") return f"**未找到景区【{spot}】的信息,请检查名称是否正确。\n\n(内容仅供参考)" + #随机生成剩余车位数量进行测试 + car_num = random.randint(0, 100) + jl_num = random.randint(50, 200) # 修改结果拼接部分,使用新的列名 - result = f"**{spot} 客流**\n\n进入人数: {row[0]}\n离开人数: {row[1]}\n\n景区瞬时承载量:{row[2]},暂无停车场数据(内容仅供参考)" + result = f"**{spot} 客流**\n\n进入人数: {row[0]}\n离开人数: {row[1]}\n\n景区瞬时承载量:{row[2]};停车场名称:临时地上停车场 ,距离:{jl_num}米,空余车位:{car_num},总车位:100,收费标准:30分钟内免费,超出后3元/小时。(内容仅供参考)" # Step 3: 写入 Redis 缓存 print(f"Writing data to Redis cache for key: {cache_key}") diff --git a/app/api/upload/__init__.py b/app/api/upload/__init__.py deleted file mode 100644 index ffe587f..0000000 --- a/app/api/upload/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# app/api/v1/upload/__init__.py -from fastapi import APIRouter -from .upload import upload_api_router as upload_router - -upload_api_router = APIRouter() -upload_api_router.include_router(upload_router, prefix='/upload') \ No newline at end of file diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py index f6d801b..bd414f3 100644 --- a/app/api/v1/__init__.py +++ b/app/api/v1/__init__.py @@ -10,7 +10,7 @@ from .menus import menus_router from .roles import roles_router from .users import users_router from .quick_question import quick_question_router -from app.api.upload import upload_api_router +from .upload import upload_api_router v1_router = APIRouter() @@ -22,6 +22,6 @@ v1_router.include_router(apis_router, prefix="/api", dependencies=[DependPermiss v1_router.include_router(depts_router, prefix="/dept", dependencies=[DependPermission]) v1_router.include_router(auditlog_router, prefix="/auditlog", dependencies=[DependPermission]) v1_router.include_router(quick_question_router, prefix="/question", dependencies=[DependPermission]) -v1_router.include_router(upload_api_router) +v1_router.include_router(upload_api_router, prefix="/upload", dependencies=[DependPermission]) diff --git a/app/api/upload/upload/__init__.py b/app/api/v1/upload/__init__.py similarity index 100% rename from app/api/upload/upload/__init__.py rename to app/api/v1/upload/__init__.py diff --git a/app/api/upload/upload/upload.py b/app/api/v1/upload/upload.py similarity index 100% rename from app/api/upload/upload/upload.py rename to app/api/v1/upload/upload.py diff --git a/web/.env.development b/web/.env.development index 2af1748..d4aa8df 100644 --- a/web/.env.development +++ b/web/.env.development @@ -5,4 +5,4 @@ VITE_PUBLIC_PATH = '/' VITE_USE_PROXY = true # base api -VITE_BASE_API = 'http://localhost:8111/api/v1' +VITE_BASE_API = '/api/v1' diff --git a/web/build/constant.js b/web/build/constant.js index cf58a73..31be433 100644 --- a/web/build/constant.js +++ b/web/build/constant.js @@ -17,7 +17,7 @@ export const PROXY_CONFIG = { * @转发路径 http://localhost:9999/api/v1/user */ '/api/v1': { - target: 'http://127.0.0.1:9999', + target: 'http://127.0.0.1:8111', // 修改为与.env.development中一致的端口 changeOrigin: true, }, } diff --git a/web/src/views/system/question/index.vue b/web/src/views/system/question/index.vue index 3479dd7..69706d7 100644 --- a/web/src/views/system/question/index.vue +++ b/web/src/views/system/question/index.vue @@ -87,7 +87,6 @@ @@ -363,9 +365,8 @@ watch( border: 1px solid #e5e7eb; border-radius: 4px; width: 100%; - /* 增加最小高度和高度设置 */ min-height: 300px; - height: 50vh; /* 使用视口高度单位,确保编辑器随页面大小调整 */ + height: 50vh; overflow: hidden; } \ No newline at end of file diff --git a/web/vite.config.js b/web/vite.config.js index 8133af2..9828e40 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -31,11 +31,6 @@ export default defineConfig(({ command, mode }) => { proxy: VITE_USE_PROXY ? { [VITE_BASE_API]: PROXY_CONFIG[VITE_BASE_API], - // 添加对/api/upload路径的代理配置 - '/api/upload': { - target: 'http://127.0.0.1:8111', - changeOrigin: true, - }, } : undefined, },