404final / app.py
raymondbrown's picture
switched to env for api key
9e1b1ed
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)