diff --git a/src/client/components/settings/AiActivityPanel.tsx b/src/client/components/settings/AiActivityPanel.tsx index bd124d0..44af587 100644 --- a/src/client/components/settings/AiActivityPanel.tsx +++ b/src/client/components/settings/AiActivityPanel.tsx @@ -19,8 +19,8 @@ export function AiActivityPanel() { const { events, summary } = query.data; const tokenTotal = summary.promptTokens + summary.completionTokens; return ( -
-
+
+
{summary.total ? Math.round((summary.ok / summary.total) * 100) : 0}% Success rate @@ -39,14 +39,20 @@ export function AiActivityPanel() {
{events.length === 0 ? ( - + No AI activity yet Attempts will appear here after AI generation or autopilot runs. ) : ( -
+
{events.map((event) => ( -
+
{event.outcome.replaceAll('_', ' ')} diff --git a/src/client/components/settings/AiIncidentMessagesPanel.tsx b/src/client/components/settings/AiIncidentMessagesPanel.tsx new file mode 100644 index 0000000..aaefb1e --- /dev/null +++ b/src/client/components/settings/AiIncidentMessagesPanel.tsx @@ -0,0 +1,165 @@ +import { type FormEvent, useState } from 'react'; +import { Sparkles } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Empty, EmptyTitle } from '@/components/ui/empty'; +import { Input } from '@/components/ui/input'; +import { Switch } from '@/components/ui/switch'; +import type { AiSettings } from '../../api/settings'; +import { useAiSettingsQuery, useTestAiSettingsMutation, useUpdateAiSettingsMutation } from '../../queries/settings'; + +function AiSettingsForm({ settings }: { settings: AiSettings }) { + const [baseUrl, setBaseUrl] = useState(settings.baseUrl ?? 'https://api.openai.com/v1'); + const [apiKey, setApiKey] = useState(''); + const [model, setModel] = useState(settings.model ?? 'gpt-4o-mini'); + const [enabled, setEnabled] = useState(settings.enabled); + const [autopilotEnabled, setAutopilotEnabled] = useState(settings.autopilotEnabled); + const [followupMinutes, setFollowupMinutes] = useState(settings.autopilotFollowupMinutes); + const [maxUpdates, setMaxUpdates] = useState(settings.autopilotMaxUpdates); + const [advanceStatus, setAdvanceStatus] = useState(settings.autopilotAdvanceStatus); + const [degradedIncidents, setDegradedIncidents] = useState(settings.autopilotDegradedIncidents); + const updateMutation = useUpdateAiSettingsMutation(); + const testMutation = useTestAiSettingsMutation(); + + function submit(event: FormEvent) { + event.preventDefault(); + updateMutation.mutate({ + enabled, + baseUrl: baseUrl.trim() || null, + model: model.trim() || null, + apiKey: apiKey.trim() || null, + autopilotEnabled, + autopilotFollowupMinutes: followupMinutes, + autopilotMaxUpdates: maxUpdates, + autopilotAdvanceStatus: advanceStatus, + autopilotDegradedIncidents: degradedIncidents, + }); + } + + return ( +
+ + + +
+ + +
+
+ Autopilot +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ {updateMutation.isSuccess && ( +

+ AI settings saved. +

+ )} + {testMutation.isSuccess && ( +

+ {testMutation.data.message} +

+ )} + {(updateMutation.error || testMutation.error) && ( +

{(updateMutation.error ?? testMutation.error)?.message ?? 'Request failed'}

+ )} +
+ ); +} + +export function AiIncidentMessagesPanel() { + const aiSettingsQuery = useAiSettingsQuery(); + if (aiSettingsQuery.isPending) return
Loading settings…
; + if (aiSettingsQuery.isError) { + return ( + + Unable to load AI settings + + ); + } + return ; +} diff --git a/src/client/components/settings/MaintenanceWindowDialog.tsx b/src/client/components/settings/MaintenanceWindowDialog.tsx index 23f55d1..22488a3 100644 --- a/src/client/components/settings/MaintenanceWindowDialog.tsx +++ b/src/client/components/settings/MaintenanceWindowDialog.tsx @@ -115,8 +115,8 @@ export function MaintenanceWindowDialog({ editing, onClose }: { editing: Mainten {editing ? `Edit ${editing.name}` : 'Add maintenance window'} Probes continue, but alerts and uptime calculations pause for selected services. -
-