Merge branch 'main' of http://331002.xyz:8418/240241002/ai_v
This commit is contained in:
commit
455a63f20f
@ -5,6 +5,14 @@
|
|||||||
### 1. 环境准备
|
### 1. 环境准备
|
||||||
确保已安装 Python 3.8+ 和 PostgreSQL / Redis。
|
确保已安装 Python 3.8+ 和 PostgreSQL / Redis。
|
||||||
|
|
||||||
|
**创建虚拟环境**
|
||||||
|
```bash
|
||||||
|
# 如果 python 命令不可用,请尝试使用 py
|
||||||
|
python -m venv .venv
|
||||||
|
# 或者
|
||||||
|
py -m venv .venv
|
||||||
|
```
|
||||||
|
|
||||||
**激活虚拟环境** (推荐)
|
**激活虚拟环境** (推荐)
|
||||||
```bash
|
```bash
|
||||||
# Windows (PowerShell)
|
# Windows (PowerShell)
|
||||||
|
|||||||
@ -68,7 +68,7 @@ def generate():
|
|||||||
|
|
||||||
# 2. 扣除积分 (如果是试用模式)
|
# 2. 扣除积分 (如果是试用模式)
|
||||||
if use_trial:
|
if use_trial:
|
||||||
deduct_points(user, cost)
|
deduct_points(user_id, cost)
|
||||||
|
|
||||||
model_value = data.get('model')
|
model_value = data.get('model')
|
||||||
prompt = data.get('prompt')
|
prompt = data.get('prompt')
|
||||||
@ -95,7 +95,7 @@ def generate():
|
|||||||
|
|
||||||
# 5. 启动异步生图任务
|
# 5. 启动异步生图任务
|
||||||
app = current_app._get_current_object()
|
app = current_app._get_current_object()
|
||||||
task_id = start_async_image_task(app, user_id, payload, api_key, target_api, cost, data.get('mode'), model_value)
|
task_id = start_async_image_task(app, user_id, payload, api_key, target_api, cost, data.get('mode'), model_value, use_trial)
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
@ -119,7 +119,7 @@ def video_generate():
|
|||||||
return jsonify({"error": error}), 400
|
return jsonify({"error": error}), 400
|
||||||
|
|
||||||
# 2. 扣除积分
|
# 2. 扣除积分
|
||||||
deduct_points(user, cost)
|
deduct_points(user_id, cost)
|
||||||
|
|
||||||
# 3. 构造 Payload
|
# 3. 构造 Payload
|
||||||
payload = {
|
payload = {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from models import Order, User, to_bj_time, get_bj_now
|
|||||||
from services.alipay_service import AlipayService
|
from services.alipay_service import AlipayService
|
||||||
from services.logger import system_logger
|
from services.logger import system_logger
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
payment_bp = Blueprint('payment', __name__, url_prefix='/payment')
|
payment_bp = Blueprint('payment', __name__, url_prefix='/payment')
|
||||||
|
|
||||||
@ -102,8 +102,7 @@ def payment_history():
|
|||||||
)
|
)
|
||||||
).order_by(Order.created_at.desc()).all()
|
).order_by(Order.created_at.desc()).all()
|
||||||
|
|
||||||
import datetime as dt_module
|
return render_template('recharge_history.html', orders=orders, modules={'datetime': datetime})
|
||||||
return render_template('recharge_history.html', orders=orders, modules={'datetime': dt_module})
|
|
||||||
|
|
||||||
@payment_bp.route('/api/history', methods=['GET'])
|
@payment_bp.route('/api/history', methods=['GET'])
|
||||||
def api_payment_history():
|
def api_payment_history():
|
||||||
|
|||||||
@ -50,31 +50,34 @@ def validate_generation_request(user, data):
|
|||||||
else:
|
else:
|
||||||
return None, None, 0, False, "可用积分已耗尽,请充值或切换至自定义 Key 模式"
|
return None, None, 0, False, "可用积分已耗尽,请充值或切换至自定义 Key 模式"
|
||||||
|
|
||||||
|
# 计算单价
|
||||||
cost = get_model_cost(model_value, is_video=False)
|
cost = get_model_cost(model_value, is_video=False)
|
||||||
if use_trial and is_premium:
|
if use_trial and is_premium:
|
||||||
cost *= 2
|
cost *= 2
|
||||||
|
|
||||||
if use_trial:
|
if use_trial:
|
||||||
if user.points < cost:
|
if user.points < cost:
|
||||||
return None, None, cost, True, "可用积分不足"
|
return None, None, cost, True, f"可用积分不足(本次需要 {cost} 积分)"
|
||||||
|
|
||||||
return api_key, target_api, cost, use_trial, None
|
return api_key, target_api, cost, use_trial, None
|
||||||
|
|
||||||
def deduct_points(user, cost):
|
def deduct_points(user_id, cost):
|
||||||
"""扣除积分"""
|
"""原子扣除积分"""
|
||||||
|
user = db.session.query(User).filter_by(id=user_id).populate_existing().with_for_update().first()
|
||||||
|
if user:
|
||||||
user.points -= cost
|
user.points -= cost
|
||||||
user.has_used_points = True
|
user.has_used_points = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
def refund_points(user_id, cost):
|
def refund_points(user_id, cost):
|
||||||
"""退还积分"""
|
"""原子退还积分"""
|
||||||
try:
|
try:
|
||||||
user = db.session.get(User, user_id)
|
user = db.session.query(User).filter_by(id=user_id).populate_existing().with_for_update().first()
|
||||||
if user:
|
if user:
|
||||||
user.points += cost
|
user.points += cost
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except:
|
except:
|
||||||
pass
|
db.session.rollback()
|
||||||
|
|
||||||
def handle_chat_generation_sync(user_id, api_key, model_value, prompt, use_trial, cost):
|
def handle_chat_generation_sync(user_id, api_key, model_value, prompt, use_trial, cost):
|
||||||
"""同步处理对话类模型"""
|
"""同步处理对话类模型"""
|
||||||
@ -114,7 +117,7 @@ def handle_chat_generation_sync(user_id, api_key, model_value, prompt, use_trial
|
|||||||
refund_points(user_id, cost)
|
refund_points(user_id, cost)
|
||||||
return {"error": str(e)}, 500
|
return {"error": str(e)}, 500
|
||||||
|
|
||||||
def start_async_image_task(app, user_id, payload, api_key, target_api, cost, mode, model_value):
|
def start_async_image_task(app, user_id, payload, api_key, target_api, cost, mode, model_value, use_trial=False):
|
||||||
"""启动异步生图任务"""
|
"""启动异步生图任务"""
|
||||||
task_id = str(uuid.uuid4())
|
task_id = str(uuid.uuid4())
|
||||||
|
|
||||||
@ -123,7 +126,7 @@ def start_async_image_task(app, user_id, payload, api_key, target_api, cost, mod
|
|||||||
|
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=process_image_generation,
|
target=process_image_generation,
|
||||||
args=(app, user_id, task_id, payload, api_key, target_api, cost)
|
args=(app, user_id, task_id, payload, api_key, target_api, cost, use_trial)
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
return task_id
|
return task_id
|
||||||
@ -150,7 +153,7 @@ def start_async_video_task(app, user_id, payload, cost, model_value):
|
|||||||
|
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=process_video_generation,
|
target=process_video_generation,
|
||||||
args=(app, user_id, task_id, payload, api_key, cost)
|
args=(app, user_id, task_id, payload, api_key, cost, True) # 视频目前默认为积分模式
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
return task_id
|
return task_id
|
||||||
|
|||||||
@ -90,7 +90,7 @@ def sync_images_background(app, record_id, raw_urls):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ 更新记录失败: {e}")
|
print(f"❌ 更新记录失败: {e}")
|
||||||
|
|
||||||
def process_image_generation(app, user_id, task_id, payload, api_key, target_api, cost):
|
def process_image_generation(app, user_id, task_id, payload, api_key, target_api, cost, use_trial=False):
|
||||||
"""异步执行图片生成并存入 Redis"""
|
"""异步执行图片生成并存入 Redis"""
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
try:
|
try:
|
||||||
@ -99,10 +99,9 @@ def process_image_generation(app, user_id, task_id, payload, api_key, target_api
|
|||||||
resp = requests.post(target_api, json=payload, headers=headers, timeout=1000)
|
resp = requests.post(target_api, json=payload, headers=headers, timeout=1000)
|
||||||
|
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
user = db.session.get(User, user_id)
|
if use_trial:
|
||||||
if user and "sk-" in api_key:
|
from services.generation_service import refund_points
|
||||||
user.points += cost
|
refund_points(user_id, cost)
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# 记录详细的失败上下文
|
# 记录详细的失败上下文
|
||||||
system_logger.error(f"生图任务失败: {resp.text}", user_id=user_id, task_id=task_id, prompt=payload.get('prompt'), model=payload.get('model'))
|
system_logger.error(f"生图任务失败: {resp.text}", user_id=user_id, task_id=task_id, prompt=payload.get('prompt'), model=payload.get('model'))
|
||||||
@ -135,10 +134,9 @@ def process_image_generation(app, user_id, task_id, payload, api_key, target_api
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 异常处理:退还积分
|
# 异常处理:退还积分
|
||||||
user = db.session.get(User, user_id)
|
if use_trial:
|
||||||
if user and "sk-" in api_key:
|
from services.generation_service import refund_points
|
||||||
user.points += cost
|
refund_points(user_id, cost)
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
system_logger.error(f"生图任务异常: {str(e)}", user_id=user_id, task_id=task_id, prompt=payload.get('prompt'), model=payload.get('model'))
|
system_logger.error(f"生图任务异常: {str(e)}", user_id=user_id, task_id=task_id, prompt=payload.get('prompt'), model=payload.get('model'))
|
||||||
redis_client.setex(f"task:{task_id}", 3600, json.dumps({"status": "error", "message": str(e)}))
|
redis_client.setex(f"task:{task_id}", 3600, json.dumps({"status": "error", "message": str(e)}))
|
||||||
@ -203,7 +201,7 @@ def sync_video_background(app, record_id, raw_url, internal_task_id=None):
|
|||||||
except Exception as dbe:
|
except Exception as dbe:
|
||||||
system_logger.error(f"更新视频记录失败: {str(dbe)}")
|
system_logger.error(f"更新视频记录失败: {str(dbe)}")
|
||||||
|
|
||||||
def process_video_generation(app, user_id, internal_task_id, payload, api_key, cost):
|
def process_video_generation(app, user_id, internal_task_id, payload, api_key, cost, use_trial=True):
|
||||||
"""异步提交并查询视频任务状态"""
|
"""异步提交并查询视频任务状态"""
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
try:
|
try:
|
||||||
@ -273,11 +271,10 @@ def process_video_generation(app, user_id, internal_task_id, payload, api_key, c
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
system_logger.error(f"视频生成执行异常: {str(e)}", user_id=user_id, task_id=internal_task_id, prompt=payload.get('prompt'))
|
system_logger.error(f"视频生成执行异常: {str(e)}", user_id=user_id, task_id=internal_task_id, prompt=payload.get('prompt'))
|
||||||
# 尝试退费
|
# 尝试退费
|
||||||
|
if use_trial:
|
||||||
try:
|
try:
|
||||||
user = db.session.get(User, user_id)
|
from services.generation_service import refund_points
|
||||||
if user:
|
refund_points(user_id, cost)
|
||||||
user.points += cost
|
|
||||||
db.session.commit()
|
|
||||||
except Exception as re:
|
except Exception as re:
|
||||||
system_logger.error(f"退费失败: {str(re)}")
|
system_logger.error(f"退费失败: {str(re)}")
|
||||||
|
|
||||||
|
|||||||
@ -195,6 +195,8 @@ async function init() {
|
|||||||
if (modeTrialBtn) modeTrialBtn.onclick = () => switchMode('trial');
|
if (modeTrialBtn) modeTrialBtn.onclick = () => switchMode('trial');
|
||||||
if (modeKeyBtn) modeKeyBtn.onclick = () => switchMode('key');
|
if (modeKeyBtn) modeKeyBtn.onclick = () => switchMode('key');
|
||||||
if (isPremiumCheckbox) isPremiumCheckbox.onchange = () => updateCostPreview();
|
if (isPremiumCheckbox) isPremiumCheckbox.onchange = () => updateCostPreview();
|
||||||
|
const numSelect = document.getElementById('numSelect');
|
||||||
|
if (numSelect) numSelect.onchange = () => updateCostPreview();
|
||||||
|
|
||||||
// 历史记录控制
|
// 历史记录控制
|
||||||
const historyDrawer = document.getElementById('historyDrawer');
|
const historyDrawer = document.getElementById('historyDrawer');
|
||||||
@ -305,13 +307,16 @@ function fillSelect(id, list) {
|
|||||||
function updateCostPreview() {
|
function updateCostPreview() {
|
||||||
const modelSelect = document.getElementById('modelSelect');
|
const modelSelect = document.getElementById('modelSelect');
|
||||||
const costPreview = document.getElementById('costPreview');
|
const costPreview = document.getElementById('costPreview');
|
||||||
|
const numSelect = document.getElementById('numSelect');
|
||||||
const isPremium = document.getElementById('isPremium')?.checked || false;
|
const isPremium = document.getElementById('isPremium')?.checked || false;
|
||||||
const selectedOption = modelSelect.options[modelSelect.selectedIndex];
|
const selectedOption = modelSelect.options[modelSelect.selectedIndex];
|
||||||
|
|
||||||
if (currentMode === 'trial' && selectedOption) {
|
if (currentMode === 'trial' && selectedOption) {
|
||||||
let cost = parseInt(selectedOption.getAttribute('data-cost') || 0);
|
let baseCost = parseInt(selectedOption.getAttribute('data-cost') || 0);
|
||||||
if (isPremium) cost *= 2; // 优质模式 2 倍积分
|
let num = parseInt(numSelect?.value || 1);
|
||||||
costPreview.innerText = `本次生成将消耗 ${cost} 积分`;
|
let totalCost = baseCost * num;
|
||||||
|
if (isPremium) totalCost *= 2; // 优质模式 2 倍积分
|
||||||
|
costPreview.innerText = `本次生成将消耗 ${totalCost} 积分`;
|
||||||
costPreview.classList.remove('hidden');
|
costPreview.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
costPreview.classList.add('hidden');
|
costPreview.classList.add('hidden');
|
||||||
@ -572,6 +577,19 @@ document.getElementById('submitBtn').onclick = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tasks = Array.from({ length: num }, (_, i) => startTask(i));
|
||||||
|
await Promise.all(tasks);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
showToast('创作引擎中断: ' + e.message, 'error');
|
||||||
|
document.getElementById('placeholder').classList.remove('hidden');
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btnText.innerText = "立即生成作品";
|
||||||
|
document.getElementById('statusInfo').classList.add('hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 提取结果展示逻辑
|
// 提取结果展示逻辑
|
||||||
const displayResult = (slot, data) => {
|
const displayResult = (slot, data) => {
|
||||||
if (data.type === 'text') {
|
if (data.type === 'text') {
|
||||||
@ -594,19 +612,6 @@ document.getElementById('submitBtn').onclick = async () => {
|
|||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
};
|
};
|
||||||
|
|
||||||
const tasks = Array.from({ length: num }, (_, i) => startTask(i));
|
|
||||||
await Promise.all(tasks);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
showToast('创作引擎中断: ' + e.message, 'error');
|
|
||||||
document.getElementById('placeholder').classList.remove('hidden');
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false;
|
|
||||||
btnText.innerText = "立即生成作品";
|
|
||||||
document.getElementById('statusInfo').classList.add('hidden');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
// 修改密码弹窗控制
|
// 修改密码弹窗控制
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user