feat(ki): Modell aus Liste der verfuegbaren OpenAI-Modelle waehlen
Bisher musste der Administrator den Modellnamen abtippen - ein Tippfehler fiel erst beim Test oder bei der ersten Frage auf, und welche Modelle der Schluessel ueberhaupt darf, war in Pulse nicht zu sehen. Einstellungen -> KI-Assistent zeigt jetzt ein Auswahlfeld. Die Liste kommt ueber POST /settings/ai/models live von OpenAI (/v1/models), neueste zuerst; Embedding-, Bild-, Audio-, Realtime-, Such- und Moderationsmodelle fliegen raus. "Liste laden" nimmt einen gerade eingetippten, noch nicht gespeicherten Schluessel mit. Das eingestellte Modell bleibt waehlbar, auch wenn OpenAI es nicht mehr listet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8a8e433397
commit
2f9c58f202
@@ -5,7 +5,7 @@ import { Team } from '../team/Team';
|
||||
import { SsoDirectory } from './SsoDirectory';
|
||||
import { HrworksSettings } from './HrworksSettings';
|
||||
import { useAuth } from '../../lib/auth';
|
||||
import { download, settingsApi, type AdminSettings } from '../../lib/api';
|
||||
import { download, settingsApi, type AdminSettings, type AiModel } from '../../lib/api';
|
||||
import { useLoader } from '../../lib/hooks';
|
||||
import { useRouter } from '../../lib/router';
|
||||
import { useToast } from '../Toast';
|
||||
@@ -455,6 +455,28 @@ const AiSettings: React.FC<{ settings: AdminSettings; onSaved: () => void }> = (
|
||||
|
||||
useEffect(() => setAi({ ...settings.ai, api_key: '' }), [settings.ai]);
|
||||
|
||||
// Auswahl kommt live von OpenAI - welche Modelle es gibt, haengt am Schluessel.
|
||||
const [models, setModels] = useState<AiModel[]>([]);
|
||||
const [modelsBusy, setModelsBusy] = useState(false);
|
||||
const [modelsError, setModelsError] = useState('');
|
||||
const loadModels = async (apiKey?: string) => {
|
||||
setModelsBusy(true);
|
||||
setModelsError('');
|
||||
try {
|
||||
setModels((await settingsApi.aiModels(apiKey)).models);
|
||||
} catch (err) {
|
||||
setModelsError(err instanceof Error ? err.message : 'Modelle konnten nicht geladen werden.');
|
||||
} finally {
|
||||
setModelsBusy(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (settings.ai.key_set) loadModels();
|
||||
}, [settings.ai.key_set]);
|
||||
|
||||
// Das eingestellte Modell bleibt waehlbar, auch wenn OpenAI es nicht (mehr) listet.
|
||||
const modelOptions = models.some((m) => m.id === ai.model) || !ai.model ? models : [{ id: ai.model, created: null }, ...models];
|
||||
|
||||
const test = async () => {
|
||||
setTesting(true);
|
||||
try {
|
||||
@@ -510,8 +532,39 @@ const AiSettings: React.FC<{ settings: AdminSettings; onSaved: () => void }> = (
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Modell" hint="Voreingestellt gpt-5.6-luna. Ein anderes Modell muss Werkzeuge über die Responses-API unterstützen.">
|
||||
<input className={`${inputClass} font-mono text-xs`} value={ai.model} onChange={(e) => set({ model: e.target.value })} />
|
||||
<Field
|
||||
label="Modell"
|
||||
hint={
|
||||
modelsError ||
|
||||
(models.length
|
||||
? `${models.length} Modelle für diesen Schlüssel, neueste zuerst. Voreingestellt gpt-5.6-luna; ein anderes Modell muss Werkzeuge über die Responses-API unterstützen.`
|
||||
: 'Die Liste kommt von OpenAI, sobald ein Schlüssel hinterlegt oder eingegeben ist.')
|
||||
}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<select
|
||||
className={`${inputClass} font-mono text-xs`}
|
||||
value={ai.model}
|
||||
onChange={(e) => set({ model: e.target.value })}
|
||||
disabled={modelsBusy}
|
||||
>
|
||||
{modelOptions.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.id}
|
||||
{m.created ? ` (${m.created})` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon="refresh"
|
||||
busy={modelsBusy}
|
||||
onClick={() => loadModels(ai.api_key)}
|
||||
disabled={!settings.ai.key_set && !ai.api_key}
|
||||
>
|
||||
Liste laden
|
||||
</Button>
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<Toggle
|
||||
|
||||
@@ -137,6 +137,7 @@ export const settingsApi = {
|
||||
save: (changes: Partial<AdminSettingsInput>) => send<{ ok: true }>('PUT', '/settings', changes),
|
||||
ssoTest: () => send<{ ok: true; sample: string[]; checked: number; eligible: number }>('POST', '/settings/sso/test'),
|
||||
aiTest: () => send<{ ok: true; model: string; answer: string }>('POST', '/settings/ai/test'),
|
||||
aiModels: (apiKey?: string) => send<{ models: AiModel[] }>('POST', '/settings/ai/models', { api_key: apiKey || undefined }),
|
||||
};
|
||||
|
||||
export interface ExportSettings {
|
||||
@@ -226,6 +227,11 @@ export const hrworksApi = {
|
||||
removeAll: () => send<{ deleted: number }>('DELETE', '/hrworks/entries'),
|
||||
};
|
||||
|
||||
export interface AiModel {
|
||||
id: string;
|
||||
created: string | null;
|
||||
}
|
||||
|
||||
export interface AiSettings {
|
||||
enabled: boolean;
|
||||
api_key: string;
|
||||
|
||||
Reference in New Issue
Block a user