fix: fix gemini models list

This commit is contained in:
2026-04-21 09:11:54 +02:00
parent b7269601eb
commit 2761282c0b
2 changed files with 67 additions and 12 deletions

View File

@ -74,11 +74,43 @@ func (p *geminiProvider) Summarize(ctx context.Context, prompt string, _ GenOpti
return result.Candidates[0].Content.Parts[0].Text, nil return result.Candidates[0].Content.Parts[0].Text, nil
} }
func (p *geminiProvider) ListModels(_ context.Context) ([]string, error) { func (p *geminiProvider) ListModels(ctx context.Context) ([]string, error) {
return []string{ url := fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models?key=%s", p.apiKey)
"gemini-2.0-flash", req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
"gemini-2.0-flash-lite", if err != nil {
"gemini-1.5-pro", return nil, err
"gemini-1.5-flash", }
}, nil resp, err := p.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("gemini list models error %d: %s", resp.StatusCode, raw)
}
var result struct {
Models []struct {
Name string `json:"name"`
SupportedMethods []string `json:"supportedGenerationMethods"`
} `json:"models"`
}
if err := json.Unmarshal(raw, &result); err != nil {
return nil, err
}
var names []string
for _, m := range result.Models {
for _, method := range m.SupportedMethods {
if method == "generateContent" {
// name is "models/gemini-xxx", strip prefix
id := m.Name
if len(id) > 7 {
id = id[7:] // strip "models/"
}
names = append(names, id)
break
}
}
}
return names, nil
} }

View File

@ -15,7 +15,7 @@ function useTextSelection(containerRef: React.RefObject<HTMLElement>) {
const [selection, setSelection] = useState<{ text: string; x: number; y: number } | null>(null) const [selection, setSelection] = useState<{ text: string; x: number; y: number } | null>(null)
useEffect(() => { useEffect(() => {
function onMouseUp() { function readSelection() {
const sel = window.getSelection() const sel = window.getSelection()
const text = sel?.toString().trim() const text = sel?.toString().trim()
if (!text || !containerRef.current) { setSelection(null); return } if (!text || !containerRef.current) { setSelection(null); return }
@ -28,14 +28,23 @@ function useTextSelection(containerRef: React.RefObject<HTMLElement>) {
y: rect.top - containerRect.top - 8, y: rect.top - containerRect.top - 8,
}) })
} }
function onMouseDown(e: MouseEvent) { function onMouseUp() { readSelection() }
// On mobile, selectionchange fires after the user lifts their finger
function onSelectionChange() {
const sel = window.getSelection()
if (!sel?.toString().trim()) setSelection(null)
else readSelection()
}
function onPointerDown(e: PointerEvent) {
if (!(e.target as Element).closest('[data-context-action]')) setSelection(null) if (!(e.target as Element).closest('[data-context-action]')) setSelection(null)
} }
document.addEventListener('mouseup', onMouseUp) document.addEventListener('mouseup', onMouseUp)
document.addEventListener('mousedown', onMouseDown) document.addEventListener('selectionchange', onSelectionChange)
document.addEventListener('pointerdown', onPointerDown)
return () => { return () => {
document.removeEventListener('mouseup', onMouseUp) document.removeEventListener('mouseup', onMouseUp)
document.removeEventListener('mousedown', onMouseDown) document.removeEventListener('selectionchange', onSelectionChange)
document.removeEventListener('pointerdown', onPointerDown)
} }
}, [containerRef]) }, [containerRef])
@ -278,11 +287,12 @@ export function Dashboard() {
<CardContent> <CardContent>
<div ref={summaryRef} className="relative"> <div ref={summaryRef} className="relative">
<SummaryContent content={current.content} /> <SummaryContent content={current.content} />
{/* Desktop: floating button above selection */}
{textSel && ( {textSel && (
<button <button
data-context-action data-context-action
onClick={addExcerpt} onClick={addExcerpt}
className="absolute z-10 flex items-center gap-1 rounded-full bg-primary text-primary-foreground text-xs px-2 py-1 shadow-lg hover:bg-primary/90 transition-colors" className="absolute z-10 hidden md:flex items-center gap-1 rounded-full bg-primary text-primary-foreground text-xs px-2 py-1 shadow-lg hover:bg-primary/90 transition-colors"
style={{ left: textSel.x, top: textSel.y, transform: 'translate(-50%, -100%)' }} style={{ left: textSel.x, top: textSel.y, transform: 'translate(-50%, -100%)' }}
> >
<Plus className="h-3 w-3" /> <Plus className="h-3 w-3" />
@ -290,6 +300,19 @@ export function Dashboard() {
</button> </button>
)} )}
</div> </div>
{/* Mobile: fixed bottom bar — avoids conflict with native selection menu */}
{textSel && (
<div className="md:hidden fixed bottom-16 left-0 right-0 z-50 flex justify-center px-4 pointer-events-none">
<button
data-context-action
onClick={addExcerpt}
className="pointer-events-auto flex items-center gap-2 rounded-full bg-primary text-primary-foreground text-sm px-4 py-2.5 shadow-xl"
>
<Plus className="h-4 w-4" />
Ajouter au contexte
</button>
</div>
)}
<p className="text-xs text-muted-foreground mt-4 italic"> <p className="text-xs text-muted-foreground mt-4 italic">
Sélectionnez du texte pour l'ajouter au contexte, puis posez une question dans le panneau qui apparaît. Sélectionnez du texte pour l'ajouter au contexte, puis posez une question dans le panneau qui apparaît.
</p> </p>