ly0303521

参数化视频生成次数,修改config.js目录

... ... @@ -11,3 +11,7 @@ PUBLIC_ZIMAGE_PORT="39009"
# Local Ports (Internal Bind)
LOCAL_BACKEND_PORT="7000"
LOCAL_FRONTEND_PORT="7001"
# Business Logic Configuration
VIDEO_GENERATION_LIMIT="1"
LIKES_FOR_REWARD="5"
... ...
... ... @@ -4,9 +4,10 @@ import os
import secrets
import time
import fcntl
import re
from pathlib import Path
from threading import Lock, RLock
from typing import List, Literal, Optional
from typing import List, Literal, Optional, Dict, Any
import httpx
from fastapi import FastAPI, HTTPException, Query
... ... @@ -25,6 +26,34 @@ GALLERY_MAX_ITEMS = int(os.getenv("GALLERY_MAX_ITEMS", "500"))
WHITELIST_PATH = Path(os.getenv("WHITELIST_PATH", Path(__file__).with_name("whitelist.txt")))
ADMIN_ID = "86427531"
# Load dynamic limits from config.js
CONFIG_JS_PATH = Path(__file__).parent.parent / "public" / "config.js"
def load_limits_from_config() -> dict:
defaults = {"VIDEO_GENERATION_LIMIT": 1, "LIKES_FOR_REWARD": 5}
try:
if not CONFIG_JS_PATH.exists():
return defaults
content = CONFIG_JS_PATH.read_text(encoding="utf-8")
# Simple regex to extract values from JS object
# Looking for: VIDEO_GENERATION_LIMIT: 1
limit_match = re.search(r'VIDEO_GENERATION_LIMIT\s*:\s*(\d+)', content)
reward_match = re.search(r'LIKES_FOR_REWARD\s*:\s*(\d+)', content)
if limit_match:
defaults["VIDEO_GENERATION_LIMIT"] = int(limit_match.group(1))
if reward_match:
defaults["LIKES_FOR_REWARD"] = int(reward_match.group(1))
return defaults
except Exception as e:
logger.error(f"Failed to load config.js: {e}")
return defaults
LIMITS = load_limits_from_config()
# --- Usage Store ---
class UsageStore:
def __init__(self, path: Path):
... ... @@ -191,14 +220,41 @@ class JsonStore:
if not target_item: return None
liked_by = target_item.get("likedBy", [])
if not isinstance(liked_by, list): liked_by = []
# --- New Reward Logic ---
# 1. Check current likes BEFORE change
current_likes_count = target_item.get("likes", 0)
author_id = target_item.get("authorId")
is_liked_after = False
if user_id in liked_by:
# UNLIKE
liked_by.remove(user_id)
target_item["likes"] = max(0, target_item.get("likes", 0) - 1)
new_likes_count = max(0, current_likes_count - 1)
is_liked_after = False
else:
# LIKE
liked_by.append(user_id)
target_item["likes"] = target_item.get("likes", 0) + 1
new_likes_count = current_likes_count + 1
is_liked_after = True
target_item["likes"] = new_likes_count
target_item["likedBy"] = liked_by
self._write(data)
# Reward Check: Only reward author when crossing threshold (e.g. 5, 10, 15...)
# We check if the NEW count is a multiple of LIKES_FOR_REWARD and we just increased it.
# (Simple version: Every N likes = 1 generation credit)
if author_id and author_id != "OFFICIAL" and author_id != ADMIN_ID:
limit = LIMITS["LIKES_FOR_REWARD"]
# Only reward on LIKE action, not unlike
if is_liked_after:
# Check if we just hit a multiple of the limit (5, 10, 15...)
if new_likes_count > 0 and new_likes_count % limit == 0:
logger.info(f"User {author_id} reached {new_likes_count} likes! Adding bonus.")
usage_store.update_bonus(author_id, 1)
return target_item
def delete_item(self, item_id: str) -> bool:
... ... @@ -230,7 +286,13 @@ app.add_middleware(
)
@app.on_event("startup")
async def startup(): app.state.http = httpx.AsyncClient(timeout=httpx.Timeout(REQUEST_TIMEOUT_SECONDS, connect=5.0))
async def startup():
# Reload limits on startup to ensure fresh config
global LIMITS
LIMITS = load_limits_from_config()
logger.info(f"Loaded limits: {LIMITS}")
app.state.http = httpx.AsyncClient(timeout=httpx.Timeout(REQUEST_TIMEOUT_SECONDS, connect=5.0))
@app.on_event("shutdown")
async def shutdown(): await app.state.http.aclose()
@app.get("/health")
... ... @@ -244,29 +306,14 @@ async def login(user_id: str = Query(..., alias="userId")):
@app.post("/likes/{item_id}")
async def toggle_like(item_id: str, user_id: str = Query(..., alias="userId")):
is_liked_before = False
items = image_store.list_items()
target_item = next((i for i in items if i.get("id") == item_id), None)
if target_item:
is_liked_before = user_id in target_item.get("likedBy", [])
# Try images first
updated_item = image_store.toggle_like(item_id, user_id)
if updated_item:
is_liked_after = user_id in updated_item.get("likedBy", [])
if is_liked_after and not is_liked_before:
usage_store.update_bonus(user_id, 1)
elif not is_liked_after and is_liked_before:
usage_store.update_bonus(user_id, -1)
return updated_item
if updated_item: return updated_item
# Then videos
updated_item = video_store.toggle_like(item_id, user_id)
if updated_item:
is_liked_after = user_id in updated_item.get("likedBy", [])
if is_liked_after and not is_liked_before:
usage_store.update_bonus(user_id, 1)
elif not is_liked_after and is_liked_before:
usage_store.update_bonus(user_id, -1)
return updated_item
if updated_item: return updated_item
raise HTTPException(status_code=404, detail="Item not found")
@app.get("/usage/{user_id}")
... ... @@ -274,11 +321,17 @@ async def get_user_usage(user_id: str):
try:
usage = usage_store.get_usage(user_id)
is_admin = user_id == ADMIN_ID
remaining = (2 - usage["daily_used"]) + usage["bonus_count"] if not is_admin else 999999
limit = LIMITS["VIDEO_GENERATION_LIMIT"]
# Logic: base_limit - daily_used + bonus
remaining = (limit - usage["daily_used"]) + usage["bonus_count"]
if is_admin: remaining = 999999
return {
"daily_used": usage["daily_used"],
"bonus_count": usage["bonus_count"],
"base_limit": 2,
"base_limit": limit,
"remaining": max(0, remaining),
"is_admin": is_admin
}
... ... @@ -287,8 +340,8 @@ async def get_user_usage(user_id: str):
return {
"daily_used": 0,
"bonus_count": 0,
"base_limit": 2,
"remaining": 2,
"base_limit": LIMITS["VIDEO_GENERATION_LIMIT"],
"remaining": LIMITS["VIDEO_GENERATION_LIMIT"],
"is_admin": user_id == ADMIN_ID
}
... ... @@ -371,4 +424,4 @@ async def remove_whitelist(user_id: str) -> dict:
# Redirect old /gallery to /gallery/images for backward compatibility
@app.get("/gallery")
async def gallery(limit: int = Query(200, ge=1, le=1000), author_id: Optional[str] = Query(None, alias="authorId")):
return await gallery_images(limit=limit, author_id=author_id)
\ No newline at end of file
return await gallery_images(limit=limit, author_id=author_id)
... ...
... ... @@ -2,5 +2,7 @@ window.APP_CONFIG = {
Z_IMAGE_DIRECT_BASE_URL: "http://106.120.52.146:39009",
TURBO_DIFFUSION_API_URL: "http://106.120.52.146:37002",
VIDEO_OSS_BASE_URL: "http://106.120.52.146:34000",
API_BASE_URL: "http://106.120.52.146:37000"
API_BASE_URL: "http://106.120.52.146:37000",
VIDEO_GENERATION_LIMIT: 1,
LIKES_FOR_REWARD: 5
};
... ...
... ... @@ -12,7 +12,7 @@ fi
FRONTEND_DIR="$BASE_DIR/z-image-generator"
BACKEND_DIR="$BASE_DIR"
CONSTANTS_FILE="$FRONTEND_DIR/constants.ts"
CONFIG_JS_FILE="$FRONTEND_DIR/public/config.js"
CONFIG_JS_FILE="$BASE_DIR/public/config.js"
LOGS_DIR="$BASE_DIR/logs"
# Ensure logs directory exists
... ... @@ -45,7 +45,9 @@ window.APP_CONFIG = {
Z_IMAGE_DIRECT_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_ZIMAGE_PORT",
TURBO_DIFFUSION_API_URL: "http://$PUBLIC_IP:$PUBLIC_TURBO_PORT",
VIDEO_OSS_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_OSS_PORT",
API_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_BACKEND_PORT"
API_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_BACKEND_PORT",
VIDEO_GENERATION_LIMIT: ${VIDEO_GENERATION_LIMIT:-1},
LIKES_FOR_REWARD: ${LIKES_FOR_REWARD:-5}
};
EOF
... ...
... ... @@ -9,6 +9,7 @@ export default defineConfig(({ mode }) => {
port: 3000,
host: '0.0.0.0',
},
publicDir: '../public',
plugins: [react()],
define: {
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
... ...