File size: 4,588 Bytes
43d708a
 
 
0e54f19
43d708a
 
 
 
8e038ca
43d708a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0e54f19
43d708a
8e038ca
25f6a95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43d708a
 
8e038ca
43d708a
8e038ca
 
 
 
43d708a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11ba25d
8e038ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43d708a
 
 
 
 
 
 
 
 
 
 
8e038ca
43d708a
 
 
 
 
 
 
 
 
 
 
8e038ca
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"use client"

import { useState, useEffect } from "react"
import { getAvailableModels } from "../lib/api"

interface Model {
  id: string
  name: string
  description?: string
}

export default function ModelSelector({
  models,
  onAdd,
  onRemove,
}: {
  models: string[]
  onAdd: (id: string) => void
  onRemove: (id: string) => void
}) {
  const [allModels, setAllModels] = useState<Model[]>([])
  const [loading, setLoading] = useState(true)
  const full = models.length >= 6

  useEffect(() => {
    async function fetchModels() {
      try {
        const data = await getAvailableModels()
        
        // Use unified model list (no backend categorization)
        // Support multiple backend response formats (new unified `models` or legacy grouped keys)
        if (data.models && Array.isArray(data.models)) {
          setAllModels(data.models)
        } else {
          const groq = data.groq_models || data.groqModels || data.groq || []
          const hf = data.hf_spaces_models || data.hf_spaces || data.hf_models || data.hfModels || []
          const merged: Model[] = []
          if (Array.isArray(groq)) {
            for (const g of groq) merged.push({ id: g.id, name: g.name || g.id, description: g.description || g.backend || '' })
          }
          if (Array.isArray(hf)) {
            for (const h of hf) merged.push({ id: h.id, name: h.name || h.id, description: h.description || h.space_url || '' })
          }
          if (merged.length > 0) setAllModels(merged)
          else setAllModels([])
        }
      } catch (err) {
        console.error("Failed to fetch models:", err)
        // Fallback to default models
        setAllModels([
          { id: "mixtral-8x7b-32768", name: "Mixtral 8x7B", description: "High-performance model" },
          { id: "llama2-70b-4096", name: "Llama 2 70B", description: "Large instruction-tuned model" },
          { id: "mistralai/Mistral-7B-Instruct-v0.2", name: "Mistral 7B", description: "Fast 7B model" },
          { id: "NousResearch/Nous-Hermes-2-Mistral-7B-DPO", name: "Nous Hermes 2", description: "High-quality model" },
        ])
      } finally {
        setLoading(false)
      }
    }

    fetchModels()
  }, [])

  if (loading) {
    return (
      <div className="px-4 py-6 space-y-6">
        <div className="text-center text-white/40 font-mono text-xs">
          Loading models...
        </div>
      </div>
    )
  }

  return (
    <div className="px-4 py-6 space-y-6">
      <div>
        <h3 className="text-[10px] font-mono text-white/30 uppercase tracking-[0.2em] mb-4">
          Select Survivors ({models.length}/6)
        </h3>
        <div className="space-y-1.5 max-h-[400px] overflow-y-auto overflow-x-hidden">
          {allModels.map((m) => {
            const isSelected = models.includes(m.id)
            return (
              <button
                key={m.id}
                onClick={() => isSelected ? onRemove(m.id) : onAdd(m.id)}
                disabled={full && !isSelected}
                className={`w-full flex items-center justify-between px-3 py-2 rounded-lg border transition-all duration-200 ${
                  isSelected 
                    ? 'bg-white/10 border-white/20' 
                    : 'border-transparent hover:bg-white/5 opacity-60 hover:opacity-100'
                } ${full && !isSelected ? 'cursor-not-allowed opacity-20' : ''}`}
                title={m.description}
              >
                <span className="font-mono text-xs text-white/90 text-left flex-1">{m.name}</span>
                <span className={`text-[8px] font-mono ml-2 px-2 py-1 rounded ${isSelected ? 'bg-white/20 text-white' : 'text-white/30'}`}>
                  {isSelected ? "✓" : "○"}
                </span>
              </button>
            )
          })}
        </div>
      </div>
      
      {models.length > 0 && (
        <div className="pt-4 border-t border-white/5">
          <div className="flex flex-wrap gap-2">
            {models.map(id => {
              const model = allModels.find(m => m.id === id)
              return (
                <div key={id} className="flex items-center gap-2 bg-white/5 px-2 py-1 rounded border border-white/10">
                  <span className="font-mono text-[10px] text-white/50">
                    {model?.name || id.split("/").pop()}
                  </span>
                  <button onClick={() => onRemove(id)} className="text-white/20 hover:text-white">✕</button>
                </div>
              )
            })}
          </div>
        </div>
      )}
    </div>
  )
}