mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 16:03:27 +00:00
feat(aiagent): Store AI provider config in localStorage (#6854)
* feat(aiagent): Store AI provider config in localStorage - Added localStorage persistence for AI provider, resource, and model selections - Configuration is loaded as default values on component initialization - Automatically saves whenever selections change - Validates stored provider is still available before loading - Uses storage key: windmill_ai_provider_config Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> * better * fix logic * Update toggle option text for default setting --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
committed by
GitHub
co-authored by
windmill-internal-app[bot]
claude[bot]
Ruben Fiszel
parent
deb4fc6ede
commit
36cd49fb41
@@ -9,6 +9,7 @@
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import ToggleButtonMore from './common/toggleButton-v2/ToggleButtonMore.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
|
||||
interface ProviderValue {
|
||||
kind?: AIProvider
|
||||
@@ -24,22 +25,34 @@
|
||||
|
||||
let { value = $bindable(), disabled = false, actions }: Props = $props()
|
||||
|
||||
// Initialize value if undefined
|
||||
if (!value) {
|
||||
const providers = Object.keys(AI_PROVIDERS)
|
||||
value = {
|
||||
kind: providers.length > 0 ? (providers[0] as AIProvider) : undefined,
|
||||
resource: undefined,
|
||||
model: undefined
|
||||
}
|
||||
}
|
||||
|
||||
let loading = $state(false)
|
||||
let availableModels = $state<string[]>([])
|
||||
let filterText = $state('')
|
||||
let useAsDefault = $state(false)
|
||||
|
||||
let modelsCache = new Map<AIProvider, string[]>()
|
||||
|
||||
const STORAGE_KEY = 'windmill_ai_provider_config'
|
||||
|
||||
// Initialize value if undefined
|
||||
if (!value) {
|
||||
const storedConfig = loadStoredConfig()
|
||||
if (storedConfig) {
|
||||
value = storedConfig
|
||||
useAsDefault = true
|
||||
} else {
|
||||
const providers = Object.keys(AI_PROVIDERS)
|
||||
value = {
|
||||
kind: providers.length > 0 ? (providers[0] as AIProvider) : undefined,
|
||||
resource: undefined,
|
||||
model: undefined
|
||||
}
|
||||
useAsDefault = false
|
||||
}
|
||||
} else {
|
||||
useAsDefault = isSameAsStoredConfig(value)
|
||||
}
|
||||
|
||||
// Reactive items for the Select component
|
||||
let items = $derived.by(() => {
|
||||
const r = availableModels.map((model) => ({
|
||||
@@ -61,6 +74,61 @@
|
||||
label: details.label
|
||||
}))
|
||||
|
||||
// Check if the current config is the same as the stored config
|
||||
function isSameAsStoredConfig(config: ProviderValue): boolean {
|
||||
const storedConfig = loadStoredConfig()
|
||||
return (
|
||||
storedConfig !== undefined &&
|
||||
storedConfig?.kind === config.kind &&
|
||||
storedConfig?.resource === config.resource &&
|
||||
storedConfig?.model === config.model
|
||||
)
|
||||
}
|
||||
|
||||
// Load stored configuration from localStorage
|
||||
function loadStoredConfig(): ProviderValue | undefined {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored)
|
||||
// Validate that the stored provider is still available
|
||||
if (parsed.kind && AI_PROVIDERS[parsed.kind]) {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load AI provider config from localStorage:', e)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Save configuration to localStorage
|
||||
function saveConfig(config: ProviderValue) {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return
|
||||
}
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(config))
|
||||
} catch (e) {
|
||||
console.error('Failed to save AI provider config to localStorage:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove configuration from localStorage
|
||||
function removeConfig() {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return
|
||||
}
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
} catch (e) {
|
||||
console.error('Failed to remove AI provider config from localStorage:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadModels(signal?: AbortSignal) {
|
||||
const provider = value?.kind
|
||||
const resourceValue = value?.resource
|
||||
@@ -99,28 +167,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Reload models when provider or resourcePath changes
|
||||
$effect(() => {
|
||||
const abortController = new AbortController()
|
||||
const provider = value?.kind
|
||||
const resourceValue = value?.resource
|
||||
const resourcePath = resourceValueToPath(resourceValue)
|
||||
|
||||
filterText = ''
|
||||
|
||||
if (provider && resourcePath) {
|
||||
loadModels(abortController.signal)
|
||||
} else {
|
||||
const defaultModels = provider ? AI_PROVIDERS[provider]?.defaultModels || [] : []
|
||||
availableModels = defaultModels
|
||||
loading = false
|
||||
}
|
||||
|
||||
return () => {
|
||||
abortController.abort()
|
||||
}
|
||||
})
|
||||
|
||||
// Handle provider selection
|
||||
function onProviderChange(selectedProvider: AIProvider) {
|
||||
if (value) {
|
||||
@@ -153,6 +199,34 @@
|
||||
return `$res:${path}`
|
||||
}
|
||||
}
|
||||
|
||||
// Reload models when provider or resourcePath changes
|
||||
$effect(() => {
|
||||
const abortController = new AbortController()
|
||||
const provider = value?.kind
|
||||
const resourceValue = value?.resource
|
||||
const resourcePath = resourceValueToPath(resourceValue)
|
||||
|
||||
filterText = ''
|
||||
|
||||
if (provider && resourcePath) {
|
||||
loadModels(abortController.signal)
|
||||
} else {
|
||||
const defaultModels = provider ? AI_PROVIDERS[provider]?.defaultModels || [] : []
|
||||
availableModels = defaultModels
|
||||
loading = false
|
||||
}
|
||||
|
||||
return () => {
|
||||
abortController.abort()
|
||||
}
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (useAsDefault && value && value.kind && value.resource && value.model) {
|
||||
saveConfig(value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -222,6 +296,23 @@
|
||||
inputClass="min-h-10 !bg-surface disabled:!bg-surface-disabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Use as Default Checkbox -->
|
||||
<div class="flex justify-end pt-1">
|
||||
<Toggle
|
||||
disabled={disabled || !value?.kind || !value?.resource || !value?.model}
|
||||
bind:checked={useAsDefault}
|
||||
options={{ right: 'Use as personal default for other new agents' }}
|
||||
size="xs"
|
||||
on:change={(e) => {
|
||||
if (!e.detail) {
|
||||
removeConfig()
|
||||
} else {
|
||||
saveConfig(value)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{@render actions?.()}
|
||||
|
||||
Reference in New Issue
Block a user