Files
windmill/frontend/src/lib/components/instanceSettings/InstanceAISettings.svelte
T
centdixandClaude Opus 4.6 db5e03610d feat: add instance-level AI settings (#8453)
* feat: add instance-level AI settings with workspace fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add AI step to onboarding setup wizard

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: thread workspace prop through resource editor and disable chat offset

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Revert "fix: thread workspace prop through resource editor and disable chat offset"

This reverts commit 9fea9cc0c239f6432d1fef1487c45e74ab752e21.

* fix: set workspace store and disable chat offset during AI setup step

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: thread workspace and disableChatOffset props through resource editors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: populate workspace and user stores for AI step path component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: initialize AI clients for test key during onboarding

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract AI config state into InstanceAISettings component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: move AI config state ownership into AISettings component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Persist instance AI settings before navigation

* Reload effective workspace AI state after save

* Scope AI key tests to the rendered workspace

* Add post-create AI onboarding for new workspaces

* Unify instance AI settings header

* Fix instance AI drawer offset on workspace selection

* Add instance AI fallback settings behavior

* Update sqlx metadata

* Update sqlx metadata

* Clarify active instance AI in workspace settings

* Refresh workspace AI state after instance AI save

* Declare instance AI summary in API schema

* Normalize empty instance AI config handling

* Clean up workspace AI settings UI

* Unify AI config provider checks

* Split AI settings metadata from effective config

* Propagate instance AI cache invalidation across servers

* Fix AI settings dirty state tracking

* Update sqlx metadata

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 19:18:36 +00:00

150 lines
4.1 KiB
Svelte

<script lang="ts">
import { JobService, SettingService, WorkspaceService, type AIConfig } from '$lib/gen'
import { setCopilotInfo } from '$lib/aiStore'
import { workspaceStore, userStore } from '$lib/stores'
import { getUserExt } from '$lib/user'
import { sendUserToast } from '$lib/toast'
import { workspaceAIClients } from '../copilot/lib'
import AISettings from '../workspaceSettings/AISettings.svelte'
import { Alert, Button } from '../common'
interface Props {
hasUnsavedChanges?: boolean
disableChatOffset?: boolean
showHubSync?: boolean
}
let {
hasUnsavedChanges = $bindable(false),
disableChatOffset = false,
showHubSync = false
}: Props = $props()
let initialConfig: AIConfig | undefined = $state(undefined)
let loaded = $state(false)
let aiSettings: AISettings | undefined = $state(undefined)
async function loadConfig() {
try {
initialConfig =
((await SettingService.getGlobal({ key: 'ai_config' })) as AIConfig | undefined) ?? {}
loaded = true
} catch (e) {
console.error('Failed to load instance AI config', e)
sendUserToast('Failed to load instance AI config', true)
}
}
async function handleCustomSave(config: AIConfig) {
const hasProviders = Object.keys(config.providers ?? {}).length > 0
await SettingService.setGlobal({
key: 'ai_config',
requestBody: { value: hasProviders ? config : null }
})
if ($workspaceStore) {
try {
const effectiveConfig = await WorkspaceService.getCopilotInfo({
workspace: $workspaceStore
})
setCopilotInfo(effectiveConfig)
} catch (e) {
console.error('Failed to refresh workspace AI state after instance save', e)
}
}
sendUserToast('Instance AI settings saved')
}
export async function persistBeforeExit(): Promise<boolean> {
return (await aiSettings?.saveIfDirtyAndValid()) ?? true
}
// Ensure stores are set (this page may bypass the (logged) layout)
async function ensureStores() {
if (!$workspaceStore) {
$workspaceStore = 'admins'
}
if (!$userStore) {
$userStore = await getUserExt($workspaceStore)
}
workspaceAIClients.init($workspaceStore)
}
ensureStores()
loadConfig()
// --- Hub sync ---
let hubSyncStatus: 'idle' | 'loading' | 'success' | 'error' = $state('idle')
let hubSyncMessage = $state('')
async function syncFromHub() {
hubSyncStatus = 'loading'
hubSyncMessage = ''
try {
await JobService.runWaitResultScriptByPath({
workspace: 'admins',
path: 'u/admin/hub_sync',
requestBody: {}
})
hubSyncStatus = 'success'
hubSyncMessage = 'Resource types synced from hub successfully'
} catch (e: any) {
hubSyncMessage =
e?.body?.error?.message ||
e?.body?.message ||
(typeof e?.body === 'string' ? e.body : null) ||
e?.message ||
'Failed to sync from hub'
hubSyncStatus = 'error'
}
}
</script>
{#if loaded}
{#if showHubSync}
<div
class="p-3 border rounded-md bg-surface-secondary mb-4 mt-4 flex items-center justify-between gap-4"
>
<div>
<p class="text-xs font-medium text-secondary">Resource types</p>
<p class="text-2xs text-tertiary mt-0.5">
AI providers require their resource types. Sync from the Hub if they are missing.
</p>
</div>
<Button
variant="default"
unifiedSize="sm"
loading={hubSyncStatus === 'loading'}
onClick={syncFromHub}
>
Sync from hub
</Button>
</div>
{#if hubSyncStatus === 'success'}
<div class="mb-4">
<Alert type="success" title="Resource types synced">
{hubSyncMessage}
</Alert>
</div>
{:else if hubSyncStatus === 'error'}
<div class="mb-4">
<Alert type="error" title="Sync failed">
{hubSyncMessage}
</Alert>
</div>
{/if}
{/if}
<AISettings
bind:this={aiSettings}
bind:hasUnsavedChanges
{initialConfig}
workspace="admins"
{disableChatOffset}
title="Windmill AI"
description="Windmill AI integrates with your favorite AI providers and models. Set your AI settings at the instance level to be able to use them on all your workspaces. Workspace-level settings can override these."
link="https://www.windmill.dev/docs/core_concepts/ai_generation"
promptScope="instance"
customSave={handleCustomSave}
/>
{/if}