{"repo": "andriyze/shakerscan", "files": {"ui/src/components/AISettingsPanel.tsx": {"99ee3a0ad73e2b40516a0a8c13d036f3211646ed": {"t": 1785746446.0120826, "n": 34867, "h": "fe392fd83e6ae430", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\nimport { Button, Fieldset, Tabs, ToggleVisual, fieldClasses, useToast } from '@/components/ui'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\n// Point the panel's local field class at the shared field styling so every\n// input/select here matches the rest of the app and gets a real focus ring.\nconst INPUT_CLASS = `mt-1 w-full ${fieldClasses()}`\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n const raw = String(value || '').trim().replace(/\\/+$/, '')\n try {\n const url = new URL(raw)\n url.protocol = url.protocol.toLowerCase()\n url.hostname = url.hostname.toLowerCase()\n return url.toString().replace(/\\/+$/, '')\n } catch {\n return raw\n }\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const toast = useToast()\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [autoFpOnRetestInput, setAutoFpOnRetestInput] = useState(false)\n const [autoFpMinConfidenceInput, setAutoFpMinConfidenceInput] = useState('0.9')\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setAutoFpOnRetestInput(Boolean(settings.auto_fp_on_retest))\n setAutoFpMinConfidenceInput(String(settings.auto_fp_min_confidence ?? 0.9))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n auto_fp_on_retest: autoFpOnRetestInput,\n auto_fp_min_confidence: Math.min(1, Math.max(0, Number.parseFloat(autoFpMinConfidenceInput || '0.9') || 0.9)),\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n const successMessage = result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n setAISettingsMessage(successMessage)\n toast.success(successMessage)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n toast.error(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n if (result.status === 'ok') {\n toast.success(message)\n } else {\n toast.error(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n toast.error(`Probe failed: ${message}`)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI provider & verification

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n setSettingsMode(key as 'basic' | 'advanced')}\n items={[\n { key: 'basic', label: 'Basic' },\n { key: 'advanced', label: 'Advanced' },\n ]}\n />\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n onChange(!checked)}\n className=\"flex w-full items-start justify-between gap-3 rounded border border-gray-800 bg-gray-900/50 px-2.5 py-2 text-left transition-colors hover:border-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-950\"\n >\n \n {label}\n {description}\n {hint && {hint}}\n \n \n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "c8f07b7652e9c03c82cd0da6048b090ee7034659": {"t": 1785764325.9782884, "n": 34867, "h": "fe392fd83e6ae430", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\nimport { Button, Fieldset, Tabs, ToggleVisual, fieldClasses, useToast } from '@/components/ui'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\n// Point the panel's local field class at the shared field styling so every\n// input/select here matches the rest of the app and gets a real focus ring.\nconst INPUT_CLASS = `mt-1 w-full ${fieldClasses()}`\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n const raw = String(value || '').trim().replace(/\\/+$/, '')\n try {\n const url = new URL(raw)\n url.protocol = url.protocol.toLowerCase()\n url.hostname = url.hostname.toLowerCase()\n return url.toString().replace(/\\/+$/, '')\n } catch {\n return raw\n }\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const toast = useToast()\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [autoFpOnRetestInput, setAutoFpOnRetestInput] = useState(false)\n const [autoFpMinConfidenceInput, setAutoFpMinConfidenceInput] = useState('0.9')\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setAutoFpOnRetestInput(Boolean(settings.auto_fp_on_retest))\n setAutoFpMinConfidenceInput(String(settings.auto_fp_min_confidence ?? 0.9))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n auto_fp_on_retest: autoFpOnRetestInput,\n auto_fp_min_confidence: Math.min(1, Math.max(0, Number.parseFloat(autoFpMinConfidenceInput || '0.9') || 0.9)),\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n const successMessage = result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n setAISettingsMessage(successMessage)\n toast.success(successMessage)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n toast.error(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n if (result.status === 'ok') {\n toast.success(message)\n } else {\n toast.error(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n toast.error(`Probe failed: ${message}`)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI provider & verification

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n setSettingsMode(key as 'basic' | 'advanced')}\n items={[\n { key: 'basic', label: 'Basic' },\n { key: 'advanced', label: 'Advanced' },\n ]}\n />\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n onChange(!checked)}\n className=\"flex w-full items-start justify-between gap-3 rounded border border-gray-800 bg-gray-900/50 px-2.5 py-2 text-left transition-colors hover:border-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-950\"\n >\n \n {label}\n {description}\n {hint && {hint}}\n \n \n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "737f3f0af2940b2bad251a6003ea86e1bde28ed2": {"t": 1785764327.3591921, "n": 34875, "h": "93616a84e8c3fc67", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\nimport { Button, Fieldset, Tabs, ToggleVisual, fieldClasses, useToast } from '@/components/ui'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\n// Point the panel's local field class at the shared field styling so every\n// input/select here matches the rest of the app and gets a real focus ring.\nconst INPUT_CLASS = `mt-1 w-full ${fieldClasses()}`\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n const raw = String(value || '').trim().replace(/\\/+$/, '')\n try {\n const url = new URL(raw)\n url.protocol = url.protocol.toLowerCase()\n url.hostname = url.hostname.toLowerCase()\n return url.toString().replace(/\\/+$/, '')\n } catch {\n return raw\n }\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const toast = useToast()\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [autoFpOnRetestInput, setAutoFpOnRetestInput] = useState(false)\n const [autoFpMinConfidenceInput, setAutoFpMinConfidenceInput] = useState('0.9')\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setAutoFpOnRetestInput(Boolean(settings.auto_fp_on_retest))\n setAutoFpMinConfidenceInput(String(settings.auto_fp_min_confidence ?? 0.9))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n auto_fp_on_retest: autoFpOnRetestInput,\n auto_fp_min_confidence: Math.min(1, Math.max(0, Number.parseFloat(autoFpMinConfidenceInput || '0.9') || 0.9)),\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n const successMessage = result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n setAISettingsMessage(successMessage)\n toast.success(successMessage)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n toast.error(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n if (result.status === 'ok') {\n toast.success(message)\n } else {\n toast.error(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n toast.error(`Probe failed: ${message}`)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI provider & verification

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n setSettingsMode(key as 'basic' | 'advanced')}\n items={[\n { key: 'basic', label: 'Basic' },\n { key: 'advanced', label: 'Advanced' },\n ]}\n />\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n onChange(!checked)}\n className=\"flex w-full items-start justify-between gap-3 rounded border border-gray-800 bg-gray-900/50 px-2.5 py-2 text-left transition-colors hover:border-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-950\"\n >\n \n \n {label}\n {hint && }\n \n {description}\n \n \n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "85eb9d35e9488f79b44af17e9f89d27342cc86b5": {"t": 1785764329.2410634, "n": 34550, "h": "ac7a167e6da1db82", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\nimport { Button, Fieldset, Tabs, Toggle, fieldClasses, useToast } from '@/components/ui'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\n// Point the panel's local field class at the shared field styling so every\n// input/select here matches the rest of the app and gets a real focus ring.\nconst INPUT_CLASS = `mt-1 ${fieldClasses()}`\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n const raw = String(value || '').trim().replace(/\\/+$/, '')\n try {\n const url = new URL(raw)\n url.protocol = url.protocol.toLowerCase()\n url.hostname = url.hostname.toLowerCase()\n return url.toString().replace(/\\/+$/, '')\n } catch {\n return raw\n }\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const toast = useToast()\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [autoFpOnRetestInput, setAutoFpOnRetestInput] = useState(false)\n const [autoFpMinConfidenceInput, setAutoFpMinConfidenceInput] = useState('0.9')\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setAutoFpOnRetestInput(Boolean(settings.auto_fp_on_retest))\n setAutoFpMinConfidenceInput(String(settings.auto_fp_min_confidence ?? 0.9))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n auto_fp_on_retest: autoFpOnRetestInput,\n auto_fp_min_confidence: Math.min(1, Math.max(0, Number.parseFloat(autoFpMinConfidenceInput || '0.9') || 0.9)),\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n const successMessage = result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n setAISettingsMessage(successMessage)\n toast.success(successMessage)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n toast.error(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n if (result.status === 'ok') {\n toast.success(message)\n } else {\n toast.error(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n toast.error(`Probe failed: ${message}`)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI provider & verification

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n setSettingsMode(key as 'basic' | 'advanced')}\n items={[\n { key: 'basic', label: 'Basic' },\n { key: 'advanced', label: 'Advanced' },\n ]}\n />\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n
\n \n \n {label}\n {hint && }\n \n {description}\n \n \n
\n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "1d4484f587d2a6b187347e752cc84fddcbe6be47": {"t": 1785764330.77839, "n": 37725, "h": "30713cc23302b098", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\nimport { useToast } from '@/components/ui'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n const raw = String(value || '').trim().replace(/\\/+$/, '')\n try {\n const url = new URL(raw)\n url.protocol = url.protocol.toLowerCase()\n url.hostname = url.hostname.toLowerCase()\n return url.toString().replace(/\\/+$/, '')\n } catch {\n return raw\n }\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const toast = useToast()\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [autoFpOnRetestInput, setAutoFpOnRetestInput] = useState(false)\n const [autoFpMinConfidenceInput, setAutoFpMinConfidenceInput] = useState('0.9')\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setAutoFpOnRetestInput(Boolean(settings.auto_fp_on_retest))\n setAutoFpMinConfidenceInput(String(settings.auto_fp_min_confidence ?? 0.9))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n auto_fp_on_retest: autoFpOnRetestInput,\n auto_fp_min_confidence: Math.min(1, Math.max(0, Number.parseFloat(autoFpMinConfidenceInput || '0.9') || 0.9)),\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n const successMessage = result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n setAISettingsMessage(successMessage)\n toast.success(successMessage)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n toast.error(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n if (result.status === 'ok') {\n toast.success(message)\n } else {\n toast.error(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n toast.error(`Probe failed: ${message}`)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI provider & verification

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n
\n setSettingsMode('basic')}\n className={`px-2.5 py-1 text-xs rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${\n settingsMode === 'basic' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'basic'}\n >\n Basic\n \n setSettingsMode('advanced')}\n className={`px-2.5 py-1 text-xs rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${\n settingsMode === 'advanced' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'advanced'}\n >\n Advanced\n \n
\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "36d36628ea546cfa8b4b00d47c9426f10821ff20": {"t": 1785764332.366383, "n": 37705, "h": "7c62b01ef529b1f3", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\nimport { useToast } from '@/components/ui'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n const raw = String(value || '').trim().replace(/\\/+$/, '')\n try {\n const url = new URL(raw)\n url.protocol = url.protocol.toLowerCase()\n url.hostname = url.hostname.toLowerCase()\n return url.toString().replace(/\\/+$/, '')\n } catch {\n return raw\n }\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const toast = useToast()\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [autoFpOnRetestInput, setAutoFpOnRetestInput] = useState(false)\n const [autoFpMinConfidenceInput, setAutoFpMinConfidenceInput] = useState('0.9')\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setAutoFpOnRetestInput(Boolean(settings.auto_fp_on_retest))\n setAutoFpMinConfidenceInput(String(settings.auto_fp_min_confidence ?? 0.9))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n auto_fp_on_retest: autoFpOnRetestInput,\n auto_fp_min_confidence: Math.min(1, Math.max(0, Number.parseFloat(autoFpMinConfidenceInput || '0.9') || 0.9)),\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n const successMessage = result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n setAISettingsMessage(successMessage)\n toast.success(successMessage)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n toast.error(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n if (result.status === 'ok') {\n toast.success(message)\n } else {\n toast.error(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n toast.error(`Probe failed: ${message}`)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n
\n setSettingsMode('basic')}\n className={`px-2.5 py-1 text-xs rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${\n settingsMode === 'basic' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'basic'}\n >\n Basic\n \n setSettingsMode('advanced')}\n className={`px-2.5 py-1 text-xs rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${\n settingsMode === 'advanced' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'advanced'}\n >\n Advanced\n \n
\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "af804bb35dd0146809875f2187bc75c474a3dfa2": {"t": 1785764335.599114, "n": 37703, "h": "90f9c32dd61ff1ef", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\nimport { useToast } from '@/components/ui'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n const raw = String(value || '').trim().replace(/\\/+$/, '')\n try {\n const url = new URL(raw)\n url.protocol = url.protocol.toLowerCase()\n url.hostname = url.hostname.toLowerCase()\n return url.toString().replace(/\\/+$/, '')\n } catch {\n return raw\n }\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const toast = useToast()\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [autoFpOnRetestInput, setAutoFpOnRetestInput] = useState(false)\n const [autoFpMinConfidenceInput, setAutoFpMinConfidenceInput] = useState('0.9')\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setAutoFpOnRetestInput(Boolean(settings.auto_fp_on_retest))\n setAutoFpMinConfidenceInput(String(settings.auto_fp_min_confidence ?? 0.9))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n auto_fp_on_retest: autoFpOnRetestInput,\n auto_fp_min_confidence: Math.min(1, Math.max(0, Number.parseFloat(autoFpMinConfidenceInput || '0.9') || 0.9)),\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n const successMessage = result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n setAISettingsMessage(successMessage)\n toast.success(successMessage)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n toast.error(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n if (result.status === 'ok') {\n toast.success(message)\n } else {\n toast.error(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n toast.error(`Probe failed: ${message}`)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n
\n setSettingsMode('basic')}\n className={`px-2.5 py-1 text-xs rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${\n settingsMode === 'basic' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'basic'}\n >\n Basic\n \n setSettingsMode('advanced')}\n className={`px-2.5 py-1 text-xs rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${\n settingsMode === 'advanced' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'advanced'}\n >\n Advanced\n \n
\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "2c80dae222d017d1dee2b42c01e5af0aca303396": {"t": 1785764337.1636698, "n": 36073, "h": "787079bbe5aa9118", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\nimport { useToast } from '@/components/ui'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n const raw = String(value || '').trim().replace(/\\/+$/, '')\n try {\n const url = new URL(raw)\n url.protocol = url.protocol.toLowerCase()\n url.hostname = url.hostname.toLowerCase()\n return url.toString().replace(/\\/+$/, '')\n } catch {\n return raw\n }\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const toast = useToast()\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n const successMessage = result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n setAISettingsMessage(successMessage)\n toast.success(successMessage)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n toast.error(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n if (result.status === 'ok') {\n toast.success(message)\n } else {\n toast.error(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n toast.error(`Probe failed: ${message}`)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n
\n setSettingsMode('basic')}\n className={`px-2.5 py-1 text-xs rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${\n settingsMode === 'basic' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'basic'}\n >\n Basic\n \n setSettingsMode('advanced')}\n className={`px-2.5 py-1 text-xs rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${\n settingsMode === 'advanced' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'advanced'}\n >\n Advanced\n \n
\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "163add2ab9c4bb0c876ed6bb0c1a150a16488243": {"t": 1785764339.811915, "n": 34830, "h": "4de83acf1b68d946", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n const raw = String(value || '').trim().replace(/\\/+$/, '')\n try {\n const url = new URL(raw)\n url.protocol = url.protocol.toLowerCase()\n url.hostname = url.hostname.toLowerCase()\n return url.toString().replace(/\\/+$/, '')\n } catch {\n return raw\n }\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n
\n setSettingsMode('basic')}\n className={`px-2.5 py-1 text-xs rounded ${\n settingsMode === 'basic' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'basic'}\n >\n Basic\n \n setSettingsMode('advanced')}\n className={`px-2.5 py-1 text-xs rounded ${\n settingsMode === 'advanced' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'advanced'}\n >\n Advanced\n \n
\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "317255ea9df895b91e574dc2ebbbb08d3dda9fe4": {"t": 1785764341.4540758, "n": 34619, "h": "c9ac6e4911b6cf36", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n return String(value || '').trim().replace(/\\/+$/, '')\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoModeEnabledInput ? demoHoneyPublicURLInput : '',\n demo_honey_scanner_url: demoModeEnabledInput ? demoHoneyScannerURLInput : '',\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n
\n setSettingsMode('basic')}\n className={`px-2.5 py-1 text-xs rounded ${\n settingsMode === 'basic' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'basic'}\n >\n Basic\n \n setSettingsMode('advanced')}\n className={`px-2.5 py-1 text-xs rounded ${\n settingsMode === 'advanced' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'advanced'}\n >\n Advanced\n \n
\n\n {(settingsMode === 'advanced' || demoModeEnabledInput) && (\n \n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "ee484a2e5adf6d847538e06601df15e8bf52c729": {"t": 1785764343.1906328, "n": 34096, "h": "d7c503be3aa1f5ad", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n return String(value || '').trim().replace(/\\/+$/, '')\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('custom')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('')\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || ''\n const scannerURL = settings.demo_honey_scanner_url || ''\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoHoneyPublicURLInput,\n demo_honey_scanner_url: demoHoneyScannerURLInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n \n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "eb8d991e6a1ad45a1d868eb60cdfff0da00b6b67": {"t": 1785764345.164024, "n": 34125, "h": "58fe10dc5feb2bef", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\nconst HONEY_HOSTED_URL = 'https://honey.shakerscan.com'\nconst HONEY_LOCAL_PUBLIC_URL = 'http://localhost:18080'\nconst HONEY_LOCAL_SCANNER_URL = 'http://host.docker.internal:18080'\n\ntype DemoHoneyMode = 'hosted' | 'local' | 'custom'\n\nfunction normalizeUrlValue(value?: string): string {\n return String(value || '').trim().replace(/\\/+$/, '')\n}\n\nfunction deriveDockerReachableUrl(value: string): string {\n const trimmed = normalizeUrlValue(value)\n try {\n const url = new URL(trimmed)\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n url.hostname = 'host.docker.internal'\n return normalizeUrlValue(url.toString())\n }\n } catch {\n return trimmed\n }\n return trimmed\n}\n\nfunction detectDemoHoneyMode(publicURL: string, scannerURL: string): DemoHoneyMode {\n const publicValue = normalizeUrlValue(publicURL)\n const scannerValue = normalizeUrlValue(scannerURL)\n if (publicValue === HONEY_HOSTED_URL && scannerValue === HONEY_HOSTED_URL) return 'hosted'\n if (publicValue === HONEY_LOCAL_PUBLIC_URL && scannerValue === HONEY_LOCAL_SCANNER_URL) return 'local'\n return 'custom'\n}\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyModeInput, setDemoHoneyModeInput] = useState('hosted')\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState(HONEY_HOSTED_URL)\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState(HONEY_HOSTED_URL)\n const [showDemoNetworking, setShowDemoNetworking] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n const publicURL = settings.demo_honey_public_url || HONEY_HOSTED_URL\n const scannerURL = settings.demo_honey_scanner_url || HONEY_HOSTED_URL\n setDemoHoneyPublicURLInput(publicURL)\n setDemoHoneyScannerURLInput(scannerURL)\n setDemoHoneyModeInput(detectDemoHoneyMode(publicURL, scannerURL))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoHoneyPublicURLInput,\n demo_honey_scanner_url: demoHoneyScannerURLInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n const selectDemoHoneyMode = (mode: DemoHoneyMode) => {\n setDemoHoneyModeInput(mode)\n if (mode === 'hosted') {\n setDemoHoneyPublicURLInput(HONEY_HOSTED_URL)\n setDemoHoneyScannerURLInput(HONEY_HOSTED_URL)\n setShowDemoNetworking(false)\n } else if (mode === 'local') {\n setDemoHoneyPublicURLInput(HONEY_LOCAL_PUBLIC_URL)\n setDemoHoneyScannerURLInput(HONEY_LOCAL_SCANNER_URL)\n } else {\n setShowDemoNetworking(true)\n }\n }\n\n const updateDemoPublicURL = (value: string) => {\n setDemoHoneyPublicURLInput(value)\n if (demoHoneyModeInput === 'local') {\n setDemoHoneyScannerURLInput(deriveDockerReachableUrl(value))\n } else {\n setDemoHoneyModeInput('custom')\n setShowDemoNetworking(true)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n \n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "ff5cd366b7d37573574694cbc21ad167d33ffa10": {"t": 1785764346.9194303, "n": 29573, "h": "67c8cc6c51846a09", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [demoModeEnabledInput, setDemoModeEnabledInput] = useState(false)\n const [demoHoneyPublicURLInput, setDemoHoneyPublicURLInput] = useState('https://honey.shakerscan.com')\n const [demoHoneyScannerURLInput, setDemoHoneyScannerURLInput] = useState('https://honey.shakerscan.com')\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setDemoModeEnabledInput(Boolean(settings.demo_mode_enabled))\n setDemoHoneyPublicURLInput(settings.demo_honey_public_url || 'https://honey.shakerscan.com')\n setDemoHoneyScannerURLInput(settings.demo_honey_scanner_url || 'https://honey.shakerscan.com')\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n demo_mode_enabled: demoModeEnabledInput,\n demo_honey_public_url: demoHoneyPublicURLInput,\n demo_honey_scanner_url: demoHoneyScannerURLInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n Demo mode: {aiSettings.demo_mode_enabled ? 'enabled' : 'disabled'}\n
\n
\n )}\n\n \n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "a5d692b27f84d8687e18b7d3bb83e995171d1f87": {"t": 1785764349.2224607, "n": 27112, "h": "4a60a99f3519fab6", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setScanAPIKeyInput('')\n setClearScanAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: aiURLInput || undefined,\n ai_api_key: scanAPIKeyInput.trim() || undefined,\n ai_model: aiModelInput || undefined,\n ai_fallback_model: aiModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n AI retest: {aiSettings.ai_verify_enabled ? 'enabled' : 'disabled'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n
\n setSettingsMode('basic')}\n className={`px-2.5 py-1 text-xs rounded ${\n settingsMode === 'basic' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'basic'}\n >\n Basic\n \n setSettingsMode('advanced')}\n className={`px-2.5 py-1 text-xs rounded ${\n settingsMode === 'advanced' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'advanced'}\n >\n Advanced\n \n
\n\n
\n

Pipeline flow

\n

\n Scanner tools run first. Optional scan-time AI helps with triage. Retests run deterministic proofs first, then\n optional AI escalation for hard cases.\n

\n
\n\n {settingsMode === 'basic' ? (\n
\n \n \n setScanAPIKeyInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder={aiSettings?.ai_api_key_configured ? 'Configured (enter to replace)' : 'sk-...'}\n />\n \n \n \n setUnifiedVerificationSeverity(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n Need provider/model overrides, proof-gated smart mode, scan-time AI classification, or auto-retest tuning?\n Switch to Advanced.\n

\n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test AI Verification'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n \n
\n ) : (\n
\n \n \n setAIURLInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"https://api.openai.com/v1/chat/completions\"\n />\n \n \n setAIModelInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"gpt-4o-mini\"\n />\n \n \n setAIModelFallbackInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"moonshotai/kimi-k2.5,openai/gpt-4o-mini\"\n />\n \n \n setAIMaskHostInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"example.com\"\n />\n \n \n setScanAPIKeyInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder={aiSettings?.ai_api_key_configured ? 'Configured (enter to replace)' : 'sk-...'}\n />\n \n \n
\n handleTestAISettings('scan')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'scan' ? 'Testing provider...' : 'Test Shared Provider'}\n \n {scanProbeMessage && (\n \n {scanProbeMessage}\n \n )}\n
\n \n\n \n \n \n setAIClassifyMinSeverityInput(e.target.value as Severity)}\n disabled={!aiScanClassificationEnabledInput}\n className={`${INPUT_CLASS} disabled:opacity-50`}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n Recommendation: keep this at medium or higher for lower noise.\n

\n \n\n \n \n

\n Retest AI uses the shared provider settings from Shared Provider.\n

\n \n setAIVerifyMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test Retest AI'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n \n\n \n \n \n setVerificationMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setAIEscalationMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n If proof-required is enabled, smart scans can look quieter because unverified findings are filtered out of\n the primary report.\n

\n \n\n \n \n \n setAutoRetestMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setAutoRetestMaxPerScanInput(e.target.value)}\n className={INPUT_CLASS}\n />\n \n \n
\n )}\n\n
\n \n
\n\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "fb28e0577c52228f1ed9a9ad550f9f26d620e9ac": {"t": 1785764351.5190024, "n": 30149, "h": "d44895a0489ad964", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [verifyAPIKeyInput, setVerifyAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [clearVerifyAPIKey, setClearVerifyAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyURLInput, setAIVerifyURLInput] = useState('')\n const [aiVerifyModelInput, setAIVerifyModelInput] = useState('')\n const [aiVerifyModelFallbackInput, setAIVerifyModelFallbackInput] = useState('')\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [settingsMode, setSettingsMode] = useState<'basic' | 'advanced'>('basic')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyURLInput(settings.ai_verify_url || '')\n setAIVerifyModelInput(settings.ai_verify_model || '')\n setAIVerifyModelFallbackInput(settings.ai_verify_model_fallback || '')\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setScanAPIKeyInput('')\n setVerifyAPIKeyInput('')\n setClearScanAPIKey(false)\n setClearVerifyAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_url: aiVerifyURLInput,\n ai_verify_model: aiVerifyModelInput,\n ai_verify_model_fallback: aiVerifyModelFallbackInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n if (clearVerifyAPIKey) {\n payload.ai_verify_api_key = ''\n } else if (verifyAPIKeyInput.trim()) {\n payload.ai_verify_api_key = verifyAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: scope === 'scan' ? aiURLInput || undefined : aiVerifyURLInput || undefined,\n ai_api_key: scope === 'scan' ? scanAPIKeyInput.trim() || undefined : verifyAPIKeyInput.trim() || undefined,\n ai_model: scope === 'scan' ? aiModelInput || undefined : aiVerifyModelInput || undefined,\n ai_fallback_model:\n scope === 'scan'\n ? aiModelFallbackInput || undefined\n : aiVerifyModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n const setUnifiedVerificationSeverity = (severity: Severity) => {\n setAIVerifyMinSeverityInput(severity)\n setVerificationMinSeverityInput(severity)\n setAIEscalationMinSeverityInput(severity)\n setAutoRetestMinSeverityInput(severity)\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n Retest key: {aiSettings.ai_verify_api_key_configured ? 'configured' : 'not set'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n
\n setSettingsMode('basic')}\n className={`px-2.5 py-1 text-xs rounded ${\n settingsMode === 'basic' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'basic'}\n >\n Basic\n \n setSettingsMode('advanced')}\n className={`px-2.5 py-1 text-xs rounded ${\n settingsMode === 'advanced' ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-700'\n }`}\n aria-pressed={settingsMode === 'advanced'}\n >\n Advanced\n \n
\n\n
\n

Pipeline flow

\n

\n Scanner tools run first. Optional scan-time AI helps with triage. Retests run deterministic proofs first, then\n optional AI escalation for hard cases.\n

\n
\n\n {settingsMode === 'basic' ? (\n
\n \n \n setScanAPIKeyInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder={aiSettings?.ai_api_key_configured ? 'Configured (enter to replace)' : 'sk-...'}\n />\n \n \n \n setUnifiedVerificationSeverity(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n Need provider/model overrides, proof-gated smart mode, scan-time AI classification, or auto-retest tuning?\n Switch to Advanced.\n

\n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test AI Verification'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n \n
\n ) : (\n
\n \n \n setAIURLInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"https://api.openai.com/v1/chat/completions\"\n />\n \n \n setAIModelInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"gpt-4o-mini\"\n />\n \n \n setAIModelFallbackInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"moonshotai/kimi-k2.5,openai/gpt-4o-mini\"\n />\n \n \n setAIMaskHostInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"example.com\"\n />\n \n \n setScanAPIKeyInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder={aiSettings?.ai_api_key_configured ? 'Configured (enter to replace)' : 'sk-...'}\n />\n \n \n
\n handleTestAISettings('scan')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'scan' ? 'Testing provider...' : 'Test Shared Provider'}\n \n {scanProbeMessage && (\n \n {scanProbeMessage}\n \n )}\n
\n \n\n \n \n \n setAIClassifyMinSeverityInput(e.target.value as Severity)}\n disabled={!aiScanClassificationEnabledInput}\n className={`${INPUT_CLASS} disabled:opacity-50`}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n Recommendation: keep this at medium or higher for lower noise.\n

\n \n\n \n \n \n setAIVerifyURLInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"Leave empty to use shared AI URL\"\n />\n \n \n setAIVerifyModelInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"moonshotai/kimi-k2.5\"\n />\n \n \n setAIVerifyModelFallbackInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"openai/gpt-4o-mini,anthropic/claude-3-5-sonnet\"\n />\n \n \n setAIVerifyMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setVerifyAPIKeyInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder={aiSettings?.ai_verify_api_key_configured ? 'Configured (enter to replace)' : 'sk-...'}\n />\n \n \n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test Retest AI'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n \n\n \n \n \n setVerificationMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setAIEscalationMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n If proof-required is enabled, smart scans can look quieter because unverified findings are filtered out of\n the primary report.\n

\n \n\n \n \n \n setAutoRetestMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setAutoRetestMaxPerScanInput(e.target.value)}\n className={INPUT_CLASS}\n />\n \n \n
\n )}\n\n
\n \n
\n\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "90d4f0eb7a071b890818abaa6990691dea0d8b2f": {"t": 1785764354.0799954, "n": 26184, "h": "b7cfe20f8a2b665c", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [verifyAPIKeyInput, setVerifyAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [clearVerifyAPIKey, setClearVerifyAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyURLInput, setAIVerifyURLInput] = useState('')\n const [aiVerifyModelInput, setAIVerifyModelInput] = useState('')\n const [aiVerifyModelFallbackInput, setAIVerifyModelFallbackInput] = useState('')\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(false)\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyURLInput(settings.ai_verify_url || '')\n setAIVerifyModelInput(settings.ai_verify_model || '')\n setAIVerifyModelFallbackInput(settings.ai_verify_model_fallback || '')\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(Boolean(settings.proof_required_for_smart))\n setScanAPIKeyInput('')\n setVerifyAPIKeyInput('')\n setClearScanAPIKey(false)\n setClearVerifyAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_url: aiVerifyURLInput,\n ai_verify_model: aiVerifyModelInput,\n ai_verify_model_fallback: aiVerifyModelFallbackInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n if (clearVerifyAPIKey) {\n payload.ai_verify_api_key = ''\n } else if (verifyAPIKeyInput.trim()) {\n payload.ai_verify_api_key = verifyAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: scope === 'scan' ? aiURLInput || undefined : aiVerifyURLInput || undefined,\n ai_api_key: scope === 'scan' ? scanAPIKeyInput.trim() || undefined : verifyAPIKeyInput.trim() || undefined,\n ai_model: scope === 'scan' ? aiModelInput || undefined : aiVerifyModelInput || undefined,\n ai_fallback_model:\n scope === 'scan'\n ? aiModelFallbackInput || undefined\n : aiVerifyModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Configure scan-time triage, authoritative retest verification, and smart-scan reporting behavior.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n Retest key: {aiSettings.ai_verify_api_key_configured ? 'configured' : 'not set'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n Smart proof filter: {aiSettings.proof_required_for_smart ? 'on' : 'off'}\n
\n
\n )}\n\n
\n

Pipeline flow

\n

\n Scanner tools run first. Optional scan-time AI helps with triage. Retests run deterministic proofs first, then\n optional AI escalation for hard cases.\n

\n
\n\n
\n \n \n setAIURLInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"https://api.openai.com/v1/chat/completions\"\n />\n \n \n setAIModelInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"gpt-4o-mini\"\n />\n \n \n setAIModelFallbackInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"moonshotai/kimi-k2.5,openai/gpt-4o-mini\"\n />\n \n \n setAIMaskHostInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"example.com\"\n />\n \n \n setScanAPIKeyInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder={aiSettings?.ai_api_key_configured ? 'Configured (enter to replace)' : 'sk-...'}\n />\n \n \n
\n handleTestAISettings('scan')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'scan' ? 'Testing provider...' : 'Test Shared Provider'}\n \n {scanProbeMessage && (\n \n {scanProbeMessage}\n \n )}\n
\n \n\n \n \n \n setAIClassifyMinSeverityInput(e.target.value as Severity)}\n disabled={!aiScanClassificationEnabledInput}\n className={`${INPUT_CLASS} disabled:opacity-50`}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n Recommendation: keep this at medium or higher for lower noise.\n

\n \n\n \n \n \n setAIVerifyURLInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"Leave empty to use shared AI URL\"\n />\n \n \n setAIVerifyModelInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"moonshotai/kimi-k2.5\"\n />\n \n \n setAIVerifyModelFallbackInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"openai/gpt-4o-mini,anthropic/claude-3-5-sonnet\"\n />\n \n \n setAIVerifyMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setVerifyAPIKeyInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder={aiSettings?.ai_verify_api_key_configured ? 'Configured (enter to replace)' : 'sk-...'}\n />\n \n \n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test Retest AI'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n \n\n \n \n \n setVerificationMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setAIEscalationMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n If proof-required is enabled, smart scans can look quieter because unverified findings are filtered out of\n the primary report.\n

\n \n\n \n \n \n setAutoRetestMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setAutoRetestMaxPerScanInput(e.target.value)}\n className={INPUT_CLASS}\n />\n \n \n
\n\n
\n \n
\n\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n hint,\n children,\n}: {\n label: string\n hint?: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n\nfunction ToggleRow({\n label,\n description,\n hint,\n checked,\n onChange,\n}: {\n label: string\n description: string\n hint?: string\n checked: boolean\n onChange: (checked: boolean) => void\n}) {\n return (\n \n )\n}\n\nfunction HelpHint({ text }: { text: string }) {\n return (\n \n ?\n \n )\n}\n"}, "dc8472508a244c2510b45970aad68ccd714f7b6c": {"t": 1785764355.59712, "n": 23067, "h": "926315ae578c7581", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nconst SEVERITY_OPTIONS: Severity[] = ['critical', 'high', 'medium', 'low', 'info']\nconst INPUT_CLASS =\n 'mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-blue-500'\nconst CHECKBOX_CLASS =\n 'h-4 w-4 rounded border-gray-700 bg-gray-800 text-blue-600 focus:ring-blue-500'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [verifyAPIKeyInput, setVerifyAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [clearVerifyAPIKey, setClearVerifyAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyURLInput, setAIVerifyURLInput] = useState('')\n const [aiVerifyModelInput, setAIVerifyModelInput] = useState('')\n const [aiVerifyModelFallbackInput, setAIVerifyModelFallbackInput] = useState('')\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(true)\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyURLInput(settings.ai_verify_url || '')\n setAIVerifyModelInput(settings.ai_verify_model || '')\n setAIVerifyModelFallbackInput(settings.ai_verify_model_fallback || '')\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(settings.proof_required_for_smart !== false)\n setScanAPIKeyInput('')\n setVerifyAPIKeyInput('')\n setClearScanAPIKey(false)\n setClearVerifyAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_url: aiVerifyURLInput,\n ai_verify_model: aiVerifyModelInput,\n ai_verify_model_fallback: aiVerifyModelFallbackInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n if (clearVerifyAPIKey) {\n payload.ai_verify_api_key = ''\n } else if (verifyAPIKeyInput.trim()) {\n payload.ai_verify_api_key = verifyAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n\n try {\n const result = await testAISettings({\n scope,\n ai_url: scope === 'scan' ? aiURLInput || undefined : aiVerifyURLInput || undefined,\n ai_api_key: scope === 'scan' ? scanAPIKeyInput.trim() || undefined : verifyAPIKeyInput.trim() || undefined,\n ai_model: scope === 'scan' ? aiModelInput || undefined : aiVerifyModelInput || undefined,\n ai_fallback_model:\n scope === 'scan'\n ? aiModelFallbackInput || undefined\n : aiVerifyModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message =\n result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n if (loading && !aiSettings) {\n return (\n
\n Loading AI settings...\n
\n )\n }\n\n return (\n
\n
\n
\n

AI & Verification Settings

\n

\n Retest verification is authoritative. Scan-time AI classification is optional triage assistance.\n

\n
\n
\n \n Refresh\n \n \n {aiSaving ? 'Saving...' : 'Save'}\n \n
\n
\n\n {aiSettings && (\n
\n
\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'}\n
\n
\n Retest key: {aiSettings.ai_verify_api_key_configured ? 'configured' : 'not set'}\n
\n
\n Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `on (${aiSettings.ai_classify_min_severity}+)` : 'off'}\n
\n
\n )}\n\n
\n \n \n setAIURLInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"https://api.openai.com/v1/chat/completions\"\n />\n \n \n setAIModelInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"gpt-4o-mini\"\n />\n \n \n setAIModelFallbackInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"moonshotai/kimi-k2.5,openai/gpt-4o-mini\"\n />\n \n \n setAIMaskHostInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"example.com\"\n />\n \n \n setScanAPIKeyInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder={aiSettings?.ai_api_key_configured ? 'Configured (enter to replace)' : 'sk-...'}\n />\n \n \n
\n handleTestAISettings('scan')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'scan' ? 'Testing provider...' : 'Test Shared Provider'}\n \n {scanProbeMessage && (\n \n {scanProbeMessage}\n \n )}\n
\n \n\n \n \n \n setAIClassifyMinSeverityInput(e.target.value as Severity)}\n disabled={!aiScanClassificationEnabledInput}\n className={`${INPUT_CLASS} disabled:opacity-50`}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n Recommendation: keep this at medium or higher for lower noise.\n

\n \n\n \n \n \n setAIVerifyURLInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"Leave empty to use shared AI URL\"\n />\n \n \n setAIVerifyModelInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"moonshotai/kimi-k2.5\"\n />\n \n \n setAIVerifyModelFallbackInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder=\"openai/gpt-4o-mini,anthropic/claude-3-5-sonnet\"\n />\n \n \n setAIVerifyMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setVerifyAPIKeyInput(e.target.value)}\n className={INPUT_CLASS}\n placeholder={aiSettings?.ai_verify_api_key_configured ? 'Configured (enter to replace)' : 'sk-...'}\n />\n \n \n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test Retest AI'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n \n\n \n \n \n setVerificationMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setAIEscalationMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n

\n If proof-required is enabled, smart scans may show fewer findings and fewer attack chains unless exploit\n evidence is collected.\n

\n \n\n \n \n \n setAutoRetestMinSeverityInput(e.target.value as Severity)}\n className={INPUT_CLASS}\n >\n {SEVERITY_OPTIONS.map((sev) => (\n \n ))}\n \n \n \n setAutoRetestMaxPerScanInput(e.target.value)}\n className={INPUT_CLASS}\n />\n \n \n
\n\n
\n \n
\n\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n\nfunction SectionCard({\n title,\n description,\n children,\n}: {\n title: string\n description: string\n children: React.ReactNode\n}) {\n return (\n
\n
\n

{title}

\n

{description}

\n
\n {children}\n
\n )\n}\n\nfunction Field({\n label,\n children,\n}: {\n label: string\n children: React.ReactNode\n}) {\n return (\n \n )\n}\n"}, "5aa488830b1c845080554d1b7868f16898a0b2b0": {"t": 1785764357.1959023, "n": 24762, "h": "c414de7f51aa8bdd", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [verifyAPIKeyInput, setVerifyAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [clearVerifyAPIKey, setClearVerifyAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyURLInput, setAIVerifyURLInput] = useState('')\n const [aiVerifyModelInput, setAIVerifyModelInput] = useState('')\n const [aiVerifyModelFallbackInput, setAIVerifyModelFallbackInput] = useState('')\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(true)\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyURLInput(settings.ai_verify_url || '')\n setAIVerifyModelInput(settings.ai_verify_model || '')\n setAIVerifyModelFallbackInput(settings.ai_verify_model_fallback || '')\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput(settings.verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput(settings.ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput(settings.proof_required_for_smart !== false)\n setScanAPIKeyInput('')\n setVerifyAPIKeyInput('')\n setClearScanAPIKey(false)\n setClearVerifyAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_url: aiVerifyURLInput,\n ai_verify_model: aiVerifyModelInput,\n ai_verify_model_fallback: aiVerifyModelFallbackInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n if (clearVerifyAPIKey) {\n payload.ai_verify_api_key = ''\n } else if (verifyAPIKeyInput.trim()) {\n payload.ai_verify_api_key = verifyAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n try {\n const result = await testAISettings({\n scope,\n ai_url: scope === 'scan' ? aiURLInput || undefined : aiVerifyURLInput || undefined,\n ai_api_key: scope === 'scan' ? scanAPIKeyInput.trim() || undefined : verifyAPIKeyInput.trim() || undefined,\n ai_model: scope === 'scan' ? aiModelInput || undefined : aiVerifyModelInput || undefined,\n ai_fallback_model:\n scope === 'scan'\n ? aiModelFallbackInput || undefined\n : aiVerifyModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message = result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n return (\n
\n
\n
\n

AI Settings

\n

\n Retest AI verification is authoritative. Scan-time AI classification is optional pre-triage.\n Changes apply to new jobs immediately.\n

\n
\n \n Refresh\n \n
\n\n
\n
\n

Shared Provider + Optional Scan Classification

\n

\n Used as the shared provider defaults. Scan-time AI classification runs only when enabled below.\n

\n \n \n \n \n \n \n \n \n
\n handleTestAISettings('scan')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'scan' ? 'Testing shared provider...' : 'Test Shared Provider'}\n \n {scanProbeMessage && (\n \n {scanProbeMessage}\n \n )}\n
\n
\n\n
\n

Retest AI Verification (Authoritative)

\n \n \n \n \n \n \n \n
\n

Auto Retest Policy

\n \n \n \n
\n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test Retest AI'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n
\n

Verification Policy

\n \n \n \n
\n
\n
\n\n
\n \n \n {aiSaving ? 'Saving...' : 'Save AI Settings'}\n \n
\n\n {aiSettings && (\n

\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'} · Retest key:{' '}\n {aiSettings.ai_verify_api_key_configured ? 'configured' : 'not set'} · Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `enabled (${aiSettings.ai_classify_min_severity}+)` : 'disabled'}\n

\n )}\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n"}, "4d20dab13f3fe174473880fde9b5a1e1db6881ce": {"t": 1785764358.6754692, "n": 24879, "h": "91077273da9f5d8b", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [verifyAPIKeyInput, setVerifyAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [clearVerifyAPIKey, setClearVerifyAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyURLInput, setAIVerifyURLInput] = useState('')\n const [aiVerifyModelInput, setAIVerifyModelInput] = useState('')\n const [aiVerifyModelFallbackInput, setAIVerifyModelFallbackInput] = useState('')\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [verificationMinSeverityInput, setVerificationMinSeverityInput] = useState('medium')\n const [aiEscalationMinSeverityInput, setAIEscalationMinSeverityInput] = useState('high')\n const [proofRequiredForSmartInput, setProofRequiredForSmartInput] = useState(true)\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyURLInput(settings.ai_verify_url || '')\n setAIVerifyModelInput(settings.ai_verify_model || '')\n setAIVerifyModelFallbackInput(settings.ai_verify_model_fallback || '')\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setVerificationMinSeverityInput((settings as Record).verification_min_severity || settings.auto_retest_min_severity || 'medium')\n setAIEscalationMinSeverityInput((settings as Record).ai_escalation_min_severity || settings.ai_verify_min_severity || 'high')\n setProofRequiredForSmartInput((settings as Record).proof_required_for_smart !== false)\n setScanAPIKeyInput('')\n setVerifyAPIKeyInput('')\n setClearScanAPIKey(false)\n setClearVerifyAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_url: aiVerifyURLInput,\n ai_verify_model: aiVerifyModelInput,\n ai_verify_model_fallback: aiVerifyModelFallbackInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n verification_min_severity: verificationMinSeverityInput,\n ai_escalation_min_severity: aiEscalationMinSeverityInput,\n proof_required_for_smart: proofRequiredForSmartInput,\n persist_to_env: persistAIToEnv,\n } as AISettingsUpdate & Record\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n if (clearVerifyAPIKey) {\n payload.ai_verify_api_key = ''\n } else if (verifyAPIKeyInput.trim()) {\n payload.ai_verify_api_key = verifyAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n try {\n const result = await testAISettings({\n scope,\n ai_url: scope === 'scan' ? aiURLInput || undefined : aiVerifyURLInput || undefined,\n ai_api_key: scope === 'scan' ? scanAPIKeyInput.trim() || undefined : verifyAPIKeyInput.trim() || undefined,\n ai_model: scope === 'scan' ? aiModelInput || undefined : aiVerifyModelInput || undefined,\n ai_fallback_model:\n scope === 'scan'\n ? aiModelFallbackInput || undefined\n : aiVerifyModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message = result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n return (\n
\n
\n
\n

AI Settings

\n

\n Retest AI verification is authoritative. Scan-time AI classification is optional pre-triage.\n Changes apply to new jobs immediately.\n

\n
\n \n Refresh\n \n
\n\n
\n
\n

Shared Provider + Optional Scan Classification

\n

\n Used as the shared provider defaults. Scan-time AI classification runs only when enabled below.\n

\n \n \n \n \n \n \n \n \n
\n handleTestAISettings('scan')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'scan' ? 'Testing shared provider...' : 'Test Shared Provider'}\n \n {scanProbeMessage && (\n \n {scanProbeMessage}\n \n )}\n
\n
\n\n
\n

Retest AI Verification (Authoritative)

\n \n \n \n \n \n \n \n
\n

Auto Retest Policy

\n \n \n \n
\n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test Retest AI'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n
\n

Verification Policy

\n \n \n \n
\n
\n
\n\n
\n \n \n {aiSaving ? 'Saving...' : 'Save AI Settings'}\n \n
\n\n {aiSettings && (\n

\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'} · Retest key:{' '}\n {aiSettings.ai_verify_api_key_configured ? 'configured' : 'not set'} · Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `enabled (${aiSettings.ai_classify_min_severity}+)` : 'disabled'}\n

\n )}\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n"}, "dbe37be761e3ffd134f7ec26d58ce93714244995": {"t": 1785764360.1948383, "n": 21695, "h": "a890468e45e16d24", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [verifyAPIKeyInput, setVerifyAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [clearVerifyAPIKey, setClearVerifyAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiScanClassificationEnabledInput, setAIScanClassificationEnabledInput] = useState(false)\n const [aiClassifyMinSeverityInput, setAIClassifyMinSeverityInput] = useState('high')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyURLInput, setAIVerifyURLInput] = useState('')\n const [aiVerifyModelInput, setAIVerifyModelInput] = useState('')\n const [aiVerifyModelFallbackInput, setAIVerifyModelFallbackInput] = useState('')\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIScanClassificationEnabledInput(Boolean(settings.ai_scan_classification_enabled))\n setAIClassifyMinSeverityInput(settings.ai_classify_min_severity || settings.ai_verify_min_severity || 'high')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyURLInput(settings.ai_verify_url || '')\n setAIVerifyModelInput(settings.ai_verify_model || '')\n setAIVerifyModelFallbackInput(settings.ai_verify_model_fallback || '')\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setScanAPIKeyInput('')\n setVerifyAPIKeyInput('')\n setClearScanAPIKey(false)\n setClearVerifyAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_scan_classification_enabled: aiScanClassificationEnabledInput,\n ai_classify_min_severity: aiClassifyMinSeverityInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_url: aiVerifyURLInput,\n ai_verify_model: aiVerifyModelInput,\n ai_verify_model_fallback: aiVerifyModelFallbackInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n if (clearVerifyAPIKey) {\n payload.ai_verify_api_key = ''\n } else if (verifyAPIKeyInput.trim()) {\n payload.ai_verify_api_key = verifyAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n try {\n const result = await testAISettings({\n scope,\n ai_url: scope === 'scan' ? aiURLInput || undefined : aiVerifyURLInput || undefined,\n ai_api_key: scope === 'scan' ? scanAPIKeyInput.trim() || undefined : verifyAPIKeyInput.trim() || undefined,\n ai_model: scope === 'scan' ? aiModelInput || undefined : aiVerifyModelInput || undefined,\n ai_fallback_model:\n scope === 'scan'\n ? aiModelFallbackInput || undefined\n : aiVerifyModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message = result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n return (\n
\n
\n
\n

AI Settings

\n

\n Retest AI verification is authoritative. Scan-time AI classification is optional pre-triage.\n Changes apply to new jobs immediately.\n

\n
\n \n Refresh\n \n
\n\n
\n
\n

Shared Provider + Optional Scan Classification

\n

\n Used as the shared provider defaults. Scan-time AI classification runs only when enabled below.\n

\n \n \n \n \n \n \n \n \n
\n handleTestAISettings('scan')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'scan' ? 'Testing shared provider...' : 'Test Shared Provider'}\n \n {scanProbeMessage && (\n \n {scanProbeMessage}\n \n )}\n
\n
\n\n
\n

Retest AI Verification (Authoritative)

\n \n \n \n \n \n \n \n
\n

Auto Retest Policy

\n \n \n \n
\n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test Retest AI'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n
\n
\n\n
\n \n \n {aiSaving ? 'Saving...' : 'Save AI Settings'}\n \n
\n\n {aiSettings && (\n

\n Shared key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'} · Retest key:{' '}\n {aiSettings.ai_verify_api_key_configured ? 'configured' : 'not set'} · Scan classification:{' '}\n {aiSettings.ai_scan_classification_enabled ? `enabled (${aiSettings.ai_classify_min_severity}+)` : 'disabled'}\n

\n )}\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n"}, "364c9421c5be23b87ba33a62e41ed468c917fdc7": {"t": 1785764361.586232, "n": 19396, "h": "d34f42a9f76953bb", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [verifyAPIKeyInput, setVerifyAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [clearVerifyAPIKey, setClearVerifyAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyURLInput, setAIVerifyURLInput] = useState('')\n const [aiVerifyModelInput, setAIVerifyModelInput] = useState('')\n const [aiVerifyModelFallbackInput, setAIVerifyModelFallbackInput] = useState('')\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [autoRetestEnabledInput, setAutoRetestEnabledInput] = useState(true)\n const [autoRetestMinSeverityInput, setAutoRetestMinSeverityInput] = useState('medium')\n const [autoRetestMaxPerScanInput, setAutoRetestMaxPerScanInput] = useState('25')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyURLInput(settings.ai_verify_url || '')\n setAIVerifyModelInput(settings.ai_verify_model || '')\n setAIVerifyModelFallbackInput(settings.ai_verify_model_fallback || '')\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setAutoRetestEnabledInput(Boolean(settings.auto_retest_on_scan_complete))\n setAutoRetestMinSeverityInput(settings.auto_retest_min_severity || 'medium')\n setAutoRetestMaxPerScanInput(String(settings.auto_retest_max_per_scan ?? 25))\n setScanAPIKeyInput('')\n setVerifyAPIKeyInput('')\n setClearScanAPIKey(false)\n setClearVerifyAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_url: aiVerifyURLInput,\n ai_verify_model: aiVerifyModelInput,\n ai_verify_model_fallback: aiVerifyModelFallbackInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n auto_retest_on_scan_complete: autoRetestEnabledInput,\n auto_retest_min_severity: autoRetestMinSeverityInput,\n auto_retest_max_per_scan: Math.max(0, Number.parseInt(autoRetestMaxPerScanInput || '0', 10) || 0),\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n if (clearVerifyAPIKey) {\n payload.ai_verify_api_key = ''\n } else if (verifyAPIKeyInput.trim()) {\n payload.ai_verify_api_key = verifyAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n try {\n const result = await testAISettings({\n scope,\n ai_url: scope === 'scan' ? aiURLInput || undefined : aiVerifyURLInput || undefined,\n ai_api_key: scope === 'scan' ? scanAPIKeyInput.trim() || undefined : verifyAPIKeyInput.trim() || undefined,\n ai_model: scope === 'scan' ? aiModelInput || undefined : aiVerifyModelInput || undefined,\n ai_fallback_model:\n scope === 'scan'\n ? aiModelFallbackInput || undefined\n : aiVerifyModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message = result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n return (\n
\n
\n
\n

AI Settings

\n

\n Configure scan AI and retest AI verification. Changes apply to new jobs immediately.\n

\n
\n \n Refresh\n \n
\n\n
\n
\n

Scan AI

\n \n \n \n \n \n \n
\n handleTestAISettings('scan')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'scan' ? 'Testing scan AI...' : 'Test Scan AI'}\n \n {scanProbeMessage && (\n \n {scanProbeMessage}\n \n )}\n
\n
\n\n
\n

Retest AI Verification

\n \n \n \n \n \n \n \n
\n

Auto Retest Policy

\n \n \n \n
\n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test Retest AI'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n
\n
\n\n
\n \n \n {aiSaving ? 'Saving...' : 'Save AI Settings'}\n \n
\n\n {aiSettings && (\n

\n Scan key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'} · Retest key:{' '}\n {aiSettings.ai_verify_api_key_configured ? 'configured' : 'not set'}\n

\n )}\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n"}, "044b667c9221a1325b6ccc40d2201d8ef6c05b5e": {"t": 1785764363.3685148, "n": 16681, "h": "5e61c77e717dd31f", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n testAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [verifyAPIKeyInput, setVerifyAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [clearVerifyAPIKey, setClearVerifyAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiModelFallbackInput, setAIModelFallbackInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyURLInput, setAIVerifyURLInput] = useState('')\n const [aiVerifyModelInput, setAIVerifyModelInput] = useState('')\n const [aiVerifyModelFallbackInput, setAIVerifyModelFallbackInput] = useState('')\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n const [testingScope, setTestingScope] = useState<'scan' | 'verify' | null>(null)\n const [scanProbeMessage, setScanProbeMessage] = useState(null)\n const [verifyProbeMessage, setVerifyProbeMessage] = useState(null)\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIModelFallbackInput(settings.ai_model_fallback || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyURLInput(settings.ai_verify_url || '')\n setAIVerifyModelInput(settings.ai_verify_model || '')\n setAIVerifyModelFallbackInput(settings.ai_verify_model_fallback || '')\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setScanAPIKeyInput('')\n setVerifyAPIKeyInput('')\n setClearScanAPIKey(false)\n setClearVerifyAPIKey(false)\n setScanProbeMessage(null)\n setVerifyProbeMessage(null)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_model_fallback: aiModelFallbackInput,\n ai_mask_host: aiMaskHostInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_url: aiVerifyURLInput,\n ai_verify_model: aiVerifyModelInput,\n ai_verify_model_fallback: aiVerifyModelFallbackInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n if (clearVerifyAPIKey) {\n payload.ai_verify_api_key = ''\n } else if (verifyAPIKeyInput.trim()) {\n payload.ai_verify_api_key = verifyAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n const handleTestAISettings = async (scope: 'scan' | 'verify') => {\n if (aiSaving || testingScope) return\n setAISettingsError(null)\n if (scope === 'scan') {\n setScanProbeMessage(null)\n } else {\n setVerifyProbeMessage(null)\n }\n setTestingScope(scope)\n try {\n const result = await testAISettings({\n scope,\n ai_url: scope === 'scan' ? aiURLInput || undefined : aiVerifyURLInput || undefined,\n ai_api_key: scope === 'scan' ? scanAPIKeyInput.trim() || undefined : verifyAPIKeyInput.trim() || undefined,\n ai_model: scope === 'scan' ? aiModelInput || undefined : aiVerifyModelInput || undefined,\n ai_fallback_model:\n scope === 'scan'\n ? aiModelFallbackInput || undefined\n : aiVerifyModelFallbackInput || undefined,\n })\n\n const usedModel = String(result.probe?.provider_meta?.model_used || '').trim()\n const latency = result.probe?.latency_ms\n const detail = usedModel ? `model ${usedModel}` : 'provider responded'\n const latencyLabel = typeof latency === 'number' ? ` in ${latency}ms` : ''\n const message = result.status === 'ok'\n ? `Probe succeeded (${detail}${latencyLabel}).`\n : `Probe failed: ${result.probe?.error || 'unknown error'}`\n\n if (scope === 'scan') {\n setScanProbeMessage(message)\n } else {\n setVerifyProbeMessage(message)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to test AI settings'\n if (scope === 'scan') {\n setScanProbeMessage(`Probe failed: ${message}`)\n } else {\n setVerifyProbeMessage(`Probe failed: ${message}`)\n }\n setAISettingsError(message)\n } finally {\n setTestingScope(null)\n }\n }\n\n return (\n
\n
\n
\n

AI Settings

\n

\n Configure scan AI and retest AI verification. Changes apply to new jobs immediately.\n

\n
\n \n Refresh\n \n
\n\n
\n
\n

Scan AI

\n \n \n \n \n \n \n
\n handleTestAISettings('scan')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'scan' ? 'Testing scan AI...' : 'Test Scan AI'}\n \n {scanProbeMessage && (\n \n {scanProbeMessage}\n \n )}\n
\n
\n\n
\n

Retest AI Verification

\n \n \n \n \n \n \n \n
\n handleTestAISettings('verify')}\n disabled={loading || aiSaving || testingScope !== null}\n className=\"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 text-white rounded text-xs\"\n >\n {testingScope === 'verify' ? 'Testing retest AI...' : 'Test Retest AI'}\n \n {verifyProbeMessage && (\n \n {verifyProbeMessage}\n \n )}\n
\n
\n
\n\n
\n \n \n {aiSaving ? 'Saving...' : 'Save AI Settings'}\n \n
\n\n {aiSettings && (\n

\n Scan key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'} · Retest key:{' '}\n {aiSettings.ai_verify_api_key_configured ? 'configured' : 'not set'}\n

\n )}\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n"}, "e878caf430484e9e2eeea134441e6a6b9b0d39e7": {"t": 1785764366.0494452, "n": 11670, "h": "fb97d496d8d89f56", "c": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport {\n getAISettings,\n updateAISettings,\n type AISettings,\n type AISettingsUpdate,\n} from '@/lib/api'\n\ntype Severity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nexport default function AISettingsPanel() {\n const [aiSettings, setAISettings] = useState(null)\n const [aiSettingsError, setAISettingsError] = useState(null)\n const [aiSettingsMessage, setAISettingsMessage] = useState(null)\n const [aiSaving, setAISaving] = useState(false)\n const [loading, setLoading] = useState(true)\n const [scanAPIKeyInput, setScanAPIKeyInput] = useState('')\n const [verifyAPIKeyInput, setVerifyAPIKeyInput] = useState('')\n const [clearScanAPIKey, setClearScanAPIKey] = useState(false)\n const [clearVerifyAPIKey, setClearVerifyAPIKey] = useState(false)\n const [persistAIToEnv, setPersistAIToEnv] = useState(false)\n const [aiURLInput, setAIURLInput] = useState('')\n const [aiModelInput, setAIModelInput] = useState('')\n const [aiMaskHostInput, setAIMaskHostInput] = useState('')\n const [aiVerifyEnabledInput, setAIVerifyEnabledInput] = useState(false)\n const [aiVerifyURLInput, setAIVerifyURLInput] = useState('')\n const [aiVerifyModelInput, setAIVerifyModelInput] = useState('')\n const [aiVerifyMinSeverityInput, setAIVerifyMinSeverityInput] = useState('high')\n\n const applyAISettingsToForm = (settings: AISettings) => {\n setAIURLInput(settings.ai_url || '')\n setAIModelInput(settings.ai_model || '')\n setAIMaskHostInput(settings.ai_mask_host || '')\n setAIVerifyEnabledInput(Boolean(settings.ai_verify_enabled))\n setAIVerifyURLInput(settings.ai_verify_url || '')\n setAIVerifyModelInput(settings.ai_verify_model || '')\n setAIVerifyMinSeverityInput(settings.ai_verify_min_severity || 'high')\n setScanAPIKeyInput('')\n setVerifyAPIKeyInput('')\n setClearScanAPIKey(false)\n setClearVerifyAPIKey(false)\n }\n\n const fetchAISettings = async () => {\n try {\n const settings = await getAISettings()\n setAISettings(settings)\n setAISettingsError(null)\n applyAISettingsToForm(settings)\n } catch {\n setAISettingsError('AI settings unavailable')\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n fetchAISettings()\n }, [])\n\n const handleSaveAISettings = async () => {\n if (aiSaving) return\n setAISettingsError(null)\n setAISettingsMessage(null)\n setAISaving(true)\n\n try {\n const payload: AISettingsUpdate = {\n ai_url: aiURLInput,\n ai_model: aiModelInput,\n ai_mask_host: aiMaskHostInput,\n ai_verify_enabled: aiVerifyEnabledInput,\n ai_verify_url: aiVerifyURLInput,\n ai_verify_model: aiVerifyModelInput,\n ai_verify_min_severity: aiVerifyMinSeverityInput,\n persist_to_env: persistAIToEnv,\n }\n\n if (clearScanAPIKey) {\n payload.ai_api_key = ''\n } else if (scanAPIKeyInput.trim()) {\n payload.ai_api_key = scanAPIKeyInput.trim()\n }\n\n if (clearVerifyAPIKey) {\n payload.ai_verify_api_key = ''\n } else if (verifyAPIKeyInput.trim()) {\n payload.ai_verify_api_key = verifyAPIKeyInput.trim()\n }\n\n const result = await updateAISettings(payload)\n setAISettings(result.settings)\n applyAISettingsToForm(result.settings)\n setAISettingsMessage(\n result.persisted_to_env\n ? result.persist_message || 'AI settings updated and persisted to .env'\n : 'AI runtime settings updated'\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update AI settings'\n setAISettingsError(message)\n } finally {\n setAISaving(false)\n }\n }\n\n return (\n
\n
\n
\n

AI Settings

\n

\n Configure scan AI and retest AI verification. Changes apply to new jobs immediately.\n

\n
\n \n Refresh\n \n
\n\n
\n
\n

Scan AI

\n \n \n \n \n \n
\n\n
\n

Retest AI Verification

\n \n \n \n \n \n \n
\n
\n\n
\n \n \n {aiSaving ? 'Saving...' : 'Save AI Settings'}\n \n
\n\n {aiSettings && (\n

\n Scan key: {aiSettings.ai_api_key_configured ? 'configured' : 'not set'} · Retest key:{' '}\n {aiSettings.ai_verify_api_key_configured ? 'configured' : 'not set'}\n

\n )}\n {aiSettingsMessage &&

{aiSettingsMessage}

}\n {aiSettingsError &&

{aiSettingsError}

}\n
\n )\n}\n"}}}}