154 lines
3.8 KiB
Svelte
154 lines
3.8 KiB
Svelte
<script lang="ts">
|
|
import { Button, notifications } from "@budibase/bbui"
|
|
|
|
import { createEventDispatcher } from "svelte"
|
|
import { API } from "@/api"
|
|
import { ErrorCode, type EnrichedBinding } from "@budibase/types"
|
|
import analytics, { Events } from "@/analytics"
|
|
import AiInput from "../ai/AIInput.svelte"
|
|
|
|
export let bindings: EnrichedBinding[] = []
|
|
export let value: string | null = ""
|
|
export let expandedOnly: boolean = false
|
|
export let parentWidth: number | null = null
|
|
|
|
const dispatch = createEventDispatcher<{
|
|
update: { code: string }
|
|
accept: void
|
|
reject: { code: string | null }
|
|
}>()
|
|
|
|
let suggestedCode: string | null = null
|
|
let previousContents: string | null = null
|
|
let expanded = false
|
|
let promptText = ""
|
|
let inputValue = ""
|
|
|
|
const thresholdExpansionWidth = 350
|
|
|
|
$: shouldAlwaysBeExpanded =
|
|
expandedOnly ||
|
|
(parentWidth !== null && parentWidth > thresholdExpansionWidth)
|
|
|
|
$: expanded = shouldAlwaysBeExpanded || expanded
|
|
|
|
async function generateJs(prompt: string) {
|
|
promptText = ""
|
|
if (!prompt.trim()) return
|
|
|
|
previousContents = value
|
|
promptText = prompt
|
|
try {
|
|
const resp = await API.generateJs({ prompt, bindings })
|
|
const code = resp.code
|
|
if (code === "") {
|
|
throw new Error(
|
|
"We didn't understand your prompt. This can happen if the prompt isn't specific, or if it's a request for something other than code. Try expressing your request in a different way."
|
|
)
|
|
}
|
|
suggestedCode = code
|
|
dispatch("update", { code })
|
|
} catch (e: any) {
|
|
console.error(e)
|
|
|
|
if ("code" in e && e.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
|
|
notifications.error(
|
|
"Monthly usage limit reached. We're exploring options to expand this soon. Questions? Contact support@budibase.com"
|
|
)
|
|
} else if ("message" in e) {
|
|
notifications.error(`Unable to generate code: ${e.message}`)
|
|
} else {
|
|
notifications.error(`Unable to generate code.`)
|
|
}
|
|
}
|
|
}
|
|
|
|
function acceptSuggestion() {
|
|
analytics.captureEvent(Events.AI_JS_ACCEPTED, {
|
|
code: suggestedCode,
|
|
prompt: promptText,
|
|
})
|
|
dispatch("accept")
|
|
reset()
|
|
}
|
|
|
|
function rejectSuggestion() {
|
|
analytics.captureEvent(Events.AI_JS_REJECTED, {
|
|
code: suggestedCode,
|
|
prompt: promptText,
|
|
})
|
|
dispatch("reject", { code: previousContents })
|
|
reset(false)
|
|
}
|
|
|
|
function reset(clearPrompt: boolean = true) {
|
|
if (clearPrompt) {
|
|
promptText = ""
|
|
inputValue = ""
|
|
}
|
|
suggestedCode = null
|
|
previousContents = null
|
|
expanded = false
|
|
}
|
|
</script>
|
|
|
|
<div class="ai-gen-container" class:expanded>
|
|
{#if suggestedCode !== null}
|
|
<div class="floating-actions">
|
|
<Button cta size="S" icon="CheckmarkCircle" on:click={acceptSuggestion}>
|
|
Accept
|
|
</Button>
|
|
<Button primary size="S" icon="Delete" on:click={rejectSuggestion}
|
|
>Reject</Button
|
|
>
|
|
</div>
|
|
{/if}
|
|
|
|
<AiInput
|
|
placeholder="Generate with AI"
|
|
onSubmit={generateJs}
|
|
bind:expanded
|
|
bind:value={inputValue}
|
|
readonly={!!suggestedCode}
|
|
expandedOnly={shouldAlwaysBeExpanded}
|
|
/>
|
|
</div>
|
|
|
|
<style>
|
|
.ai-gen-container {
|
|
height: 40px;
|
|
position: absolute;
|
|
bottom: var(--spacing-s);
|
|
right: var(--spacing-s);
|
|
display: flex;
|
|
overflow: visible;
|
|
transition: width 0.4s cubic-bezier(0.19, 1, 0.22, 1);
|
|
width: 22ch;
|
|
}
|
|
|
|
.ai-gen-container.expanded {
|
|
width: calc(100% - var(--spacing-s) * 2);
|
|
}
|
|
|
|
.floating-actions {
|
|
position: absolute;
|
|
display: flex;
|
|
gap: var(--spacing-s);
|
|
bottom: calc(100% + var(--spacing-s));
|
|
z-index: 2;
|
|
animation: fade-in 0.2s ease-out forwards;
|
|
width: 100%;
|
|
}
|
|
|
|
@keyframes fade-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
</style>
|