Fix GPT-5 API compatibility and add AIUsageLog migration

- Add AIUsageLog table migration for token tracking
- Fix GPT-5 temperature parameter (not supported, like o-series)
- Add usesNewTokenParam() and supportsTemperature() functions
- Add GPT-5+ category to model selection UI
- Update model sorting to show GPT-5+ first

GPT-5 and newer models use max_completion_tokens and don't support
custom temperature values, similar to reasoning models.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 15:04:16 +01:00
parent c0ce6f9f1f
commit 3986da172f
8 changed files with 423 additions and 41 deletions

View File

@@ -4,11 +4,13 @@ import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { toast } from 'sonner'
import { Bot, Loader2, Zap } from 'lucide-react'
import { Bot, Loader2, Zap, AlertCircle, RefreshCw, Brain } from 'lucide-react'
import { trpc } from '@/lib/trpc/client'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Switch } from '@/components/ui/switch'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Skeleton } from '@/components/ui/skeleton'
import {
Form,
FormControl,
@@ -21,7 +23,9 @@ import {
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
@@ -60,6 +64,17 @@ export function AISettingsForm({ settings }: AISettingsFormProps) {
},
})
// Fetch available models from OpenAI API
const {
data: modelsData,
isLoading: modelsLoading,
error: modelsError,
refetch: refetchModels,
} = trpc.settings.listAIModels.useQuery(undefined, {
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
retry: false,
})
const updateSettings = trpc.settings.updateMultiple.useMutation({
onSuccess: () => {
toast.success('AI settings saved successfully')
@@ -73,7 +88,9 @@ export function AISettingsForm({ settings }: AISettingsFormProps) {
const testConnection = trpc.settings.testAIConnection.useMutation({
onSuccess: (result) => {
if (result.success) {
toast.success('AI connection successful')
toast.success(`AI connection successful! Model: ${result.model || result.modelTested}`)
// Refetch models after successful API key save/test
refetchModels()
} else {
toast.error(`Connection failed: ${result.error}`)
}
@@ -99,6 +116,29 @@ export function AISettingsForm({ settings }: AISettingsFormProps) {
updateSettings.mutate({ settings: settingsToUpdate })
}
// Group models by category for better display
type ModelInfo = { id: string; name: string; isReasoning: boolean; category: string }
const groupedModels = modelsData?.models?.reduce<Record<string, ModelInfo[]>>(
(acc, model) => {
const category = model.category
if (!acc[category]) acc[category] = []
acc[category].push(model)
return acc
},
{}
)
const categoryLabels: Record<string, string> = {
'gpt-5+': 'GPT-5+ Series (Latest)',
'gpt-4o': 'GPT-4o Series',
'gpt-4': 'GPT-4 Series',
'gpt-3.5': 'GPT-3.5 Series',
reasoning: 'Reasoning Models (o1, o3, o4)',
other: 'Other Models',
}
const categoryOrder = ['gpt-5+', 'gpt-4o', 'gpt-4', 'gpt-3.5', 'reasoning', 'other']
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
@@ -147,38 +187,6 @@ export function AISettingsForm({ settings }: AISettingsFormProps) {
)}
/>
<FormField
control={form.control}
name="ai_model"
render={({ field }) => (
<FormItem>
<FormLabel>Model</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select model" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="gpt-4o">GPT-4o (Recommended)</SelectItem>
<SelectItem value="gpt-4o-mini">GPT-4o Mini</SelectItem>
<SelectItem value="gpt-5">GPT-5</SelectItem>
<SelectItem value="gpt-5-mini">GPT-5 Mini</SelectItem>
<SelectItem value="o3">o3</SelectItem>
<SelectItem value="o3-mini">o3 Mini</SelectItem>
<SelectItem value="o4-mini">o4 Mini</SelectItem>
<SelectItem value="gpt-4-turbo">GPT-4 Turbo</SelectItem>
<SelectItem value="gpt-4">GPT-4</SelectItem>
</SelectContent>
</Select>
<FormDescription>
OpenAI model to use for AI features
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="openai_api_key"
@@ -200,6 +208,88 @@ export function AISettingsForm({ settings }: AISettingsFormProps) {
)}
/>
<FormField
control={form.control}
name="ai_model"
render={({ field }) => (
<FormItem>
<div className="flex items-center justify-between">
<FormLabel>Model</FormLabel>
{modelsData?.success && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => refetchModels()}
className="h-6 px-2 text-xs"
>
<RefreshCw className="mr-1 h-3 w-3" />
Refresh
</Button>
)}
</div>
{modelsLoading ? (
<Skeleton className="h-10 w-full" />
) : modelsError || !modelsData?.success ? (
<div className="space-y-2">
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
{modelsError?.message || modelsData?.error || 'Failed to load models. Save your API key first and test the connection.'}
</AlertDescription>
</Alert>
<Input
value={field.value}
onChange={(e) => field.onChange(e.target.value)}
placeholder="Enter model ID manually (e.g., gpt-4o)"
/>
</div>
) : (
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select model" />
</SelectTrigger>
</FormControl>
<SelectContent>
{categoryOrder
.filter((cat) => groupedModels?.[cat]?.length)
.map((category) => (
<SelectGroup key={category}>
<SelectLabel className="text-xs font-semibold text-muted-foreground">
{categoryLabels[category] || category}
</SelectLabel>
{groupedModels?.[category]?.map((model) => (
<SelectItem key={model.id} value={model.id}>
<div className="flex items-center gap-2">
{model.isReasoning && (
<Brain className="h-3 w-3 text-purple-500" />
)}
<span>{model.name}</span>
</div>
</SelectItem>
))}
</SelectGroup>
))}
</SelectContent>
</Select>
)}
<FormDescription>
{form.watch('ai_model')?.startsWith('o') ? (
<span className="flex items-center gap-1 text-purple-600">
<Brain className="h-3 w-3" />
Reasoning model - optimized for complex analysis tasks
</span>
) : (
'OpenAI model to use for AI features'
)}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="ai_send_descriptions"