|
|
|
|
@ -0,0 +1,432 @@
|
|
|
|
|
from flask import Flask, render_template, request, jsonify, session
|
|
|
|
|
import json
|
|
|
|
|
import requests
|
|
|
|
|
import os
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
app.secret_key = 'your-secret-key-here'
|
|
|
|
|
|
|
|
|
|
# 大模型API配置
|
|
|
|
|
LLM_CONFIG = {
|
|
|
|
|
"deepseek": {
|
|
|
|
|
"api_key": os.getenv("DEEPSEEK_API_KEY", "your-deepseek-api-key"),
|
|
|
|
|
"base_url": "https://api.deepseek.com/v1"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@app.route('/')
|
|
|
|
|
def index():
|
|
|
|
|
return render_template('index.html')
|
|
|
|
|
|
|
|
|
|
@app.route('/ai')
|
|
|
|
|
def ai():
|
|
|
|
|
return render_template('ai.html')
|
|
|
|
|
|
|
|
|
|
# 新增:大模型行程规划API
|
|
|
|
|
@app.route('/api/generate_itinerary', methods=['POST'])
|
|
|
|
|
def generate_itinerary():
|
|
|
|
|
try:
|
|
|
|
|
data = request.json
|
|
|
|
|
user_input = {
|
|
|
|
|
"destination": data.get('destination'),
|
|
|
|
|
"days": data.get('days'),
|
|
|
|
|
"trip_type": data.get('trip_type'),
|
|
|
|
|
"travelers": data.get('travelers'),
|
|
|
|
|
"budget": data.get('budget'),
|
|
|
|
|
"special_needs": data.get('special_needs', '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 调用大模型生成行程
|
|
|
|
|
itinerary = call_llm_for_itinerary(user_input)
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
"success": True,
|
|
|
|
|
"itinerary": itinerary,
|
|
|
|
|
"title": f"{user_input['destination']}{user_input['days']}日{user_input['trip_type']}之旅"
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return jsonify({
|
|
|
|
|
"success": False,
|
|
|
|
|
"error": str(e)
|
|
|
|
|
}), 500
|
|
|
|
|
|
|
|
|
|
# 新增:语音对话API
|
|
|
|
|
@app.route('/api/voice_chat', methods=['POST'])
|
|
|
|
|
def voice_chat():
|
|
|
|
|
try:
|
|
|
|
|
data = request.json
|
|
|
|
|
user_message = data.get('message')
|
|
|
|
|
conversation_history = data.get('history', [])
|
|
|
|
|
|
|
|
|
|
# 调用大模型进行对话
|
|
|
|
|
response = call_llm_for_chat(user_message, conversation_history)
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
"success": True,
|
|
|
|
|
"response": response
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return jsonify({
|
|
|
|
|
"success": False,
|
|
|
|
|
"error": str(e)
|
|
|
|
|
}), 500
|
|
|
|
|
|
|
|
|
|
def call_llm_for_itinerary(user_input):
|
|
|
|
|
"""调用大模型生成行程"""
|
|
|
|
|
prompt = f"""
|
|
|
|
|
作为专业的旅行规划专家,请为以下需求生成详细的旅行行程:
|
|
|
|
|
|
|
|
|
|
目的地:{user_input['destination']}
|
|
|
|
|
天数:{user_input['days']}天
|
|
|
|
|
旅行类型:{user_input['trip_type']}
|
|
|
|
|
人数:{user_input['travelers']}
|
|
|
|
|
预算:{user_input['budget']}
|
|
|
|
|
特殊需求:{user_input['special_needs']}
|
|
|
|
|
|
|
|
|
|
请按以下格式回复:
|
|
|
|
|
## 行程概览
|
|
|
|
|
- 总天数:{user_input['days']}天
|
|
|
|
|
- 主题特色:{user_input['trip_type']}
|
|
|
|
|
|
|
|
|
|
## 每日详细安排
|
|
|
|
|
**第1天**
|
|
|
|
|
- 🕘 上午:[活动1] + [详细说明] + [时长]
|
|
|
|
|
- 🕐 下午:[活动2] + [详细说明] + [时长]
|
|
|
|
|
- 🕖 晚上:[活动3] + [详细说明] + [时长]
|
|
|
|
|
- 🍽 餐饮推荐:[餐厅1] [特色] [预算]
|
|
|
|
|
|
|
|
|
|
## 实用贴士
|
|
|
|
|
- 交通建议
|
|
|
|
|
- 注意事项
|
|
|
|
|
- 省钱技巧
|
|
|
|
|
|
|
|
|
|
请确保行程合理,考虑景点开放时间、交通距离、体力分配等因素。
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# 实际项目中调用大模型API
|
|
|
|
|
# 这里先用模拟数据
|
|
|
|
|
return generate_mock_itinerary(user_input)
|
|
|
|
|
|
|
|
|
|
def call_llm_for_chat(message, history):
|
|
|
|
|
"""调用大模型进行对话"""
|
|
|
|
|
# 构建对话上下文
|
|
|
|
|
messages = [
|
|
|
|
|
{"role": "system", "content": "你是一个专业的旅行助手,帮助用户规划行程、推荐目的地、提供旅行建议。回复要友好、专业、简洁。"}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# 添加历史对话
|
|
|
|
|
for msg in history[-6:]: # 只保留最近6轮对话
|
|
|
|
|
messages.append(msg)
|
|
|
|
|
|
|
|
|
|
# 添加当前消息
|
|
|
|
|
messages.append({"role": "user", "content": message})
|
|
|
|
|
|
|
|
|
|
# 实际项目中调用大模型API
|
|
|
|
|
# 这里先用模拟回复
|
|
|
|
|
return generate_mock_chat_response(message)
|
|
|
|
|
|
|
|
|
|
def generate_mock_itinerary(user_input):
|
|
|
|
|
"""生成模拟行程数据"""
|
|
|
|
|
# 这里可以扩展更多目的地和行程类型
|
|
|
|
|
base_itineraries = {
|
|
|
|
|
'日本东京': [
|
|
|
|
|
{
|
|
|
|
|
'day': 1,
|
|
|
|
|
'title': '抵达东京 - 浅草寺与东京塔',
|
|
|
|
|
'schedule': [
|
|
|
|
|
{'time': '上午', 'content': '抵达成田机场,乘坐成田特快到市区,办理酒店入住', 'duration': '09:00-12:00'},
|
|
|
|
|
{'time': '中午', 'content': '浅草寺周边午餐,推荐尝试天妇罗或荞麦面', 'duration': '12:30-14:00'},
|
|
|
|
|
{'time': '下午', 'content': '参观浅草寺,仲见世商店街购买纪念品', 'duration': '14:30-17:00'},
|
|
|
|
|
{'time': '晚上', 'content': '登上东京塔,欣赏夜景并拍照', 'duration': '18:30-20:30'}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destination = user_input['destination']
|
|
|
|
|
days = int(user_input['days'])
|
|
|
|
|
|
|
|
|
|
# 获取基础行程或默认行程
|
|
|
|
|
itinerary = base_itineraries.get(destination, base_itineraries['日本东京'])
|
|
|
|
|
|
|
|
|
|
# 补充不足的天数
|
|
|
|
|
while len(itinerary) < days:
|
|
|
|
|
day_index = len(itinerary) + 1
|
|
|
|
|
itinerary.append({
|
|
|
|
|
'day': day_index,
|
|
|
|
|
'title': f'自由探索 - 深度体验{destination}',
|
|
|
|
|
'schedule': [
|
|
|
|
|
{'time': '上午', 'content': '根据兴趣自由安排,推荐前往当地小众景点', 'duration': '09:00-12:00'},
|
|
|
|
|
{'time': '中午', 'content': '尝试当地特色美食,建议询问民宿老板推荐', 'duration': '12:30-14:00'},
|
|
|
|
|
{'time': '下午', 'content': '休闲活动(如咖啡馆看书、河边散步)', 'duration': '14:30-17:00'}
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return itinerary[:days] # 只返回所需天数的行程
|
|
|
|
|
|
|
|
|
|
def generate_mock_chat_response(message):
|
|
|
|
|
"""生成模拟聊天回复"""
|
|
|
|
|
responses = {
|
|
|
|
|
'推荐': '根据您的喜好,我推荐以下几个目的地:\n\n1. **日本京都** - 适合文化体验,有丰富的寺庙和传统建筑\n2. **泰国清迈** - 适合美食和放松,物价相对较低\n3. **云南丽江** - 国内首选,自然风光和纳西文化都很棒',
|
|
|
|
|
'预算': '预算规划建议:\n\n- **经济型** (¥3000以下):选择青旅、当地交通、街边小吃\n- **舒适型** (¥3000-8000):商务酒店、部分出租车、餐厅用餐\n- **豪华型** (¥8000以上):星级酒店、包车、高端餐厅',
|
|
|
|
|
'天气': '请告诉我具体的目的地和出行时间,我可以为您查询当地的天气情况和最佳旅行季节。',
|
|
|
|
|
'签证': '签证信息因目的地而异:\n\n- **泰国**:落地签或提前办理\n- **日本**:需要提前办理签证\n- **欧洲**:申根签证,需要较长时间准备材料'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 简单的关键词匹配
|
|
|
|
|
for keyword, response in responses.items():
|
|
|
|
|
if keyword in message:
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
return '我主要专注于旅行规划相关的问题,比如目的地推荐、行程安排、预算规划、签证信息等。如果您有具体的旅行问题,我很乐意为您提供帮助!'
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
app.run(debug=True, port=5000)
|
|
|
|
|
from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import requests
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from config import Config
|
|
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
app.config.from_object(Config)
|
|
|
|
|
|
|
|
|
|
# 确保上传目录存在
|
|
|
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
|
|
|
|
|
|
|
|
|
# 模拟用户数据(实际项目中使用数据库)
|
|
|
|
|
users = {
|
|
|
|
|
'test@example.com': {
|
|
|
|
|
'password': '123456',
|
|
|
|
|
'username': '测试用户',
|
|
|
|
|
'avatar': 'https://picsum.photos/id/1005/200/200'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 主页路由
|
|
|
|
|
@app.route('/')
|
|
|
|
|
def index():
|
|
|
|
|
return render_template('index.html')
|
|
|
|
|
|
|
|
|
|
# 登录页路由
|
|
|
|
|
@app.route('/login', methods=['GET', 'POST'])
|
|
|
|
|
def login():
|
|
|
|
|
if request.method == 'POST':
|
|
|
|
|
email = request.form.get('email')
|
|
|
|
|
password = request.form.get('password')
|
|
|
|
|
|
|
|
|
|
user = users.get(email)
|
|
|
|
|
if user and user['password'] == password:
|
|
|
|
|
session['user'] = {
|
|
|
|
|
'email': email,
|
|
|
|
|
'username': user['username'],
|
|
|
|
|
'avatar': user['avatar']
|
|
|
|
|
}
|
|
|
|
|
flash('登录成功!', 'success')
|
|
|
|
|
return redirect(url_for('index'))
|
|
|
|
|
else:
|
|
|
|
|
flash('邮箱或密码错误', 'error')
|
|
|
|
|
|
|
|
|
|
return render_template('login.html')
|
|
|
|
|
|
|
|
|
|
# 注册页路由
|
|
|
|
|
@app.route('/register', methods=['GET', 'POST'])
|
|
|
|
|
def register():
|
|
|
|
|
if request.method == 'POST':
|
|
|
|
|
email = request.form.get('email')
|
|
|
|
|
password = request.form.get('password')
|
|
|
|
|
confirm_password = request.form.get('confirm_password')
|
|
|
|
|
|
|
|
|
|
if password != confirm_password:
|
|
|
|
|
flash('两次输入的密码不一致', 'error')
|
|
|
|
|
elif email in users:
|
|
|
|
|
flash('该邮箱已被注册', 'error')
|
|
|
|
|
else:
|
|
|
|
|
users[email] = {
|
|
|
|
|
'password': password,
|
|
|
|
|
'username': email.split('@')[0],
|
|
|
|
|
'avatar': 'https://picsum.photos/id/1005/200/200'
|
|
|
|
|
}
|
|
|
|
|
flash('注册成功,请登录', 'success')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
return render_template('register.html')
|
|
|
|
|
|
|
|
|
|
# 退出登录
|
|
|
|
|
@app.route('/logout')
|
|
|
|
|
def logout():
|
|
|
|
|
session.pop('user', None)
|
|
|
|
|
flash('已退出登录', 'info')
|
|
|
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
|
|
|
|
# 个人中心
|
|
|
|
|
@app.route('/profile')
|
|
|
|
|
def profile():
|
|
|
|
|
if 'user' not in session:
|
|
|
|
|
flash('请先登录', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
return render_template('profile.html')
|
|
|
|
|
|
|
|
|
|
# AI行程规划页
|
|
|
|
|
@app.route('/ai')
|
|
|
|
|
def ai():
|
|
|
|
|
return render_template('ai.html')
|
|
|
|
|
|
|
|
|
|
# 打卡页面
|
|
|
|
|
@app.route('/checkin')
|
|
|
|
|
def checkin():
|
|
|
|
|
if 'user' not in session:
|
|
|
|
|
flash('请先登录', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
return render_template('checkin.html')
|
|
|
|
|
|
|
|
|
|
# 发表笔记页面
|
|
|
|
|
@app.route('/postnote')
|
|
|
|
|
def postnote():
|
|
|
|
|
if 'user' not in session:
|
|
|
|
|
flash('请先登录', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
return render_template('postnote.html')
|
|
|
|
|
|
|
|
|
|
# API路由 - 生成行程
|
|
|
|
|
@app.route('/api/generate_itinerary', methods=['POST'])
|
|
|
|
|
def generate_itinerary():
|
|
|
|
|
try:
|
|
|
|
|
data = request.json
|
|
|
|
|
user_input = {
|
|
|
|
|
"destination": data.get('destination'),
|
|
|
|
|
"days": data.get('days'),
|
|
|
|
|
"trip_type": data.get('trip_type'),
|
|
|
|
|
"travelers": data.get('travelers'),
|
|
|
|
|
"budget": data.get('budget'),
|
|
|
|
|
"special_needs": data.get('special_needs', '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 调用大模型生成行程
|
|
|
|
|
itinerary = call_llm_for_itinerary(user_input)
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
"success": True,
|
|
|
|
|
"itinerary": itinerary,
|
|
|
|
|
"title": f"{user_input['destination']}{user_input['days']}日{user_input['trip_type']}之旅"
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return jsonify({
|
|
|
|
|
"success": False,
|
|
|
|
|
"error": str(e)
|
|
|
|
|
}), 500
|
|
|
|
|
|
|
|
|
|
# API路由 - AI对话
|
|
|
|
|
@app.route('/api/ai_chat', methods=['POST'])
|
|
|
|
|
def ai_chat():
|
|
|
|
|
try:
|
|
|
|
|
data = request.json
|
|
|
|
|
user_message = data.get('message')
|
|
|
|
|
|
|
|
|
|
# 调用大模型进行对话
|
|
|
|
|
response = call_llm_for_chat(user_message)
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
"success": True,
|
|
|
|
|
"response": response
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return jsonify({
|
|
|
|
|
"success": False,
|
|
|
|
|
"error": str(e)
|
|
|
|
|
}), 500
|
|
|
|
|
|
|
|
|
|
# 大模型调用函数
|
|
|
|
|
def call_llm_for_itinerary(user_input):
|
|
|
|
|
"""调用大模型生成行程"""
|
|
|
|
|
# 这里使用模拟数据,实际项目中调用真实API
|
|
|
|
|
return generate_mock_itinerary(user_input)
|
|
|
|
|
|
|
|
|
|
def call_llm_for_chat(message):
|
|
|
|
|
"""调用大模型进行对话"""
|
|
|
|
|
# 这里使用模拟回复,实际项目中调用真实API
|
|
|
|
|
return generate_mock_chat_response(message)
|
|
|
|
|
|
|
|
|
|
def generate_mock_itinerary(user_input):
|
|
|
|
|
"""生成模拟行程数据"""
|
|
|
|
|
destination = user_input['destination']
|
|
|
|
|
days = int(user_input['days'])
|
|
|
|
|
|
|
|
|
|
base_itineraries = {
|
|
|
|
|
'日本东京': [
|
|
|
|
|
{
|
|
|
|
|
'day': 1,
|
|
|
|
|
'title': '抵达东京 - 浅草寺与东京塔',
|
|
|
|
|
'schedule': [
|
|
|
|
|
{'time': '上午', 'content': '抵达成田机场,乘坐成田特快到市区,办理酒店入住', 'duration': '09:00-12:00'},
|
|
|
|
|
{'time': '中午', 'content': '浅草寺周边午餐,推荐尝试天妇罗或荞麦面', 'duration': '12:30-14:00'},
|
|
|
|
|
{'time': '下午', 'content': '参观浅草寺,仲见世商店街购买纪念品', 'duration': '14:30-17:00'},
|
|
|
|
|
{'time': '晚上', 'content': '登上东京塔,欣赏夜景并拍照', 'duration': '18:30-20:30'}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'day': 2,
|
|
|
|
|
'title': '上野公园 - 秋叶原动漫体验',
|
|
|
|
|
'schedule': [
|
|
|
|
|
{'time': '上午', 'content': '上野公园散步,参观东京国立博物馆', 'duration': '09:30-12:30'},
|
|
|
|
|
{'time': '中午', 'content': '上野公园周边鳗鱼饭午餐', 'duration': '13:00-14:30'},
|
|
|
|
|
{'time': '下午', 'content': '秋叶原电器街体验动漫文化,逛免税店', 'duration': '15:00-18:00'}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
'云南丽江': [
|
|
|
|
|
{
|
|
|
|
|
'day': 1,
|
|
|
|
|
'title': '抵达丽江 - 古城初体验',
|
|
|
|
|
'schedule': [
|
|
|
|
|
{'time': '上午', 'content': '抵达丽江三义机场,乘车到古城,办理民宿入住', 'duration': '09:00-12:00'},
|
|
|
|
|
{'time': '中午', 'content': '古城内尝试纳西族特色菜(腊排骨火锅)', 'duration': '12:30-14:00'},
|
|
|
|
|
{'time': '下午', 'content': '丽江古城石板路漫步,参观四方街、大水车', 'duration': '14:30-17:30'},
|
|
|
|
|
{'time': '晚上', 'content': '古城酒吧街体验民谣演出', 'duration': '19:00-21:00'}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 获取基础行程或默认行程
|
|
|
|
|
itinerary = base_itineraries.get(destination, base_itineraries['日本东京'])
|
|
|
|
|
|
|
|
|
|
# 补充不足的天数
|
|
|
|
|
while len(itinerary) < days:
|
|
|
|
|
day_index = len(itinerary) + 1
|
|
|
|
|
itinerary.append({
|
|
|
|
|
'day': day_index,
|
|
|
|
|
'title': f'自由探索 - 深度体验{destination}',
|
|
|
|
|
'schedule': [
|
|
|
|
|
{'time': '上午', 'content': '根据兴趣自由安排,推荐前往当地小众景点', 'duration': '09:00-12:00'},
|
|
|
|
|
{'time': '中午', 'content': '尝试当地特色美食,建议询问民宿老板推荐', 'duration': '12:30-14:00'},
|
|
|
|
|
{'time': '下午', 'content': '休闲活动(如咖啡馆看书、河边散步)', 'duration': '14:30-17:00'}
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return itinerary[:days]
|
|
|
|
|
|
|
|
|
|
def generate_mock_chat_response(message):
|
|
|
|
|
"""生成模拟聊天回复"""
|
|
|
|
|
responses = {
|
|
|
|
|
'推荐': '根据您的喜好,我推荐以下几个目的地:\n\n1. **日本京都** - 适合文化体验,有丰富的寺庙和传统建筑\n2. **泰国清迈** - 适合美食和放松,物价相对较低\n3. **云南丽江** - 国内首选,自然风光和纳西文化都很棒',
|
|
|
|
|
'预算': '预算规划建议:\n\n- **经济型** (¥3000以下):选择青旅、当地交通、街边小吃\n- **舒适型** (¥3000-8000):商务酒店、部分出租车、餐厅用餐\n- **豪华型** (¥8000以上):星级酒店、包车、高端餐厅',
|
|
|
|
|
'天气': '请告诉我具体的目的地和出行时间,我可以为您查询当地的天气情况和最佳旅行季节。',
|
|
|
|
|
'签证': '签证信息因目的地而异:\n\n- **泰国**:落地签或提前办理\n- **日本**:需要提前办理签证\n- **欧洲**:申根签证,需要较长时间准备材料'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 简单的关键词匹配
|
|
|
|
|
for keyword, response in responses.items():
|
|
|
|
|
if keyword in message:
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
return '我主要专注于旅行规划相关的问题,比如目的地推荐、行程安排、预算规划、签证信息等。如果您有具体的旅行问题,我很乐意为您提供帮助!'
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
app.run(debug=True, host='0.0.0.0', port=5000)
|