1<script lang="ts">
  2	import { RotateCcw, FlaskConical } from '@lucide/svelte';
  3	import { Checkbox } from '$lib/components/ui/checkbox';
  4	import { Input } from '$lib/components/ui/input';
  5	import Label from '$lib/components/ui/label/label.svelte';
  6	import * as Select from '$lib/components/ui/select';
  7	import { Textarea } from '$lib/components/ui/textarea';
  8	import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
  9	import { settingsStore } from '$lib/stores/settings.svelte';
 10	import { ChatSettingsParameterSourceIndicator } from '$lib/components/app';
 11	import type { Component } from 'svelte';
 12
 13	interface Props {
 14		fields: SettingsFieldConfig[];
 15		localConfig: SettingsConfigType;
 16		onConfigChange: (key: string, value: string | boolean) => void;
 17		onThemeChange?: (theme: string) => void;
 18	}
 19
 20	let { fields, localConfig, onConfigChange, onThemeChange }: Props = $props();
 21
 22	// Helper function to get parameter source info for syncable parameters
 23	function getParameterSourceInfo(key: string) {
 24		if (!settingsStore.canSyncParameter(key)) {
 25			return null;
 26		}
 27
 28		return settingsStore.getParameterInfo(key);
 29	}
 30</script>
 31
 32{#each fields as field (field.key)}
 33	<div class="space-y-2">
 34		{#if field.type === 'input'}
 35			{@const paramInfo = getParameterSourceInfo(field.key)}
 36			{@const currentValue = String(localConfig[field.key] ?? '')}
 37			{@const propsDefault = paramInfo?.serverDefault}
 38			{@const isCustomRealTime = (() => {
 39				if (!paramInfo || propsDefault === undefined) return false;
 40
 41				// Apply same rounding logic for real-time comparison
 42				const inputValue = currentValue;
 43				const numericInput = parseFloat(inputValue);
 44				const normalizedInput = !isNaN(numericInput)
 45					? Math.round(numericInput * 1000000) / 1000000
 46					: inputValue;
 47				const normalizedDefault =
 48					typeof propsDefault === 'number'
 49						? Math.round(propsDefault * 1000000) / 1000000
 50						: propsDefault;
 51
 52				return normalizedInput !== normalizedDefault;
 53			})()}
 54
 55			<div class="flex items-center gap-2">
 56				<Label for={field.key} class="flex items-center gap-1.5 text-sm font-medium">
 57					{field.label}
 58
 59					{#if field.isExperimental}
 60						<FlaskConical class="h-3.5 w-3.5 text-muted-foreground" />
 61					{/if}
 62				</Label>
 63				{#if isCustomRealTime}
 64					<ChatSettingsParameterSourceIndicator />
 65				{/if}
 66			</div>
 67
 68			<div class="relative w-full md:max-w-md">
 69				<Input
 70					id={field.key}
 71					value={currentValue}
 72					oninput={(e) => {
 73						// Update local config immediately for real-time badge feedback
 74						onConfigChange(field.key, e.currentTarget.value);
 75					}}
 76					placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`}
 77					class="w-full {isCustomRealTime ? 'pr-8' : ''}"
 78				/>
 79				{#if isCustomRealTime}
 80					<button
 81						type="button"
 82						onclick={() => {
 83							settingsStore.resetParameterToServerDefault(field.key);
 84							// Trigger UI update by calling onConfigChange with the default value
 85							const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key];
 86							onConfigChange(field.key, String(defaultValue));
 87						}}
 88						class="absolute top-1/2 right-2 inline-flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded transition-colors hover:bg-muted"
 89						aria-label="Reset to default"
 90						title="Reset to default"
 91					>
 92						<RotateCcw class="h-3 w-3" />
 93					</button>
 94				{/if}
 95			</div>
 96			{#if field.help || SETTING_CONFIG_INFO[field.key]}
 97				<p class="mt-1 text-xs text-muted-foreground">
 98					{@html field.help || SETTING_CONFIG_INFO[field.key]}
 99				</p>
100			{/if}
101		{:else if field.type === 'textarea'}
102			<Label for={field.key} class="block flex items-center gap-1.5 text-sm font-medium">
103				{field.label}
104
105				{#if field.isExperimental}
106					<FlaskConical class="h-3.5 w-3.5 text-muted-foreground" />
107				{/if}
108			</Label>
109
110			<Textarea
111				id={field.key}
112				value={String(localConfig[field.key] ?? '')}
113				onchange={(e) => onConfigChange(field.key, e.currentTarget.value)}
114				placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`}
115				class="min-h-[10rem] w-full md:max-w-2xl"
116			/>
117
118			{#if field.help || SETTING_CONFIG_INFO[field.key]}
119				<p class="mt-1 text-xs text-muted-foreground">
120					{field.help || SETTING_CONFIG_INFO[field.key]}
121				</p>
122			{/if}
123
124			{#if field.key === 'systemMessage'}
125				<div class="mt-3 flex items-center gap-2">
126					<Checkbox
127						id="showSystemMessage"
128						checked={Boolean(localConfig.showSystemMessage ?? true)}
129						onCheckedChange={(checked) => onConfigChange('showSystemMessage', Boolean(checked))}
130					/>
131
132					<Label for="showSystemMessage" class="cursor-pointer text-sm font-normal">
133						Show system message in conversations
134					</Label>
135				</div>
136			{/if}
137		{:else if field.type === 'select'}
138			{@const selectedOption = field.options?.find(
139				(opt: { value: string; label: string; icon?: Component }) =>
140					opt.value === localConfig[field.key]
141			)}
142			{@const paramInfo = getParameterSourceInfo(field.key)}
143			{@const currentValue = localConfig[field.key]}
144			{@const propsDefault = paramInfo?.serverDefault}
145			{@const isCustomRealTime = (() => {
146				if (!paramInfo || propsDefault === undefined) return false;
147
148				// For select fields, do direct comparison (no rounding needed)
149				return currentValue !== propsDefault;
150			})()}
151
152			<div class="flex items-center gap-2">
153				<Label for={field.key} class="flex items-center gap-1.5 text-sm font-medium">
154					{field.label}
155
156					{#if field.isExperimental}
157						<FlaskConical class="h-3.5 w-3.5 text-muted-foreground" />
158					{/if}
159				</Label>
160				{#if isCustomRealTime}
161					<ChatSettingsParameterSourceIndicator />
162				{/if}
163			</div>
164
165			<Select.Root
166				type="single"
167				value={currentValue}
168				onValueChange={(value) => {
169					if (field.key === 'theme' && value && onThemeChange) {
170						onThemeChange(value);
171					} else {
172						onConfigChange(field.key, value);
173					}
174				}}
175			>
176				<div class="relative w-full md:w-auto md:max-w-md">
177					<Select.Trigger class="w-full">
178						<div class="flex items-center gap-2">
179							{#if selectedOption?.icon}
180								{@const IconComponent = selectedOption.icon}
181								<IconComponent class="h-4 w-4" />
182							{/if}
183
184							{selectedOption?.label || `Select ${field.label.toLowerCase()}`}
185						</div>
186					</Select.Trigger>
187					{#if isCustomRealTime}
188						<button
189							type="button"
190							onclick={() => {
191								settingsStore.resetParameterToServerDefault(field.key);
192								// Trigger UI update by calling onConfigChange with the default value
193								const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key];
194								onConfigChange(field.key, String(defaultValue));
195							}}
196							class="absolute top-1/2 right-8 inline-flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded transition-colors hover:bg-muted"
197							aria-label="Reset to default"
198							title="Reset to default"
199						>
200							<RotateCcw class="h-3 w-3" />
201						</button>
202					{/if}
203				</div>
204				<Select.Content>
205					{#if field.options}
206						{#each field.options as option (option.value)}
207							<Select.Item value={option.value} label={option.label}>
208								<div class="flex items-center gap-2">
209									{#if option.icon}
210										{@const IconComponent = option.icon}
211										<IconComponent class="h-4 w-4" />
212									{/if}
213									{option.label}
214								</div>
215							</Select.Item>
216						{/each}
217					{/if}
218				</Select.Content>
219			</Select.Root>
220			{#if field.help || SETTING_CONFIG_INFO[field.key]}
221				<p class="mt-1 text-xs text-muted-foreground">
222					{field.help || SETTING_CONFIG_INFO[field.key]}
223				</p>
224			{/if}
225		{:else if field.type === 'checkbox'}
226			<div class="flex items-start space-x-3">
227				<Checkbox
228					id={field.key}
229					checked={Boolean(localConfig[field.key])}
230					onCheckedChange={(checked) => onConfigChange(field.key, checked)}
231					class="mt-1"
232				/>
233
234				<div class="space-y-1">
235					<label
236						for={field.key}
237						class="flex cursor-pointer items-center gap-1.5 pt-1 pb-0.5 text-sm leading-none font-medium"
238					>
239						{field.label}
240
241						{#if field.isExperimental}
242							<FlaskConical class="h-3.5 w-3.5 text-muted-foreground" />
243						{/if}
244					</label>
245
246					{#if field.help || SETTING_CONFIG_INFO[field.key]}
247						<p class="text-xs text-muted-foreground">
248							{field.help || SETTING_CONFIG_INFO[field.key]}
249						</p>
250					{/if}
251				</div>
252			</div>
253		{/if}
254	</div>
255{/each}