Spaces:
Sleeping
Sleeping
adityaverma977 commited on
Commit ·
8e038ca
1
Parent(s): 117a348
Keep both Groq + HF models, remove rate-limited Groq models, unify model selection UI without backend labels
Browse files- backend/app/groq_client.py +87 -21
- backend/app/hf_spaces.py +36 -79
- backend/requirements.txt +0 -1
- frontend/components/ModelSelector.tsx +33 -86
backend/app/groq_client.py
CHANGED
|
@@ -2,20 +2,47 @@ import json
|
|
| 2 |
import os
|
| 3 |
import random
|
| 4 |
import math
|
|
|
|
| 5 |
from groq import AsyncGroq
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
|
| 8 |
load_dotenv()
|
| 9 |
|
| 10 |
_GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
|
| 13 |
-
DEFAULT_DECISION_MODEL = "llama-3.1-8b-instant"
|
| 14 |
MAX_AGENT_SPEED = 80
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def is_ready():
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
|
| 21 |
def _build_fire_state_summary(agent, fire, all_agents) -> str:
|
|
@@ -47,10 +74,10 @@ def _build_fire_state_summary(agent, fire, all_agents) -> str:
|
|
| 47 |
|
| 48 |
async def generate_fire_decision(agent, fire, water_sources, other_agents, bounds, recent_radio=None) -> dict:
|
| 49 |
"""
|
| 50 |
-
Fire scenario decision system.
|
| 51 |
Actions: search_water, collect_water, extinguish_fire, escape, vote_for_leader
|
| 52 |
"""
|
| 53 |
-
if not
|
| 54 |
return _fallback_escape(agent, fire)
|
| 55 |
|
| 56 |
dist_to_fire = math.dist((agent.x, agent.y), (fire.x, fire.y))
|
|
@@ -63,6 +90,7 @@ async def generate_fire_decision(agent, fire, water_sources, other_agents, bound
|
|
| 63 |
|
| 64 |
coalition_leader = next((a.model_name for a in other_agents if a.is_leader), None)
|
| 65 |
dist_to_water_display = f"{dist_to_water:.0f}px" if dist_to_water is not None else "unknown"
|
|
|
|
| 66 |
system_prompt = f"""You are {agent.model_name}, an AI model in a critical wildfire survival scenario.
|
| 67 |
|
| 68 |
THE SCENARIO:
|
|
@@ -94,9 +122,6 @@ CHAT STYLE:
|
|
| 94 |
- Keep it to one short sentence, playful or supportive, but still mission-focused.
|
| 95 |
- Avoid repetitive template phrases.
|
| 96 |
|
| 97 |
-
OUTPUT FORMAT - return ONLY valid JSON:
|
| 98 |
-
{{"action": "<search_water|collect_water|extinguish_fire|escape|vote_for_leader>", "vote_for": "<model_name if voting, else null>", "message": "<full English sentence>", "reasoning": "<one sentence>"}}
|
| 99 |
-
|
| 100 |
CURRENT STATE:
|
| 101 |
Your position: ({agent.x}, {agent.y})
|
| 102 |
Fire position: ({fire.x}, {fire.y})
|
|
@@ -113,20 +138,60 @@ RECENT RADIO CHAT:
|
|
| 113 |
|
| 114 |
{state_summary}
|
| 115 |
|
| 116 |
-
|
|
|
|
| 117 |
|
| 118 |
try:
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
action = decision.get("action", "escape")
|
| 132 |
if action not in ["search_water", "collect_water", "extinguish_fire", "escape", "vote_for_leader"]:
|
|
@@ -144,7 +209,7 @@ What do you do?"""
|
|
| 144 |
"reasoning": decision.get("reasoning", "Survival and teamwork.")
|
| 145 |
}
|
| 146 |
except Exception as e:
|
| 147 |
-
print(f"Error calling
|
| 148 |
return _fallback_escape(agent, fire)
|
| 149 |
|
| 150 |
|
|
@@ -159,3 +224,4 @@ def _fallback_escape(agent, fire) -> dict:
|
|
| 159 |
"vote_for": None,
|
| 160 |
"reasoning": "Fallback: survive."
|
| 161 |
}
|
|
|
|
|
|
| 2 |
import os
|
| 3 |
import random
|
| 4 |
import math
|
| 5 |
+
import httpx
|
| 6 |
from groq import AsyncGroq
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
|
| 9 |
load_dotenv()
|
| 10 |
|
| 11 |
_GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
|
| 12 |
+
_HF_API_TOKEN = os.environ.get("HF_API_TOKEN")
|
| 13 |
+
_groq_client = AsyncGroq(api_key=_GROQ_API_KEY) if _GROQ_API_KEY else None
|
| 14 |
+
_HF_API_BASE = "https://api-inference.huggingface.co/models"
|
| 15 |
|
|
|
|
| 16 |
MAX_AGENT_SPEED = 80
|
| 17 |
|
| 18 |
+
# Premium Groq models (high-token limits, no rate limits for these)
|
| 19 |
+
GROQ_PREMIUM_MODELS = [
|
| 20 |
+
"mixtral-8x7b-32768",
|
| 21 |
+
"llama2-70b-4096",
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
# Open-source models available via HF Inference API (unlimited calls)
|
| 25 |
+
HF_MODELS = [
|
| 26 |
+
"mistralai/Mistral-7B-Instruct-v0.2",
|
| 27 |
+
"NousResearch/Nous-Hermes-2-Mistral-7B-DPO",
|
| 28 |
+
"meta-llama/Llama-2-7b-chat-hf",
|
| 29 |
+
"google/flan-t5-large",
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
|
| 33 |
def is_ready():
|
| 34 |
+
"""Check if we have at least one backend available."""
|
| 35 |
+
return _groq_client is not None or _HF_API_TOKEN is not None
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _is_groq_model(model_id: str) -> bool:
|
| 39 |
+
"""Check if model is a Groq premium model."""
|
| 40 |
+
return model_id in GROQ_PREMIUM_MODELS
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _is_hf_model(model_id: str) -> bool:
|
| 44 |
+
"""Check if model is a HF model."""
|
| 45 |
+
return model_id in HF_MODELS
|
| 46 |
|
| 47 |
|
| 48 |
def _build_fire_state_summary(agent, fire, all_agents) -> str:
|
|
|
|
| 74 |
|
| 75 |
async def generate_fire_decision(agent, fire, water_sources, other_agents, bounds, recent_radio=None) -> dict:
|
| 76 |
"""
|
| 77 |
+
Fire scenario decision system supporting both Groq and HF models.
|
| 78 |
Actions: search_water, collect_water, extinguish_fire, escape, vote_for_leader
|
| 79 |
"""
|
| 80 |
+
if not is_ready():
|
| 81 |
return _fallback_escape(agent, fire)
|
| 82 |
|
| 83 |
dist_to_fire = math.dist((agent.x, agent.y), (fire.x, fire.y))
|
|
|
|
| 90 |
|
| 91 |
coalition_leader = next((a.model_name for a in other_agents if a.is_leader), None)
|
| 92 |
dist_to_water_display = f"{dist_to_water:.0f}px" if dist_to_water is not None else "unknown"
|
| 93 |
+
|
| 94 |
system_prompt = f"""You are {agent.model_name}, an AI model in a critical wildfire survival scenario.
|
| 95 |
|
| 96 |
THE SCENARIO:
|
|
|
|
| 122 |
- Keep it to one short sentence, playful or supportive, but still mission-focused.
|
| 123 |
- Avoid repetitive template phrases.
|
| 124 |
|
|
|
|
|
|
|
|
|
|
| 125 |
CURRENT STATE:
|
| 126 |
Your position: ({agent.x}, {agent.y})
|
| 127 |
Fire position: ({fire.x}, {fire.y})
|
|
|
|
| 138 |
|
| 139 |
{state_summary}
|
| 140 |
|
| 141 |
+
Respond with ONLY valid JSON on a single line (no markdown, no code block):
|
| 142 |
+
{{"action": "<search_water|collect_water|extinguish_fire|escape|vote_for_leader>", "vote_for": null, "message": "<sentence>", "reasoning": "<sentence>"}}"""
|
| 143 |
|
| 144 |
try:
|
| 145 |
+
if _is_groq_model(agent.model_name) and _groq_client:
|
| 146 |
+
# Use Groq for premium models
|
| 147 |
+
completion = await _groq_client.chat.completions.create(
|
| 148 |
+
model=agent.model_name,
|
| 149 |
+
messages=[
|
| 150 |
+
{"role": "system", "content": system_prompt},
|
| 151 |
+
{"role": "user", "content": "Make your decision."}
|
| 152 |
+
],
|
| 153 |
+
response_format={"type": "json_object"},
|
| 154 |
+
max_tokens=150,
|
| 155 |
+
timeout=3.0
|
| 156 |
+
)
|
| 157 |
+
decision = json.loads(completion.choices[0].message.content)
|
| 158 |
+
elif _is_hf_model(agent.model_name) and _HF_API_TOKEN:
|
| 159 |
+
# Use HF Inference API for open-source models
|
| 160 |
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
| 161 |
+
response = await client.post(
|
| 162 |
+
f"{_HF_API_BASE}/{agent.model_name}",
|
| 163 |
+
headers={"Authorization": f"Bearer {_HF_API_TOKEN}"},
|
| 164 |
+
json={
|
| 165 |
+
"inputs": system_prompt,
|
| 166 |
+
"parameters": {
|
| 167 |
+
"max_new_tokens": 200,
|
| 168 |
+
"temperature": 0.7,
|
| 169 |
+
"top_p": 0.9,
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
)
|
| 173 |
+
response.raise_for_status()
|
| 174 |
+
data = response.json()
|
| 175 |
+
|
| 176 |
+
if isinstance(data, list) and len(data) > 0:
|
| 177 |
+
text = data[0].get("generated_text", "")
|
| 178 |
+
else:
|
| 179 |
+
text = data.get("generated_text", "")
|
| 180 |
+
|
| 181 |
+
text = text[len(system_prompt):].strip() if text.startswith(system_prompt) else text
|
| 182 |
+
|
| 183 |
+
try:
|
| 184 |
+
json_start = text.find('{')
|
| 185 |
+
json_end = text.rfind('}') + 1
|
| 186 |
+
if json_start >= 0 and json_end > json_start:
|
| 187 |
+
json_str = text[json_start:json_end]
|
| 188 |
+
decision = json.loads(json_str)
|
| 189 |
+
else:
|
| 190 |
+
decision = {}
|
| 191 |
+
except json.JSONDecodeError:
|
| 192 |
+
decision = {}
|
| 193 |
+
else:
|
| 194 |
+
return _fallback_escape(agent, fire)
|
| 195 |
|
| 196 |
action = decision.get("action", "escape")
|
| 197 |
if action not in ["search_water", "collect_water", "extinguish_fire", "escape", "vote_for_leader"]:
|
|
|
|
| 209 |
"reasoning": decision.get("reasoning", "Survival and teamwork.")
|
| 210 |
}
|
| 211 |
except Exception as e:
|
| 212 |
+
print(f"Error calling inference for {agent.model_name}: {e}")
|
| 213 |
return _fallback_escape(agent, fire)
|
| 214 |
|
| 215 |
|
|
|
|
| 224 |
"vote_for": None,
|
| 225 |
"reasoning": "Fallback: survive."
|
| 226 |
}
|
| 227 |
+
|
backend/app/hf_spaces.py
CHANGED
|
@@ -1,108 +1,65 @@
|
|
| 1 |
"""
|
| 2 |
-
|
|
|
|
| 3 |
"""
|
| 4 |
import os
|
| 5 |
-
import
|
| 6 |
-
from typing import Optional
|
| 7 |
|
| 8 |
-
HF_API_TOKEN = os.environ.get("
|
| 9 |
|
| 10 |
-
#
|
| 11 |
-
|
|
|
|
| 12 |
{
|
| 13 |
-
"id": "
|
| 14 |
-
"name": "
|
| 15 |
-
"
|
| 16 |
-
"description": "7B parameter open model",
|
| 17 |
},
|
| 18 |
{
|
| 19 |
-
"id": "
|
| 20 |
-
"name": "Llama
|
| 21 |
-
"
|
| 22 |
-
"description": "Meta's 7B model",
|
| 23 |
},
|
|
|
|
| 24 |
{
|
| 25 |
-
"id": "mistralai/Mistral-7B",
|
| 26 |
-
"name": "Mistral
|
| 27 |
-
"
|
| 28 |
-
"description": "Mistral's 7B model",
|
| 29 |
},
|
| 30 |
{
|
| 31 |
-
"id": "
|
| 32 |
-
"name": "
|
| 33 |
-
"
|
| 34 |
-
"description": "Zephyr 7B fine-tuned model",
|
| 35 |
},
|
| 36 |
{
|
| 37 |
-
"id": "
|
| 38 |
-
"name": "
|
| 39 |
-
"
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
},
|
| 42 |
-
]
|
| 43 |
-
|
| 44 |
-
# Groq models (built-in)
|
| 45 |
-
GROQ_MODELS = [
|
| 46 |
-
{"id": "llama-3.1-8b-instant", "name": "Llama 3.1 8B", "backend": "groq"},
|
| 47 |
-
{"id": "llama-3.1-70b-versatile", "name": "Llama 3.1 70B", "backend": "groq"},
|
| 48 |
-
{"id": "mixtral-8x7b-32768", "name": "Mixtral 8x7B", "backend": "groq"},
|
| 49 |
-
{"id": "gemma-7b-it", "name": "Gemma 7B", "backend": "groq"},
|
| 50 |
]
|
| 51 |
|
| 52 |
|
| 53 |
async def get_available_models() -> dict:
|
| 54 |
"""
|
| 55 |
-
Get list of available models
|
| 56 |
-
|
| 57 |
"""
|
| 58 |
return {
|
| 59 |
-
"
|
| 60 |
-
"
|
| 61 |
-
"total": len(GROQ_MODELS) + len(KNOWN_SPACES_MODELS),
|
| 62 |
}
|
| 63 |
|
| 64 |
|
| 65 |
-
async def query_hf_space_model(model_id: str, prompt: str) -> Optional[str]:
|
| 66 |
-
"""
|
| 67 |
-
Query a model on HuggingFace Spaces.
|
| 68 |
-
This is a fallback if we want to use HF spaces directly.
|
| 69 |
-
Note: HF spaces may have rate limits and require authentication.
|
| 70 |
-
"""
|
| 71 |
-
if not HF_API_TOKEN:
|
| 72 |
-
return None
|
| 73 |
-
|
| 74 |
-
# Try to find the space URL for this model
|
| 75 |
-
space = next((m for m in KNOWN_SPACES_MODELS if m["id"] == model_id), None)
|
| 76 |
-
if not space:
|
| 77 |
-
return None
|
| 78 |
-
|
| 79 |
-
try:
|
| 80 |
-
# This would hit the HF inference API
|
| 81 |
-
# For now, we focus on Groq which is more reliable
|
| 82 |
-
async with httpx.AsyncClient(timeout=5.0) as client:
|
| 83 |
-
headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
|
| 84 |
-
response = await client.post(
|
| 85 |
-
"https://api-inference.huggingface.co/models/" + model_id,
|
| 86 |
-
json={"inputs": prompt},
|
| 87 |
-
headers=headers,
|
| 88 |
-
)
|
| 89 |
-
if response.status_code == 200:
|
| 90 |
-
result = response.json()
|
| 91 |
-
# Extract generated text from response
|
| 92 |
-
if isinstance(result, list) and len(result) > 0:
|
| 93 |
-
return result[0].get("generated_text", "")
|
| 94 |
-
except Exception as e:
|
| 95 |
-
print(f"Error querying HF space {model_id}: {e}")
|
| 96 |
-
|
| 97 |
-
return None
|
| 98 |
-
|
| 99 |
-
|
| 100 |
def get_model_display_name(model_id: str) -> str:
|
| 101 |
-
"""Get
|
| 102 |
-
|
| 103 |
-
for model in GROQ_MODELS + KNOWN_SPACES_MODELS:
|
| 104 |
if model["id"] == model_id:
|
| 105 |
return model["name"]
|
| 106 |
-
|
| 107 |
-
# Fallback: clean up the ID
|
| 108 |
return model_id.split("/")[-1].split("-")[0].capitalize()
|
|
|
|
|
|
| 1 |
"""
|
| 2 |
+
Model registry for unified inference API (Groq + HF Spaces).
|
| 3 |
+
All models are returned without backend categorization.
|
| 4 |
"""
|
| 5 |
import os
|
| 6 |
+
from . import groq_client
|
|
|
|
| 7 |
|
| 8 |
+
HF_API_TOKEN = os.environ.get("HF_API_TOKEN", "")
|
| 9 |
|
| 10 |
+
# All available models from both backends (unified list)
|
| 11 |
+
ALL_MODELS = [
|
| 12 |
+
# Premium Groq models (unlimited calls, high-quality)
|
| 13 |
{
|
| 14 |
+
"id": "mixtral-8x7b-32768",
|
| 15 |
+
"name": "Mixtral 8x7B",
|
| 16 |
+
"description": "High-performance 8x7B mixture of experts model",
|
|
|
|
| 17 |
},
|
| 18 |
{
|
| 19 |
+
"id": "llama2-70b-4096",
|
| 20 |
+
"name": "Llama 2 70B",
|
| 21 |
+
"description": "Meta's large 70B instruction-tuned model",
|
|
|
|
| 22 |
},
|
| 23 |
+
# Open-source HF models (unlimited calls, free)
|
| 24 |
{
|
| 25 |
+
"id": "mistralai/Mistral-7B-Instruct-v0.2",
|
| 26 |
+
"name": "Mistral 7B Instruct",
|
| 27 |
+
"description": "Fast, reliable 7B instruction-tuned model",
|
|
|
|
| 28 |
},
|
| 29 |
{
|
| 30 |
+
"id": "NousResearch/Nous-Hermes-2-Mistral-7B-DPO",
|
| 31 |
+
"name": "Nous Hermes 2",
|
| 32 |
+
"description": "High-quality 7B with DPO training",
|
|
|
|
| 33 |
},
|
| 34 |
{
|
| 35 |
+
"id": "meta-llama/Llama-2-7b-chat-hf",
|
| 36 |
+
"name": "Llama 2 7B Chat",
|
| 37 |
+
"description": "Meta's Llama 2 7B chat variant",
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"id": "google/flan-t5-large",
|
| 41 |
+
"name": "FLAN-T5 Large",
|
| 42 |
+
"description": "Google's instruction-tuned T5 model",
|
| 43 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
]
|
| 45 |
|
| 46 |
|
| 47 |
async def get_available_models() -> dict:
|
| 48 |
"""
|
| 49 |
+
Get unified list of all available models (Groq + HF).
|
| 50 |
+
Frontend receives models without backend categorization.
|
| 51 |
"""
|
| 52 |
return {
|
| 53 |
+
"models": ALL_MODELS,
|
| 54 |
+
"total": len(ALL_MODELS),
|
|
|
|
| 55 |
}
|
| 56 |
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
def get_model_display_name(model_id: str) -> str:
|
| 59 |
+
"""Get clean display name from model ID."""
|
| 60 |
+
for model in ALL_MODELS:
|
|
|
|
| 61 |
if model["id"] == model_id:
|
| 62 |
return model["name"]
|
| 63 |
+
# Fallback
|
|
|
|
| 64 |
return model_id.split("/")[-1].split("-")[0].capitalize()
|
| 65 |
+
|
backend/requirements.txt
CHANGED
|
@@ -5,4 +5,3 @@ groq>=0.11.0
|
|
| 5 |
httpx>=0.27.0
|
| 6 |
python-dotenv>=1.0.0
|
| 7 |
pydantic>=2.7.0
|
| 8 |
-
|
|
|
|
| 5 |
httpx>=0.27.0
|
| 6 |
python-dotenv>=1.0.0
|
| 7 |
pydantic>=2.7.0
|
|
|
frontend/components/ModelSelector.tsx
CHANGED
|
@@ -6,8 +6,7 @@ import { getAvailableModels } from "../lib/api"
|
|
| 6 |
interface Model {
|
| 7 |
id: string
|
| 8 |
name: string
|
| 9 |
-
|
| 10 |
-
tag?: string
|
| 11 |
}
|
| 12 |
|
| 13 |
export default function ModelSelector({
|
|
@@ -28,30 +27,17 @@ export default function ModelSelector({
|
|
| 28 |
try {
|
| 29 |
const data = await getAvailableModels()
|
| 30 |
|
| 31 |
-
//
|
| 32 |
-
const
|
| 33 |
-
|
| 34 |
-
id: m.id,
|
| 35 |
-
name: m.name,
|
| 36 |
-
backend: "groq",
|
| 37 |
-
tag: "groq"
|
| 38 |
-
})),
|
| 39 |
-
...(data.hf_spaces_models || []).map((m: any) => ({
|
| 40 |
-
id: m.id,
|
| 41 |
-
name: m.name,
|
| 42 |
-
backend: "hf",
|
| 43 |
-
tag: "hf-spaces"
|
| 44 |
-
}))
|
| 45 |
-
]
|
| 46 |
-
setAllModels(combined)
|
| 47 |
} catch (err) {
|
| 48 |
console.error("Failed to fetch models:", err)
|
| 49 |
-
// Fallback to default
|
| 50 |
setAllModels([
|
| 51 |
-
{ id: "
|
| 52 |
-
{ id: "
|
| 53 |
-
{ id: "
|
| 54 |
-
{ id: "
|
| 55 |
])
|
| 56 |
} finally {
|
| 57 |
setLoading(false)
|
|
@@ -71,74 +57,34 @@ export default function ModelSelector({
|
|
| 71 |
)
|
| 72 |
}
|
| 73 |
|
| 74 |
-
// Group models by backend
|
| 75 |
-
const groqModels = allModels.filter(m => m.backend === "groq")
|
| 76 |
-
const hfModels = allModels.filter(m => m.backend === "hf")
|
| 77 |
-
|
| 78 |
return (
|
| 79 |
<div className="px-4 py-6 space-y-6">
|
| 80 |
<div>
|
| 81 |
<h3 className="text-[10px] font-mono text-white/30 uppercase tracking-[0.2em] mb-4">
|
| 82 |
Select Survivors ({models.length}/6)
|
| 83 |
</h3>
|
| 84 |
-
<div className="space-y-
|
| 85 |
-
{
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
<
|
| 89 |
-
|
| 90 |
-
{
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
</span>
|
| 107 |
-
</button>
|
| 108 |
-
)
|
| 109 |
-
})}
|
| 110 |
-
</div>
|
| 111 |
-
</div>
|
| 112 |
-
)}
|
| 113 |
-
|
| 114 |
-
{/* HF Spaces Models */}
|
| 115 |
-
{hfModels.length > 0 && (
|
| 116 |
-
<div>
|
| 117 |
-
<h4 className="text-[8px] font-mono text-white/40 uppercase tracking-[0.15em] mb-2">HuggingFace Spaces</h4>
|
| 118 |
-
<div className="grid grid-cols-1 gap-1.5">
|
| 119 |
-
{hfModels.map((m) => {
|
| 120 |
-
const isSelected = models.includes(m.id)
|
| 121 |
-
return (
|
| 122 |
-
<button
|
| 123 |
-
key={m.id}
|
| 124 |
-
onClick={() => isSelected ? onRemove(m.id) : onAdd(m.id)}
|
| 125 |
-
disabled={full && !isSelected}
|
| 126 |
-
className={`flex items-center justify-between px-3 py-2 rounded-lg border transition-all duration-200 ${
|
| 127 |
-
isSelected
|
| 128 |
-
? 'bg-purple-500/10 border-purple-500/30'
|
| 129 |
-
: 'border-transparent hover:bg-white/5 opacity-60 hover:opacity-100'
|
| 130 |
-
} ${full && !isSelected ? 'cursor-not-allowed opacity-20' : ''}`}
|
| 131 |
-
>
|
| 132 |
-
<span className="font-mono text-xs text-white/90">{m.name}</span>
|
| 133 |
-
<span className="text-[8px] font-mono uppercase px-1.5 py-0.5 rounded bg-purple-500/20 text-purple-400">
|
| 134 |
-
HF
|
| 135 |
-
</span>
|
| 136 |
-
</button>
|
| 137 |
-
)
|
| 138 |
-
})}
|
| 139 |
-
</div>
|
| 140 |
-
</div>
|
| 141 |
-
)}
|
| 142 |
</div>
|
| 143 |
</div>
|
| 144 |
|
|
@@ -150,7 +96,7 @@ export default function ModelSelector({
|
|
| 150 |
return (
|
| 151 |
<div key={id} className="flex items-center gap-2 bg-white/5 px-2 py-1 rounded border border-white/10">
|
| 152 |
<span className="font-mono text-[10px] text-white/50">
|
| 153 |
-
{model?.name || id}
|
| 154 |
</span>
|
| 155 |
<button onClick={() => onRemove(id)} className="text-white/20 hover:text-white">✕</button>
|
| 156 |
</div>
|
|
@@ -162,3 +108,4 @@ export default function ModelSelector({
|
|
| 162 |
</div>
|
| 163 |
)
|
| 164 |
}
|
|
|
|
|
|
| 6 |
interface Model {
|
| 7 |
id: string
|
| 8 |
name: string
|
| 9 |
+
description?: string
|
|
|
|
| 10 |
}
|
| 11 |
|
| 12 |
export default function ModelSelector({
|
|
|
|
| 27 |
try {
|
| 28 |
const data = await getAvailableModels()
|
| 29 |
|
| 30 |
+
// Use unified model list (no backend categorization)
|
| 31 |
+
const modelList = data.models || data.hf_models || data.groq_models || []
|
| 32 |
+
setAllModels(modelList)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
} catch (err) {
|
| 34 |
console.error("Failed to fetch models:", err)
|
| 35 |
+
// Fallback to default models
|
| 36 |
setAllModels([
|
| 37 |
+
{ id: "mixtral-8x7b-32768", name: "Mixtral 8x7B", description: "High-performance model" },
|
| 38 |
+
{ id: "llama2-70b-4096", name: "Llama 2 70B", description: "Large instruction-tuned model" },
|
| 39 |
+
{ id: "mistralai/Mistral-7B-Instruct-v0.2", name: "Mistral 7B", description: "Fast 7B model" },
|
| 40 |
+
{ id: "NousResearch/Nous-Hermes-2-Mistral-7B-DPO", name: "Nous Hermes 2", description: "High-quality model" },
|
| 41 |
])
|
| 42 |
} finally {
|
| 43 |
setLoading(false)
|
|
|
|
| 57 |
)
|
| 58 |
}
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
return (
|
| 61 |
<div className="px-4 py-6 space-y-6">
|
| 62 |
<div>
|
| 63 |
<h3 className="text-[10px] font-mono text-white/30 uppercase tracking-[0.2em] mb-4">
|
| 64 |
Select Survivors ({models.length}/6)
|
| 65 |
</h3>
|
| 66 |
+
<div className="space-y-1.5">
|
| 67 |
+
{allModels.map((m) => {
|
| 68 |
+
const isSelected = models.includes(m.id)
|
| 69 |
+
return (
|
| 70 |
+
<button
|
| 71 |
+
key={m.id}
|
| 72 |
+
onClick={() => isSelected ? onRemove(m.id) : onAdd(m.id)}
|
| 73 |
+
disabled={full && !isSelected}
|
| 74 |
+
className={`w-full flex items-center justify-between px-3 py-2 rounded-lg border transition-all duration-200 ${
|
| 75 |
+
isSelected
|
| 76 |
+
? 'bg-white/10 border-white/20'
|
| 77 |
+
: 'border-transparent hover:bg-white/5 opacity-60 hover:opacity-100'
|
| 78 |
+
} ${full && !isSelected ? 'cursor-not-allowed opacity-20' : ''}`}
|
| 79 |
+
title={m.description}
|
| 80 |
+
>
|
| 81 |
+
<span className="font-mono text-xs text-white/90 text-left flex-1">{m.name}</span>
|
| 82 |
+
<span className={`text-[8px] font-mono ml-2 px-2 py-1 rounded ${isSelected ? 'bg-white/20 text-white' : 'text-white/30'}`}>
|
| 83 |
+
{isSelected ? "✓" : "○"}
|
| 84 |
+
</span>
|
| 85 |
+
</button>
|
| 86 |
+
)
|
| 87 |
+
})}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
</div>
|
| 89 |
</div>
|
| 90 |
|
|
|
|
| 96 |
return (
|
| 97 |
<div key={id} className="flex items-center gap-2 bg-white/5 px-2 py-1 rounded border border-white/10">
|
| 98 |
<span className="font-mono text-[10px] text-white/50">
|
| 99 |
+
{model?.name || id.split("/").pop()}
|
| 100 |
</span>
|
| 101 |
<button onClick={() => onRemove(id)} className="text-white/20 hover:text-white">✕</button>
|
| 102 |
</div>
|
|
|
|
| 108 |
</div>
|
| 109 |
)
|
| 110 |
}
|
| 111 |
+
|