"use client"; import { useEffect, useState } from "react"; import { trpc } from "~/lib/trpc/client.js"; import { AiProviderPanel, GenerationSettingsPanel, } from "./system-settings/AiSettingsPanels.js"; import { LegacyRuntimeSecretsNotice } from "./system-settings/LegacyRuntimeSecretsNotice.js"; import { type ImageProvider, type Provider, type ScoreWeights, type SaveResult, type SystemRole, type UrlParsedType, } from "./system-settings/shared.js"; import { AnonymizationSettingsPanel } from "./system-settings/AnonymizationSettingsPanel.js"; import { ImageGenerationPanel } from "./system-settings/ImageGenerationPanel.js"; import { SmtpSettingsPanel } from "./system-settings/SmtpSettingsPanel.js"; import { TimelineSettingsPanel } from "./system-settings/TimelineSettingsPanel.js"; import { VacationSettingsPanel } from "./system-settings/VacationSettingsPanel.js"; import { ValueScorePanel } from "./system-settings/ValueScorePanel.js"; import { parseAzureUrl, type GeminiTestResult } from "./system-settings/shared.js"; export function SystemSettingsClient() { const [provider, setProvider] = useState("openai"); const [endpoint, setEndpoint] = useState(""); const [model, setModel] = useState(""); const [apiVersion, setApiVersion] = useState("2025-01-01-preview"); const [maxTokens, setMaxTokens] = useState(2000); const [temperature, setTemperature] = useState(1); const [summaryPrompt, setSummaryPrompt] = useState(""); const [saved, setSaved] = useState(false); const [testResult, setTestResult] = useState(null); const [urlPasteValue, setUrlPasteValue] = useState(""); const [urlParseError, setUrlParseError] = useState(false); const [urlParsedType, setUrlParsedType] = useState(null); const [scoreWeights, setScoreWeights] = useState({ skillDepth: 0.3, skillBreadth: 0.15, costEfficiency: 0.25, chargeability: 0.15, experience: 0.15, }); const [scoreVisibleRoles, setScoreVisibleRoles] = useState(["ADMIN", "MANAGER"]); const [scoreSaved, setScoreSaved] = useState(false); const [recomputeResult, setRecomputeResult] = useState<{ updated: number } | null>(null); const [dalleDeployment, setDalleDeployment] = useState(""); const [dalleEndpoint, setDalleEndpoint] = useState(""); const [imageProvider, setImageProvider] = useState("dalle"); const [geminiModel, setGeminiModel] = useState(""); const [imageSaved, setImageSaved] = useState(false); const [smtpHost, setSmtpHost] = useState(""); const [smtpPort, setSmtpPort] = useState(587); const [smtpUser, setSmtpUser] = useState(""); const [smtpFrom, setSmtpFrom] = useState(""); const [smtpTls, setSmtpTls] = useState(true); const [smtpSaved, setSmtpSaved] = useState(false); const [smtpTestResult, setSmtpTestResult] = useState(null); const [anonymizationEnabled, setAnonymizationEnabled] = useState(false); const [anonymizationDomain, setAnonymizationDomain] = useState("superhartmut.de"); const [anonymizationSaved, setAnonymizationSaved] = useState(false); const [vacationDefaultDays, setVacationDefaultDays] = useState(28); const [vacationSaved, setVacationSaved] = useState(false); const [undoMaxSteps, setUndoMaxSteps] = useState(50); const [timelineSaved, setTimelineSaved] = useState(false); const [legacyCleanupResult, setLegacyCleanupResult] = useState(null); const [geminiTestResult, setGeminiTestResult] = useState(null); const utils = trpc.useUtils(); const { data: settings, isLoading } = trpc.settings.getSystemSettings.useQuery(undefined, { staleTime: 0, }); useEffect(() => { if (!settings) { return; } setProvider((settings.aiProvider ?? "openai") as Provider); setEndpoint(settings.azureOpenAiEndpoint ?? ""); setModel(settings.azureOpenAiDeployment ?? ""); setApiVersion(settings.azureApiVersion ?? "2025-01-01-preview"); setMaxTokens(settings.aiMaxCompletionTokens ?? 2000); setTemperature(settings.aiTemperature ?? 1); setSummaryPrompt(settings.aiSummaryPrompt ?? ""); if (settings.scoreWeights) { setScoreWeights(settings.scoreWeights as ScoreWeights); } if (settings.scoreVisibleRoles) { setScoreVisibleRoles(settings.scoreVisibleRoles as SystemRole[]); } setDalleDeployment(settings.azureDalleDeployment ?? ""); setDalleEndpoint(settings.azureDalleEndpoint ?? ""); setImageProvider((settings.imageProvider ?? "dalle") as ImageProvider); setGeminiModel(settings.geminiModel ?? ""); setSmtpHost(settings.smtpHost ?? ""); setSmtpPort(settings.smtpPort ?? 587); setSmtpUser(settings.smtpUser ?? ""); setSmtpFrom(settings.smtpFrom ?? ""); setSmtpTls(settings.smtpTls ?? true); setAnonymizationEnabled(settings.anonymizationEnabled ?? false); setAnonymizationDomain(settings.anonymizationDomain ?? "superhartmut.de"); setVacationDefaultDays(settings.vacationDefaultDays ?? 28); setUndoMaxSteps(settings.timelineUndoMaxSteps ?? 50); }, [settings]); function invalidateSystemSettings() { void utils.settings.getSystemSettings.invalidate(); } function handleUrlPaste(raw: string) { setUrlPasteValue(raw); if (!raw) { setUrlParseError(false); setUrlParsedType(null); return; } const parsed = parseAzureUrl(raw); if (!parsed) { setUrlParseError(true); setUrlParsedType(null); return; } setEndpoint(parsed.endpoint); setApiVersion(parsed.apiVersion); if (parsed.deployment) { setModel(parsed.deployment); } setUrlParseError(false); setUrlParsedType(parsed.urlType); setUrlPasteValue(""); } const updateMutation = trpc.settings.updateSystemSettings.useMutation({ onSuccess: () => { setSaved(true); setTestResult(null); setLegacyCleanupResult(null); invalidateSystemSettings(); setTimeout(() => setSaved(false), 3000); }, }); const testMutation = trpc.settings.testAiConnection.useMutation({ onSuccess: (data) => setTestResult(data), onError: (error) => setTestResult({ ok: false, error: error.message }), }); const saveScoreMutation = trpc.settings.updateSystemSettings.useMutation({ onSuccess: () => { setScoreSaved(true); invalidateSystemSettings(); setTimeout(() => setScoreSaved(false), 3000); }, }); const recomputeMutation = trpc.resource.recomputeValueScores.useMutation({ onSuccess: (data) => setRecomputeResult(data), }); const saveSmtpMutation = trpc.settings.updateSystemSettings.useMutation({ onSuccess: () => { setSmtpSaved(true); setSmtpTestResult(null); setLegacyCleanupResult(null); invalidateSystemSettings(); setTimeout(() => setSmtpSaved(false), 3000); }, }); const testSmtpMutation = trpc.settings.testSmtpConnection.useMutation({ onSuccess: (data) => setSmtpTestResult(data), onError: (error) => setSmtpTestResult({ ok: false, error: error.message }), }); const saveAnonymizationMutation = trpc.settings.updateSystemSettings.useMutation({ onSuccess: () => { setAnonymizationSaved(true); setLegacyCleanupResult(null); invalidateSystemSettings(); setTimeout(() => setAnonymizationSaved(false), 3000); }, }); const saveVacationMutation = trpc.settings.updateSystemSettings.useMutation({ onSuccess: () => { setVacationSaved(true); invalidateSystemSettings(); setTimeout(() => setVacationSaved(false), 3000); }, }); const saveTimelineMutation = trpc.settings.updateSystemSettings.useMutation({ onSuccess: () => { setTimelineSaved(true); invalidateSystemSettings(); setTimeout(() => setTimelineSaved(false), 3000); }, }); const saveImageMutation = trpc.settings.updateSystemSettings.useMutation({ onSuccess: () => { setImageSaved(true); setLegacyCleanupResult(null); invalidateSystemSettings(); setTimeout(() => setImageSaved(false), 3000); }, }); const clearRuntimeSecretsMutation = trpc.settings.clearStoredRuntimeSecrets.useMutation({ onSuccess: (data) => { setLegacyCleanupResult( data.clearedFields.length > 0 ? `Cleared ${data.clearedFields.length} legacy database secret field${data.clearedFields.length === 1 ? "" : "s"}.` : "No legacy database secrets were left to clear.", ); invalidateSystemSettings(); }, onError: (error) => setLegacyCleanupResult(error.message), }); const testGeminiMutation = trpc.settings.testGeminiConnection.useMutation({ onSuccess: (data) => setGeminiTestResult(data as GeminiTestResult), onError: (error) => setGeminiTestResult({ ok: false, error: error.message }), }); function handleSave() { updateMutation.mutate({ aiProvider: provider, azureOpenAiEndpoint: provider === "azure" ? endpoint : "", azureOpenAiDeployment: model, azureApiVersion: provider === "azure" ? apiVersion : undefined, aiMaxCompletionTokens: maxTokens, aiTemperature: temperature, aiSummaryPrompt: summaryPrompt || undefined, }); } function handleSaveScoreSettings() { saveScoreMutation.mutate({ scoreWeights, scoreVisibleRoles }); } function handleSaveImage() { saveImageMutation.mutate({ imageProvider, azureDalleDeployment: dalleDeployment || undefined, azureDalleEndpoint: provider === "azure" && dalleEndpoint ? dalleEndpoint : undefined, geminiModel: geminiModel || undefined, }); } function handleSaveSmtp() { saveSmtpMutation.mutate({ smtpHost: smtpHost || undefined, smtpPort, smtpUser: smtpUser || undefined, smtpFrom: smtpFrom || undefined, smtpTls, }); } function handleSaveVacation() { saveVacationMutation.mutate({ vacationDefaultDays }); } function handleSaveTimeline() { saveTimelineMutation.mutate({ timelineUndoMaxSteps: undoMaxSteps }); } function handleSaveAnonymization() { saveAnonymizationMutation.mutate({ anonymizationEnabled, anonymizationDomain: anonymizationDomain.trim() || "superhartmut.de", anonymizationMode: "global", }); } function updateWeight(key: keyof ScoreWeights, value: number) { setScoreWeights((previous) => ({ ...previous, [key]: value })); } function toggleRole(role: SystemRole) { setScoreVisibleRoles((previous) => previous.includes(role) ? previous.filter((entry) => entry !== role) : [...previous, role], ); } function handleClearLegacyRuntimeSecrets() { if ( typeof window !== "undefined" && !window.confirm( "Clear all legacy runtime secrets from database storage? Environment-based deployment secrets must already be configured.", ) ) { return; } clearRuntimeSecretsMutation.mutate(); } const weightSum = Object.values(scoreWeights).reduce((sum, value) => sum + value, 0); const weightSumOk = Math.abs(weightSum - 1.0) < 0.01; if (isLoading) { return (
); } if (!settings) { return (
System settings could not be loaded.
); } return (

System Settings

Configure AI integration for skill profile generation.

{ setProvider(nextProvider); setTestResult(null); }} onEndpointChange={setEndpoint} onModelChange={setModel} onApiVersionChange={setApiVersion} onUrlPaste={handleUrlPaste} onSave={handleSave} onTest={() => { setTestResult(null); testMutation.mutate(); }} /> setSummaryPrompt("")} />
{ setRecomputeResult(null); recomputeMutation.mutate(); }} onSave={handleSaveScoreSettings} /> testGeminiMutation.mutate()} /> testSmtpMutation.mutate()} />
); }