ly0303521

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

@@ -11,3 +11,7 @@ PUBLIC_ZIMAGE_PORT="39009" @@ -11,3 +11,7 @@ PUBLIC_ZIMAGE_PORT="39009"
11 # Local Ports (Internal Bind) 11 # Local Ports (Internal Bind)
12 LOCAL_BACKEND_PORT="7000" 12 LOCAL_BACKEND_PORT="7000"
13 LOCAL_FRONTEND_PORT="7001" 13 LOCAL_FRONTEND_PORT="7001"
  14 +
  15 +# Business Logic Configuration
  16 +VIDEO_GENERATION_LIMIT="1"
  17 +LIKES_FOR_REWARD="5"
@@ -4,9 +4,10 @@ import os @@ -4,9 +4,10 @@ import os
4 import secrets 4 import secrets
5 import time 5 import time
6 import fcntl 6 import fcntl
  7 +import re
7 from pathlib import Path 8 from pathlib import Path
8 from threading import Lock, RLock 9 from threading import Lock, RLock
9 -from typing import List, Literal, Optional 10 +from typing import List, Literal, Optional, Dict, Any
10 11
11 import httpx 12 import httpx
12 from fastapi import FastAPI, HTTPException, Query 13 from fastapi import FastAPI, HTTPException, Query
@@ -25,6 +26,34 @@ GALLERY_MAX_ITEMS = int(os.getenv("GALLERY_MAX_ITEMS", "500")) @@ -25,6 +26,34 @@ GALLERY_MAX_ITEMS = int(os.getenv("GALLERY_MAX_ITEMS", "500"))
25 WHITELIST_PATH = Path(os.getenv("WHITELIST_PATH", Path(__file__).with_name("whitelist.txt"))) 26 WHITELIST_PATH = Path(os.getenv("WHITELIST_PATH", Path(__file__).with_name("whitelist.txt")))
26 ADMIN_ID = "86427531" 27 ADMIN_ID = "86427531"
27 28
  29 +# Load dynamic limits from config.js
  30 +CONFIG_JS_PATH = Path(__file__).parent.parent / "public" / "config.js"
  31 +
  32 +def load_limits_from_config() -> dict:
  33 + defaults = {"VIDEO_GENERATION_LIMIT": 1, "LIKES_FOR_REWARD": 5}
  34 + try:
  35 + if not CONFIG_JS_PATH.exists():
  36 + return defaults
  37 +
  38 + content = CONFIG_JS_PATH.read_text(encoding="utf-8")
  39 +
  40 + # Simple regex to extract values from JS object
  41 + # Looking for: VIDEO_GENERATION_LIMIT: 1
  42 + limit_match = re.search(r'VIDEO_GENERATION_LIMIT\s*:\s*(\d+)', content)
  43 + reward_match = re.search(r'LIKES_FOR_REWARD\s*:\s*(\d+)', content)
  44 +
  45 + if limit_match:
  46 + defaults["VIDEO_GENERATION_LIMIT"] = int(limit_match.group(1))
  47 + if reward_match:
  48 + defaults["LIKES_FOR_REWARD"] = int(reward_match.group(1))
  49 +
  50 + return defaults
  51 + except Exception as e:
  52 + logger.error(f"Failed to load config.js: {e}")
  53 + return defaults
  54 +
  55 +LIMITS = load_limits_from_config()
  56 +
28 # --- Usage Store --- 57 # --- Usage Store ---
29 class UsageStore: 58 class UsageStore:
30 def __init__(self, path: Path): 59 def __init__(self, path: Path):
@@ -191,14 +220,41 @@ class JsonStore: @@ -191,14 +220,41 @@ class JsonStore:
191 if not target_item: return None 220 if not target_item: return None
192 liked_by = target_item.get("likedBy", []) 221 liked_by = target_item.get("likedBy", [])
193 if not isinstance(liked_by, list): liked_by = [] 222 if not isinstance(liked_by, list): liked_by = []
  223 +
  224 + # --- New Reward Logic ---
  225 + # 1. Check current likes BEFORE change
  226 + current_likes_count = target_item.get("likes", 0)
  227 + author_id = target_item.get("authorId")
  228 +
  229 + is_liked_after = False
  230 +
194 if user_id in liked_by: 231 if user_id in liked_by:
  232 + # UNLIKE
195 liked_by.remove(user_id) 233 liked_by.remove(user_id)
196 - target_item["likes"] = max(0, target_item.get("likes", 0) - 1) 234 + new_likes_count = max(0, current_likes_count - 1)
  235 + is_liked_after = False
197 else: 236 else:
  237 + # LIKE
198 liked_by.append(user_id) 238 liked_by.append(user_id)
199 - target_item["likes"] = target_item.get("likes", 0) + 1 239 + new_likes_count = current_likes_count + 1
  240 + is_liked_after = True
  241 +
  242 + target_item["likes"] = new_likes_count
200 target_item["likedBy"] = liked_by 243 target_item["likedBy"] = liked_by
201 self._write(data) 244 self._write(data)
  245 +
  246 + # Reward Check: Only reward author when crossing threshold (e.g. 5, 10, 15...)
  247 + # We check if the NEW count is a multiple of LIKES_FOR_REWARD and we just increased it.
  248 + # (Simple version: Every N likes = 1 generation credit)
  249 + if author_id and author_id != "OFFICIAL" and author_id != ADMIN_ID:
  250 + limit = LIMITS["LIKES_FOR_REWARD"]
  251 + # Only reward on LIKE action, not unlike
  252 + if is_liked_after:
  253 + # Check if we just hit a multiple of the limit (5, 10, 15...)
  254 + if new_likes_count > 0 and new_likes_count % limit == 0:
  255 + logger.info(f"User {author_id} reached {new_likes_count} likes! Adding bonus.")
  256 + usage_store.update_bonus(author_id, 1)
  257 +
202 return target_item 258 return target_item
203 259
204 def delete_item(self, item_id: str) -> bool: 260 def delete_item(self, item_id: str) -> bool:
@@ -230,7 +286,13 @@ app.add_middleware( @@ -230,7 +286,13 @@ app.add_middleware(
230 ) 286 )
231 @app.on_event("startup") 287 @app.on_event("startup")
232 288
233 -async def startup(): app.state.http = httpx.AsyncClient(timeout=httpx.Timeout(REQUEST_TIMEOUT_SECONDS, connect=5.0)) 289 +async def startup():
  290 + # Reload limits on startup to ensure fresh config
  291 + global LIMITS
  292 + LIMITS = load_limits_from_config()
  293 + logger.info(f"Loaded limits: {LIMITS}")
  294 + app.state.http = httpx.AsyncClient(timeout=httpx.Timeout(REQUEST_TIMEOUT_SECONDS, connect=5.0))
  295 +
234 @app.on_event("shutdown") 296 @app.on_event("shutdown")
235 async def shutdown(): await app.state.http.aclose() 297 async def shutdown(): await app.state.http.aclose()
236 @app.get("/health") 298 @app.get("/health")
@@ -244,29 +306,14 @@ async def login(user_id: str = Query(..., alias="userId")): @@ -244,29 +306,14 @@ async def login(user_id: str = Query(..., alias="userId")):
244 306
245 @app.post("/likes/{item_id}") 307 @app.post("/likes/{item_id}")
246 async def toggle_like(item_id: str, user_id: str = Query(..., alias="userId")): 308 async def toggle_like(item_id: str, user_id: str = Query(..., alias="userId")):
247 - is_liked_before = False  
248 - items = image_store.list_items()  
249 - target_item = next((i for i in items if i.get("id") == item_id), None)  
250 - if target_item:  
251 - is_liked_before = user_id in target_item.get("likedBy", [])  
252 - 309 + # Try images first
253 updated_item = image_store.toggle_like(item_id, user_id) 310 updated_item = image_store.toggle_like(item_id, user_id)
254 - if updated_item:  
255 - is_liked_after = user_id in updated_item.get("likedBy", [])  
256 - if is_liked_after and not is_liked_before:  
257 - usage_store.update_bonus(user_id, 1)  
258 - elif not is_liked_after and is_liked_before:  
259 - usage_store.update_bonus(user_id, -1)  
260 - return updated_item 311 + if updated_item: return updated_item
261 312
  313 + # Then videos
262 updated_item = video_store.toggle_like(item_id, user_id) 314 updated_item = video_store.toggle_like(item_id, user_id)
263 - if updated_item:  
264 - is_liked_after = user_id in updated_item.get("likedBy", [])  
265 - if is_liked_after and not is_liked_before:  
266 - usage_store.update_bonus(user_id, 1)  
267 - elif not is_liked_after and is_liked_before:  
268 - usage_store.update_bonus(user_id, -1)  
269 - return updated_item 315 + if updated_item: return updated_item
  316 +
270 raise HTTPException(status_code=404, detail="Item not found") 317 raise HTTPException(status_code=404, detail="Item not found")
271 318
272 @app.get("/usage/{user_id}") 319 @app.get("/usage/{user_id}")
@@ -274,11 +321,17 @@ async def get_user_usage(user_id: str): @@ -274,11 +321,17 @@ async def get_user_usage(user_id: str):
274 try: 321 try:
275 usage = usage_store.get_usage(user_id) 322 usage = usage_store.get_usage(user_id)
276 is_admin = user_id == ADMIN_ID 323 is_admin = user_id == ADMIN_ID
277 - remaining = (2 - usage["daily_used"]) + usage["bonus_count"] if not is_admin else 999999 324 +
  325 + limit = LIMITS["VIDEO_GENERATION_LIMIT"]
  326 +
  327 + # Logic: base_limit - daily_used + bonus
  328 + remaining = (limit - usage["daily_used"]) + usage["bonus_count"]
  329 + if is_admin: remaining = 999999
  330 +
278 return { 331 return {
279 "daily_used": usage["daily_used"], 332 "daily_used": usage["daily_used"],
280 "bonus_count": usage["bonus_count"], 333 "bonus_count": usage["bonus_count"],
281 - "base_limit": 2, 334 + "base_limit": limit,
282 "remaining": max(0, remaining), 335 "remaining": max(0, remaining),
283 "is_admin": is_admin 336 "is_admin": is_admin
284 } 337 }
@@ -287,8 +340,8 @@ async def get_user_usage(user_id: str): @@ -287,8 +340,8 @@ async def get_user_usage(user_id: str):
287 return { 340 return {
288 "daily_used": 0, 341 "daily_used": 0,
289 "bonus_count": 0, 342 "bonus_count": 0,
290 - "base_limit": 2,  
291 - "remaining": 2, 343 + "base_limit": LIMITS["VIDEO_GENERATION_LIMIT"],
  344 + "remaining": LIMITS["VIDEO_GENERATION_LIMIT"],
292 "is_admin": user_id == ADMIN_ID 345 "is_admin": user_id == ADMIN_ID
293 } 346 }
294 347
@@ -2,5 +2,7 @@ window.APP_CONFIG = { @@ -2,5 +2,7 @@ window.APP_CONFIG = {
2 Z_IMAGE_DIRECT_BASE_URL: "http://106.120.52.146:39009", 2 Z_IMAGE_DIRECT_BASE_URL: "http://106.120.52.146:39009",
3 TURBO_DIFFUSION_API_URL: "http://106.120.52.146:37002", 3 TURBO_DIFFUSION_API_URL: "http://106.120.52.146:37002",
4 VIDEO_OSS_BASE_URL: "http://106.120.52.146:34000", 4 VIDEO_OSS_BASE_URL: "http://106.120.52.146:34000",
5 - API_BASE_URL: "http://106.120.52.146:37000" 5 + API_BASE_URL: "http://106.120.52.146:37000",
  6 + VIDEO_GENERATION_LIMIT: 1,
  7 + LIKES_FOR_REWARD: 5
6 }; 8 };
@@ -12,7 +12,7 @@ fi @@ -12,7 +12,7 @@ fi
12 FRONTEND_DIR="$BASE_DIR/z-image-generator" 12 FRONTEND_DIR="$BASE_DIR/z-image-generator"
13 BACKEND_DIR="$BASE_DIR" 13 BACKEND_DIR="$BASE_DIR"
14 CONSTANTS_FILE="$FRONTEND_DIR/constants.ts" 14 CONSTANTS_FILE="$FRONTEND_DIR/constants.ts"
15 -CONFIG_JS_FILE="$FRONTEND_DIR/public/config.js" 15 +CONFIG_JS_FILE="$BASE_DIR/public/config.js"
16 LOGS_DIR="$BASE_DIR/logs" 16 LOGS_DIR="$BASE_DIR/logs"
17 17
18 # Ensure logs directory exists 18 # Ensure logs directory exists
@@ -45,7 +45,9 @@ window.APP_CONFIG = { @@ -45,7 +45,9 @@ window.APP_CONFIG = {
45 Z_IMAGE_DIRECT_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_ZIMAGE_PORT", 45 Z_IMAGE_DIRECT_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_ZIMAGE_PORT",
46 TURBO_DIFFUSION_API_URL: "http://$PUBLIC_IP:$PUBLIC_TURBO_PORT", 46 TURBO_DIFFUSION_API_URL: "http://$PUBLIC_IP:$PUBLIC_TURBO_PORT",
47 VIDEO_OSS_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_OSS_PORT", 47 VIDEO_OSS_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_OSS_PORT",
48 - API_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_BACKEND_PORT" 48 + API_BASE_URL: "http://$PUBLIC_IP:$PUBLIC_BACKEND_PORT",
  49 + VIDEO_GENERATION_LIMIT: ${VIDEO_GENERATION_LIMIT:-1},
  50 + LIKES_FOR_REWARD: ${LIKES_FOR_REWARD:-5}
49 }; 51 };
50 EOF 52 EOF
51 53
@@ -9,6 +9,7 @@ export default defineConfig(({ mode }) => { @@ -9,6 +9,7 @@ export default defineConfig(({ mode }) => {
9 port: 3000, 9 port: 3000,
10 host: '0.0.0.0', 10 host: '0.0.0.0',
11 }, 11 },
  12 + publicDir: '../public',
12 plugins: [react()], 13 plugins: [react()],
13 define: { 14 define: {
14 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), 15 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),