mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 08:02:18 +00:00
* feat: track AI fill, AI fix, evals, reusable agents and debugger usage Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: pin ee ref to the feature_usage registry commit Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: update ee-repo-ref to c3b6f62ea579a3583d4b474e9885c77104cfc87e This commit updates the EE repository reference after PR #745 was merged in windmill-ee-private. Previous ee-repo-ref: 77992910929188a854eadc06ee45971877b6f954 New ee-repo-ref: c3b6f62ea579a3583d4b474e9885c77104cfc87e Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
154 lines
4.9 KiB
Svelte
154 lines
4.9 KiB
Svelte
<script lang="ts">
|
|
import { base } from '$lib/base'
|
|
import { Button } from '../common'
|
|
|
|
import { SUPPORTED_LANGUAGES } from './lib'
|
|
import type { SupportedLanguage } from '$lib/common'
|
|
import Popover from '$lib/components/meltComponents/Popover.svelte'
|
|
import { autoPlacement } from '@floating-ui/core'
|
|
import { WandSparkles } from 'lucide-svelte'
|
|
import { aiChatManager, type AIChatManager } from './chat/AIChatManager.svelte'
|
|
import { copilotInfo } from '$lib/aiStore'
|
|
import OpenInSessionButton from '$lib/components/sessions/OpenInSessionButton.svelte'
|
|
import { getOpenInSessionHandoff } from '$lib/components/sessions/openInSessionContext'
|
|
import { AIBtnClasses } from './chat/AIButtonStyle'
|
|
import { getContext } from 'svelte'
|
|
import { logFeatureUsage } from '$lib/utils/featureUsage'
|
|
|
|
let {
|
|
lang,
|
|
error,
|
|
jobId,
|
|
moduleId
|
|
}: {
|
|
lang: SupportedLanguage
|
|
/** The failing run's error, used when there is no job to point at. */
|
|
error?: string
|
|
/** The failing run's job id. Preferred over `error`: the chat reads the
|
|
* run itself with `get_job_logs`, which gives it the logs rather than
|
|
* just the thrown value, and keeps the composer readable. */
|
|
jobId?: string
|
|
/** Set when this sits in a flow step's preview, so the session opens on
|
|
* that step rather than the flow root. */
|
|
moduleId?: string
|
|
} = $props()
|
|
|
|
// The enclosing editor's "Open in AI session" hand-off (ScriptEditor for a
|
|
// standalone script, FlowBuilder for a step).
|
|
const handoff = getOpenInSessionHandoff()
|
|
const seedPrompt = $derived.by(() => {
|
|
const what = moduleId ? `step \`${moduleId}\`` : 'this script'
|
|
if (jobId) {
|
|
return `The last test run of ${what} failed (job \`${jobId}\`). Read its logs, then fix the code.`
|
|
}
|
|
// No job to read: the error text has to travel with the request.
|
|
return error
|
|
? `Fix this error in ${what}:\n\n\`\`\`\n${error}\n\`\`\``
|
|
: `Fix the error from the last run of ${what}.`
|
|
})
|
|
// Anonymous counter for a failing run being handed to AI, keyed by where the run was. All
|
|
// three branches below report the same action: which one is on screen follows the session
|
|
// gate and whether a chat is already beside this panel, not a choice made here.
|
|
function logAiFix() {
|
|
logFeatureUsage('ai_fix', 'requested', { key: moduleId ? 'flow_step' : 'script' })
|
|
}
|
|
|
|
const sessionSource = $derived.by(() => {
|
|
const source = handoff?.source({ moduleId })
|
|
if (!source) return undefined
|
|
// The counter wraps the editor's own hook rather than replacing it: that hook persists
|
|
// the draft the session opens on, so dropping it would fix an older copy of the code.
|
|
const editorBeforeOpen = source.beforeOpen
|
|
return {
|
|
...source,
|
|
seedPrompt,
|
|
autoSend: true,
|
|
beforeOpen: async () => {
|
|
logAiFix()
|
|
await editorBeforeOpen?.()
|
|
}
|
|
}
|
|
})
|
|
|
|
// Inside a session pane the chat is already beside this panel, so there is
|
|
// nothing to hand off to: send into that chat instead. OpenInSessionButton
|
|
// renders nothing there, which would otherwise leave the sessions population
|
|
// with no fix affordance at all.
|
|
const sessionScopedManager = getContext<AIChatManager | undefined>('aiChatManager')
|
|
</script>
|
|
|
|
{#if SUPPORTED_LANGUAGES.has(lang)}
|
|
{#if sessionScopedManager}
|
|
<Button
|
|
title="Fix the failing run in this chat"
|
|
size="xs"
|
|
color="light"
|
|
spacingSize="xs2"
|
|
startIcon={{ icon: WandSparkles }}
|
|
on:click={() => {
|
|
logAiFix()
|
|
sessionScopedManager.sendOrQueue(seedPrompt)
|
|
}}
|
|
btnClasses={AIBtnClasses('default')}
|
|
>
|
|
AI Fix
|
|
</Button>
|
|
{:else}
|
|
<OpenInSessionButton
|
|
source={sessionSource}
|
|
btnClasses={AIBtnClasses('default')}
|
|
label="Fix in AI session"
|
|
tooltip="Open an AI session on this item and fix the failing run"
|
|
btnProps={{ iconOnly: false, startIcon: { icon: WandSparkles } }}
|
|
>
|
|
{#snippet fallback()}
|
|
<Popover
|
|
floatingConfig={{
|
|
middleware: [
|
|
autoPlacement({
|
|
allowedPlacements: ['bottom-end', 'top-end']
|
|
})
|
|
]
|
|
}}
|
|
displayArrow={true}
|
|
>
|
|
{#snippet trigger()}
|
|
<div class="flex flex-row">
|
|
<Button
|
|
title="Fix code"
|
|
size="xs"
|
|
color="light"
|
|
spacingSize="xs2"
|
|
startIcon={{ icon: WandSparkles }}
|
|
on:click={() => {
|
|
if ($copilotInfo.enabled) {
|
|
logAiFix()
|
|
aiChatManager.fix()
|
|
}
|
|
}}
|
|
btnClasses="text-ai bg-violet-100 dark:bg-gray-700 min-w-[84px]"
|
|
propagateEvent={!$copilotInfo.enabled}
|
|
>
|
|
AI Fix
|
|
</Button>
|
|
</div>
|
|
{/snippet}
|
|
{#snippet content()}
|
|
<div class="p-4">
|
|
<div class="w-80">
|
|
<p class="text-sm"
|
|
>Enable Windmill AI in the <a
|
|
class="inline-flex flex-row items-center gap-1"
|
|
href="{base}/workspace_settings?tab=ai"
|
|
target="_blank">workspace settings</a
|
|
></p
|
|
></div
|
|
>
|
|
</div>
|
|
{/snippet}
|
|
</Popover>
|
|
{/snippet}
|
|
</OpenInSessionButton>
|
|
{/if}
|
|
{/if}
|