Spaces:
Sleeping
Sleeping
| 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("<h1 id='header-title'>VALORANT Strategy Generator</h1>") | |
| gr.HTML("<div id='subtext'>Dynamic, AI-powered tactics on the fly adapting to any in-game scenario.</div>") | |
| gr.HTML("<div id='subtext'>Use the options below and click 'Generate Strategy'. Note: Results are AI generated and can vary.</div>") | |
| with gr.Column(elem_classes="section"): | |
| gr.HTML('<div class="section-title">Setup & Goals</div>') | |
| 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('<div class="section-title">Agents & Playstyle</div>') | |
| 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) | |