feat(byok): fetch live models dynamically from provider api with user api key on input
This commit is contained in:
@@ -53,6 +53,10 @@ export function ByokSettingsPanel() {
|
||||
const [customModel, setCustomModel] = useState('')
|
||||
const [isCustomModel, setIsCustomModel] = useState(false)
|
||||
|
||||
// Dynamic models fetched directly via user's API Key
|
||||
const [liveModels, setLiveModels] = useState<string[]>([])
|
||||
const [isFetchingLiveModels, setIsFetchingLiveModels] = useState(false)
|
||||
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['user', 'api-keys'],
|
||||
queryFn: fetchByokKeys,
|
||||
@@ -62,13 +66,46 @@ export function ByokSettingsPanel() {
|
||||
|
||||
const handleProviderChange = (p: string) => {
|
||||
setProvider(p)
|
||||
const sug = providerModels[p] || []
|
||||
if (sug.length > 0) {
|
||||
setModel(sug[0])
|
||||
setApiKey('')
|
||||
setLiveModels([])
|
||||
setIsCustomModel(false)
|
||||
setModel('')
|
||||
setCustomModel('')
|
||||
}
|
||||
|
||||
// Triggered dynamically to fetch models when user enters/pastes their API key
|
||||
const fetchLiveModels = async (p: string, key: string) => {
|
||||
if (!p || !key || key.length < 8) return;
|
||||
setIsFetchingLiveModels(true)
|
||||
try {
|
||||
const query = new URLSearchParams({ provider: p, key })
|
||||
const res = await fetch(`/api/user/api-keys/live-models?${query.toString()}`)
|
||||
if (res.ok) {
|
||||
const body = await res.json()
|
||||
if (body.success && Array.isArray(body.models)) {
|
||||
setLiveModels(body.models)
|
||||
if (body.models.length > 0) {
|
||||
setModel(body.models[0])
|
||||
setIsCustomModel(false)
|
||||
} else {
|
||||
setIsCustomModel(true)
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[fetchLiveModels] Failed:', err)
|
||||
} finally {
|
||||
setIsFetchingLiveModels(false)
|
||||
}
|
||||
|
||||
// Fallback if request fails
|
||||
const fallbackList = providerModels[p] || []
|
||||
setLiveModels(fallbackList)
|
||||
if (fallbackList.length > 0) {
|
||||
setModel(fallbackList[0])
|
||||
setIsCustomModel(false)
|
||||
} else {
|
||||
setModel('')
|
||||
setCustomModel('')
|
||||
setIsCustomModel(true)
|
||||
}
|
||||
}
|
||||
@@ -104,6 +141,7 @@ export function ByokSettingsPanel() {
|
||||
setProvider('')
|
||||
setModel('')
|
||||
setCustomModel('')
|
||||
setLiveModels([])
|
||||
setIsCustomModel(false)
|
||||
invalidate()
|
||||
},
|
||||
@@ -230,8 +268,15 @@ export function ByokSettingsPanel() {
|
||||
{provider && (
|
||||
<div className="grid gap-4 sm:grid-cols-2 pt-2 border-t border-border/40">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="byok-model-select" className="text-[10px] font-bold uppercase tracking-widest text-concrete">Modèle de l'IA (Optionnel)</Label>
|
||||
{providerModels[provider] && providerModels[provider].length > 0 ? (
|
||||
<Label htmlFor="byok-model-select" className="text-[10px] font-bold uppercase tracking-widest text-concrete">
|
||||
Modèle de l'IA (Optionnel)
|
||||
</Label>
|
||||
{isFetchingLiveModels ? (
|
||||
<div className="flex items-center gap-2 text-xs text-concrete py-2">
|
||||
<Loader2 className="h-3 w-3 animate-spin text-brand-accent" />
|
||||
Récupération de vos modèles disponibles...
|
||||
</div>
|
||||
) : liveModels && liveModels.length > 0 ? (
|
||||
<Select
|
||||
value={isCustomModel ? 'custom' : model}
|
||||
onValueChange={(val) => {
|
||||
@@ -248,7 +293,7 @@ export function ByokSettingsPanel() {
|
||||
<SelectValue placeholder="Choisir un modèle..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{providerModels[provider].map((m) => (
|
||||
{liveModels.map((m) => (
|
||||
<SelectItem key={m} value={m}>
|
||||
{m}
|
||||
</SelectItem>
|
||||
@@ -258,12 +303,12 @@ export function ByokSettingsPanel() {
|
||||
</Select>
|
||||
) : (
|
||||
<div className="text-xs text-concrete py-2 italic">
|
||||
Spécifiez le modèle ci-contre si besoin.
|
||||
Entrez votre clé API ci-dessous pour charger vos modèles.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(isCustomModel || !(providerModels[provider] && providerModels[provider].length > 0)) && (
|
||||
{(isCustomModel || (!isFetchingLiveModels && !(liveModels && liveModels.length > 0))) && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="byok-model-custom" className="text-[10px] font-bold uppercase tracking-widest text-concrete">Saisir le nom du modèle</Label>
|
||||
<Input
|
||||
@@ -290,7 +335,11 @@ export function ByokSettingsPanel() {
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value
|
||||
setApiKey(val)
|
||||
fetchLiveModels(provider, val)
|
||||
}}
|
||||
placeholder={t('byokSettings.apiKeyPlaceholder')}
|
||||
disabled={saveMutation.isPending}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user