Files
windmill/frontend/src/lib/components/copilot/RegexGen.svelte
T

189 lines
4.8 KiB
Svelte

<script lang="ts">
import { run } from 'svelte/legacy';
import { base } from '$lib/base'
import { Button } from '../common'
import { getNonStreamingMetadataCompletion } from './lib'
import { sendUserToast } from '$lib/toast'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { autoPlacement } from '@floating-ui/core'
import { ExternalLink, HistoryIcon, Wand2 } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import { copilotInfo } from '$lib/aiStore'
// state
let funcDesc: string = $state('')
let genLoading: boolean = $state(false)
let input: HTMLInputElement | undefined = $state()
let abortController: AbortController | undefined = $state(undefined)
const dispatch = createEventDispatcher()
async function onGenerate() {
if (funcDesc?.length <= 0) {
return
}
savePrompt()
genLoading = true
abortController = new AbortController()
try {
const res = await getNonStreamingMetadataCompletion(
[
{
role: 'system',
content:
'Generate a regex pattern that one can use in a javascript Regex object. Output only the regex itself without any wrapping characters including the / characters. The regex should match the following:'
},
{
role: 'user',
content: funcDesc
}
],
abortController
)
dispatch('gen', { res: res, prompt: funcDesc })
funcDesc = ''
} catch (err) {
if (!abortController.signal.aborted) {
sendUserToast('Failed to generate regex: ' + err, true)
}
} finally {
genLoading = false
}
}
run(() => {
input && setTimeout(() => input?.focus(), 100)
});
let promptHistory: string[] = $state(JSON.parse(getPromptsRegex() || '[]'))
function getPromptsRegex(): string | undefined {
try {
return localStorage.getItem('prompts-regex') ?? undefined
} catch (e) {
console.error('error interacting with local storage', e)
}
return undefined
}
function savePrompt() {
if (promptHistory.includes(funcDesc)) {
return
}
promptHistory.unshift(funcDesc)
while (promptHistory.length > 5) {
promptHistory.pop()
}
try {
localStorage.setItem('prompts-regex', JSON.stringify(promptHistory))
} catch (e) {
console.error('error interacting with local storage', e)
}
}
function clearPromptHistory() {
promptHistory = []
try {
localStorage.setItem('prompts-regex', JSON.stringify(promptHistory))
} catch (e) {
console.error('error interacting with local storage', e)
}
}
</script>
<Popover
floatingConfig={{
middleware: [
autoPlacement({
allowedPlacements: ['bottom-start', 'bottom-end', 'top-start', 'top-end', 'top', 'bottom']
})
]
}}
>
{#snippet trigger()}
<Button
title="Generate regexes from prompt"
btnClasses="text-ai bg-violet-100 dark:bg-gray-700"
size="sm"
color={genLoading ? 'red' : 'light'}
spacingSize="md"
startIcon={{ icon: Wand2 }}
loading={genLoading}
propagateEvent
clickableWhileLoading
on:click={genLoading ? () => abortController?.abort() : () => {}}
/>
{/snippet}
{#snippet content({ close })}
<div class="block text-primary p-4">
{#if $copilotInfo.enabled}
<div class="flex flex-col gap-4">
<div class="flex w-96">
<input
type="text"
bind:this={input}
bind:value={funcDesc}
onkeypress={({ key }) => {
if (key === 'Enter' && funcDesc?.length > 0) {
close()
onGenerate()
}
}}
placeholder="Describe what the regex should do"
/>
<Button
size="xs"
color="light"
buttonType="button"
btnClasses="!ml-2 text-ai bg-violet-100 dark:bg-gray-700"
aria-label="Generate"
on:click={() => {
close()
onGenerate()
}}
disabled={funcDesc?.length <= 0}
iconOnly
startIcon={{ icon: Wand2 }}
/>
</div>
{#if promptHistory.length > 0}
<div class="w-96 flex flex-col gap-1">
{#each promptHistory as p (p)}
<Button
size="xs2"
color="light"
btnClasses="justify-start overflow-x-scroll no-scrollbar"
startIcon={{ icon: HistoryIcon, classes: 'shrink-0' }}
on:click={() => {
funcDesc = p
}}>{p}</Button
>
{/each}
<button
class="underline text-xs text-start px-2 text-secondary font-normal"
onclick={clearPromptHistory}>clear history</button
>
</div>
{/if}
</div>
{:else}
<p class="text-sm">
Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
workspace settings <ExternalLink size={16} />
</a>
</p>
{/if}
</div>
{/snippet}
</Popover>