Update GPT and add Profile

This commit is contained in:
2025-12-16 22:12:12 +07:00
parent 08b96fd1f7
commit 696dc714c4
15 changed files with 1063 additions and 90 deletions

View File

@@ -13,101 +13,131 @@ class GPTService:
async def generate_challenges(
self,
game_title: str,
game_genre: str | None = None
) -> list[ChallengeGenerated]:
games: list[dict]
) -> dict[int, list[ChallengeGenerated]]:
"""
Generate challenges for a game using GPT.
Generate challenges for multiple games in one API call.
Args:
game_title: Name of the game
game_genre: Optional genre of the game
games: List of dicts with keys: id, title, genre
Returns:
List of generated challenges
Dict mapping game_id to list of generated challenges
"""
genre_text = f" (жанр: {game_genre})" if game_genre else ""
if not games:
return {}
prompt = f"""Ты — эксперт по видеоиграм. Сгенерируй 6 КОНКРЕТНЫХ челленджей для игры "{game_title}"{genre_text}.
games_text = "\n".join([
f"- {g['title']}" + (f" (жанр: {g['genre']})" if g.get('genre') else "")
for g in games
])
ВАЖНО: Челленджи должны быть СПЕЦИФИЧНЫМИ для этой игры!
prompt = f"""Ты — эксперт по видеоиграм. Сгенерируй по 6 КОНКРЕТНЫХ челленджей для каждой из следующих игр:
{games_text}
ВАЖНО: Челленджи должны быть СПЕЦИФИЧНЫМИ для каждой игры!
- Используй РЕАЛЬНЫЕ названия локаций, боссов, персонажей, миссий, уровней из игры
- Основывайся на том, какие челленджи РЕАЛЬНО делают игроки в этой игре (спидраны, no-hit боссов, сбор коллекционных предметов и т.д.)
- Основывайся на том, какие челленджи РЕАЛЬНО делают игроки в этой игре
- НЕ генерируй абстрактные челленджи типа "пройди уровень" или "убей 10 врагов"
Примеры ХОРОШИХ челленджей:
- Dark Souls: "Победи Орнштейна и Смоуга без призыва" / "Пройди Чумной город без отравления"
- GTA V: "Получи золото в миссии «Ювелирное дело»" / "Выиграй уличную гонку на Vinewood"
- Hollow Knight: "Победи Хорнет без получения урона" / "Найди все грибные споры в Грибных пустошах"
- Minecraft: "Убей Дракона Края за один визит в Энд" / "Построй работающую ферму железа"
Требования по сложности ДЛЯ КАЖДОЙ ИГРЫ:
- 2 лёгких (15-30 мин): простые задачи
- 2 средних (1-2 часа): требуют навыка
- 2 сложных (3+ часа): серьёзный челлендж
Требования по сложности:
- 2 лёгких (15-30 мин): простые задачи, знакомство с игрой
- 2 средних (1-2 часа): требуют навыка или исследования
- 2 сложных (3+ часа): серьёзный челлендж, достижения, полное прохождение
Формат ответа — JSON с объектом где ключи это ТОЧНЫЕ названия игр, как они указаны в запросе:
{{
"Название игры 1": {{
"challenges": [
{{"title": "...", "description": "...", "type": "completion|no_death|speedrun|collection|achievement|challenge_run", "difficulty": "easy|medium|hard", "points": 50, "estimated_time": 30, "proof_type": "screenshot|video|steam", "proof_hint": "..."}}
]
}},
"Название игры 2": {{
"challenges": [...]
}}
}}
Формат ответа — JSON:
- title: название на русском (до 50 символов), конкретное и понятное
- description: что именно сделать (1-2 предложения), с деталями из игры
- type: completion | no_death | speedrun | collection | achievement | challenge_run
- difficulty: easy | medium | hard
- points: easy=20-40, medium=45-75, hard=90-150
- estimated_time: время в минутах
- proof_type: screenshot | video | steam
- proof_hint: ЧТО КОНКРЕТНО должно быть видно на скриншоте/видео (экран победы, достижение, локация и т.д.)
Ответь ТОЛЬКО JSON:
{{"challenges": [{{"title": "...", "description": "...", "type": "...", "difficulty": "...", "points": 50, "estimated_time": 30, "proof_type": "...", "proof_hint": "..."}}]}}"""
points: easy=20-40, medium=45-75, hard=90-150
Ответь ТОЛЬКО JSON."""
response = await self.client.chat.completions.create(
model="gpt-5-mini",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"},
temperature=0.8,
max_tokens=2500,
)
content = response.choices[0].message.content
data = json.loads(content)
challenges = []
for ch in data.get("challenges", []):
# Validate and normalize type
ch_type = ch.get("type", "completion")
if ch_type not in ["completion", "no_death", "speedrun", "collection", "achievement", "challenge_run"]:
ch_type = "completion"
# Map game titles to IDs (case-insensitive, strip whitespace)
title_to_id = {g['title'].lower().strip(): g['id'] for g in games}
# Validate difficulty
difficulty = ch.get("difficulty", "medium")
if difficulty not in ["easy", "medium", "hard"]:
difficulty = "medium"
# Also keep original titles for logging
id_to_title = {g['id']: g['title'] for g in games}
# Validate proof_type
proof_type = ch.get("proof_type", "screenshot")
if proof_type not in ["screenshot", "video", "steam"]:
proof_type = "screenshot"
print(f"[GPT] Requested games: {[g['title'] for g in games]}")
print(f"[GPT] Response keys: {list(data.keys())}")
# Validate points based on difficulty
points = ch.get("points", 30)
if not isinstance(points, int) or points < 1:
points = 30
# Clamp points to expected ranges
if difficulty == "easy":
points = max(20, min(40, points))
elif difficulty == "medium":
points = max(45, min(75, points))
elif difficulty == "hard":
points = max(90, min(150, points))
result = {}
for game_title, game_data in data.items():
# Try exact match first, then case-insensitive
game_id = title_to_id.get(game_title.lower().strip())
challenges.append(ChallengeGenerated(
title=ch.get("title", "Unnamed Challenge")[:100],
description=ch.get("description", "Complete the challenge"),
type=ch_type,
difficulty=difficulty,
points=points,
estimated_time=ch.get("estimated_time"),
proof_type=proof_type,
proof_hint=ch.get("proof_hint"),
))
if not game_id:
# Try partial match if exact match fails
for stored_title, gid in title_to_id.items():
if stored_title in game_title.lower() or game_title.lower() in stored_title:
game_id = gid
break
return challenges
if not game_id:
print(f"[GPT] Could not match game: '{game_title}'")
continue
challenges = []
for ch in game_data.get("challenges", []):
challenges.append(self._parse_challenge(ch))
result[game_id] = challenges
print(f"[GPT] Generated {len(challenges)} challenges for '{id_to_title.get(game_id)}'")
return result
def _parse_challenge(self, ch: dict) -> ChallengeGenerated:
"""Parse and validate a single challenge from GPT response"""
ch_type = ch.get("type", "completion")
if ch_type not in ["completion", "no_death", "speedrun", "collection", "achievement", "challenge_run"]:
ch_type = "completion"
difficulty = ch.get("difficulty", "medium")
if difficulty not in ["easy", "medium", "hard"]:
difficulty = "medium"
proof_type = ch.get("proof_type", "screenshot")
if proof_type not in ["screenshot", "video", "steam"]:
proof_type = "screenshot"
points = ch.get("points", 30)
if not isinstance(points, int) or points < 1:
points = 30
if difficulty == "easy":
points = max(20, min(40, points))
elif difficulty == "medium":
points = max(45, min(75, points))
elif difficulty == "hard":
points = max(90, min(150, points))
return ChallengeGenerated(
title=ch.get("title", "Unnamed Challenge")[:100],
description=ch.get("description", "Complete the challenge"),
type=ch_type,
difficulty=difficulty,
points=points,
estimated_time=ch.get("estimated_time"),
proof_type=proof_type,
proof_hint=ch.get("proof_hint"),
)
gpt_service = GPTService()