import openai import gradio as gr import requests import os #from config import OPENAI_API_KEY # SETUP OPENAI KEY from external file openai.api_key = os.getenv("OPEN_API_KEY") # System prompt to ensure strategy-focused responses STRATEGY_SYSTEM_PROMPT = ( "You are a highly experienced VALORANT strategist. Provide detailed tactical strategies " "focusing on map control, utility usage, timing, and team coordination. Do NOT just list agent abilities; " "focus on actionable plays, specific callouts, and step-by-step plans." ) # API endpoints for agent and map details VALORANT_AGENTS_API = "https://valorant-api.com/v1/agents?isPlayableCharacter=true" VALORANT_MAPS_API = "https://valorant-api.com/v1/maps" # Allowed Valorant maps in your UI ALLOWED_MAPS = [ "Ascent", "Abyss", "Bind", "Breeze", "Fracture", "Haven", "Icebox", "Lotus", "Pearl", "Split", "Sunset" ] # Fetch playable agents at startup (fallback to a minimal list if it fails) try: agents_resp = requests.get(VALORANT_AGENTS_API).json() AGENT_NAMES = [ a["displayName"] for a in agents_resp.get("data", []) if a.get("isPlayableCharacter", False) ] if not AGENT_NAMES: raise ValueError("No agents fetched") except Exception: AGENT_NAMES = [ "Astra", "Breach", "Brimstone", "Chamber", "Clove", "Cypher", "Deadlock", "Fade", "Gekko", "Harbor", "Iso", "Jett", "Kay/o", "Killjoy", "Omen", "Phoenix", "Raze", "Reyna", "Sage", "Sova", "Skye", "Tejo", "Viper", "Waylay", "Yoru", "Vyse" ] # Fetch map data at startup, but only keep the allowed ones try: maps_resp = requests.get(VALORANT_MAPS_API).json().get("data", []) # build a dict of map displayName -> full map object MAP_DATA = { m["displayName"]: m for m in maps_resp if m.get("displayName") in ALLOWED_MAPS } # list of names for the dropdown MAP_NAMES = list(MAP_DATA.keys()) if not MAP_NAMES: raise ValueError("No allowed maps fetched from API") except Exception: # fallback if API fails MAP_DATA = {name: {"callouts": []} for name in ALLOWED_MAPS} MAP_NAMES = ALLOWED_MAPS WIKI_API_URL = "https://en.wikipedia.org/w/api.php" def fetch_agent_wiki(agent_name): """ Fallback: fetch summary from Wikipedia for an agent. """ try: res = requests.get( WIKI_API_URL, params={ "action": "query", "prop": "extracts", "exintro": True, "format": "json", "titles": agent_name } ).json() page = next(iter(res["query"]["pages"].values())) return page.get("extract", "") except Exception: return "" def generate_valorant_strategy(messages, model="gpt-4"): try: res = openai.ChatCompletion.create( model=model, messages=messages, temperature=0.7, max_tokens=700 ) return res.choices[0].message.content.strip() except Exception as e: return f"❌ ERROR generating strategy: {e}" def on_click_strategy(map_choice, side_choice, round_goal, site_choice, economy_choice, playstyle_choice, agent_choice, auto_comp): """ Builds the prompt, fetches agent info (API + fallback), generates a strategy, and validates that any callouts mentioned actually exist on the chosen map. """ # Determine agents if auto_comp: comp_prompt = [ {"role": "system", "content": STRATEGY_SYSTEM_PROMPT}, {"role": "user", "content": ( f"For VALORANT map '{map_choice}' at {site_choice.lower()} site on the " f"{side_choice.lower()} side with {economy_choice.lower()} economy and a round goal of " f"{round_goal}, propose a balanced team of five agents, at least one Controller. " "List only the agent names separated by commas." )} ] comp_res = generate_valorant_strategy(comp_prompt) agent_list = [a.strip() for a in comp_res.split(",")] else: agent_list = agent_choice # Build initial strategy prompt user_content = ( f"On the {side_choice.lower()} side of {map_choice} at {site_choice.lower()} site " f"with a {economy_choice.lower()} economy and a round goal of {round_goal}, " f"generate a {playstyle_choice.lower()} strategy using agents: {', '.join(agent_list)}." ) messages = [ {"role": "system", "content": STRATEGY_SYSTEM_PROMPT}, {"role": "user", "content": user_content} ] # First pass strategy = generate_valorant_strategy(messages) # Validate callouts all_callouts = { callout.get("regionName") for m in MAP_DATA.values() for callout in m.get("callouts", []) if callout.get("regionName") } allowed = { callout.get("regionName") for callout in MAP_DATA.get(map_choice, {}).get("callouts", []) if callout.get("regionName") } used = {c for c in all_callouts if c and c in strategy} invalid = used - allowed if invalid: correction = ( f"Your strategy mentioned callouts not on {map_choice}: {', '.join(invalid)}. " f"Only use these callouts for {map_choice}: {', '.join(sorted(allowed))}. " "Please regenerate the full strategy." ) messages.append({"role": "user", "content": correction}) strategy = generate_valorant_strategy(messages) return strategy css = """ :root { --valorant-white: #ffffff; } /* Default (dark mode) styling */ .gradio-container { background-image: url('https://path.to/valorant-background.jpg'); background-size: cover; background-position: center; padding: 30px; } .gradio-blocks { backdrop-filter: blur(8px); } #header-title { text-align: center; color: var(--valorant-white); font-size: 2.8em; font-weight: 800; margin-bottom: 10px; text-shadow: 3px 3px 10px rgba(0, 0, 0, 0.9); } #subtext { text-align: center; color: var(--valorant-white); font-size: 1em; margin-bottom: 20px; } .section { background-color: rgba(15, 15, 15, 0.85); padding: 20px; border-radius: 8px; margin-bottom: 20px; } .section-title { font-size: 1.2em; font-weight: 700; margin-bottom: 10px; color: var(--valorant-white); } #generate-btn { background-color: #FF4655; color: white; padding: 12px 24px; font-weight: 700; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.6); margin-top: 10px; } #generate-btn:hover { background-color: #E8434C; } /* Light mode overrides */ @media (prefers-color-scheme: light) { #header-title, #subtext, .section-title { color: #000000; text-shadow: 3px 3px 10px rgba(255, 255, 255, 0.7); } .section { background-color: rgba(255, 255, 255, 0.85); } } """ with gr.Blocks(css=css, title="VALORANT Strategy Generator") as ui: gr.HTML("

VALORANT Strategy Generator

") gr.HTML("
Dynamic, AI-powered tactics on the fly adapting to any in-game scenario.
") gr.HTML("
Use the options below and click 'Generate Strategy'. Note: Results are AI generated and can vary.
") with gr.Column(elem_classes="section"): gr.HTML('
Setup & Goals
') with gr.Row(): map_choice = gr.Dropdown( label="Select Map", choices=MAP_NAMES, value=MAP_NAMES[0] ) side_choice = gr.Radio(label="Side", choices=["Attack", "Defense"], value="Attack") round_goal = gr.Radio( label="Round Goal", choices=["Play off bomb", "Play for picks"], value="Play off bomb" ) site_choice = gr.Radio( label="Site", choices=["A Site", "B Site"], value="A Site" ) map_choice.change( fn=lambda m: gr.update( choices=(['A Site', 'B Site', 'C Site'] if m in ['Haven', 'Lotus'] else ['A Site', 'B Site']), value='A Site' ), inputs=[map_choice], outputs=[site_choice] ) economy_choice = gr.Radio( label="Economy Type", choices=["Eco", "Half-buy", "Full-buy"], value="Full-buy" ) auto_comp = gr.Checkbox( label="Auto-generate Team Composition", info="Let the model pick a balanced 5-agent team based on map and site.", value=False ) with gr.Column(elem_classes="section"): gr.HTML('
Agents & Playstyle
') with gr.Row(): playstyle_choice = gr.Radio( label="Preferred Playstyle", choices=["Default", "Aggressive", "Control", "Lurk-heavy", "Execute-heavy"], value="Default" ) agent_choice = gr.CheckboxGroup( label="Select Agents (up to 5)", choices=AGENT_NAMES, value=["Jett", "Phoenix"] ) side_choice.change( fn=lambda side: gr.update( choices=["Play off bomb", "Play for picks"] if side == "Attack" else ["Retake", "Stall the plant", "Run them down"], value=("Play off bomb" if side == "Attack" else "Retake") ), inputs=[side_choice], outputs=[round_goal] ) auto_comp.change( fn=lambda auto: gr.update(visible=False, value=[]) if auto else gr.update(visible=True), inputs=[auto_comp], outputs=[agent_choice] ) strategy_output = gr.Textbox( label="Strategy Output", lines=20, interactive=False, elem_classes="section", show_copy_button=True ) generate_btn = gr.Button("Generate Strategy", elem_id="generate-btn") generate_btn.click( on_click_strategy, inputs=[map_choice, side_choice, round_goal, site_choice, economy_choice, playstyle_choice, agent_choice, auto_comp], outputs=[strategy_output] ) if __name__ == "__main__": ui.launch(share=True)