Move code
This commit is contained in:
parent
acb17112ea
commit
c790ea1af0
|
@ -1,21 +1,11 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
ActionButton,
|
||||
Icon,
|
||||
notifications,
|
||||
Button,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Body,
|
||||
Link,
|
||||
} from "@budibase/bbui"
|
||||
import { auth, admin, licensing } from "@/stores/portal"
|
||||
import { ActionButton, notifications } from "@budibase/bbui"
|
||||
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { API } from "@/api"
|
||||
import type { EnrichedBinding } from "@budibase/types"
|
||||
import BBAI from "assets/bb-ai.svg"
|
||||
import analytics, { Events } from "@/analytics"
|
||||
import AiInput from "../ai/AIInput.svelte"
|
||||
|
||||
export let bindings: EnrichedBinding[] = []
|
||||
export let value: string | null = ""
|
||||
|
@ -28,22 +18,13 @@
|
|||
reject: { code: string | null }
|
||||
}>()
|
||||
|
||||
let promptInput: HTMLInputElement
|
||||
let buttonElement: HTMLButtonElement
|
||||
let promptLoading = false
|
||||
let suggestedCode: string | null = null
|
||||
let previousContents: string | null = null
|
||||
let expanded = false
|
||||
let containerWidth = "auto"
|
||||
let promptText = ""
|
||||
let animateBorder = false
|
||||
let switchOnAIModal: Modal
|
||||
let addCreditsModal: Modal
|
||||
|
||||
const thresholdExpansionWidth = 350
|
||||
$: accountPortalAccess = $auth?.user?.accountPortalAccess
|
||||
$: accountPortal = $admin.accountPortalUrl
|
||||
$: aiEnabled = $auth?.user?.llm
|
||||
|
||||
$: expanded =
|
||||
expandedOnly ||
|
||||
|
@ -51,9 +32,6 @@
|
|||
? true
|
||||
: expanded
|
||||
|
||||
$: creditsExceeded = $licensing.aiCreditsExceeded
|
||||
$: disabled = suggestedCode !== null || !aiEnabled || creditsExceeded
|
||||
|
||||
$: if (
|
||||
expandedOnly ||
|
||||
(expanded && parentWidth !== null && parentWidth > thresholdExpansionWidth)
|
||||
|
@ -64,11 +42,11 @@
|
|||
}
|
||||
|
||||
async function generateJs(prompt: string) {
|
||||
promptText = ""
|
||||
if (!prompt.trim()) return
|
||||
|
||||
previousContents = value
|
||||
promptLoading = true
|
||||
animateBorder = true
|
||||
promptText = prompt
|
||||
try {
|
||||
const resp = await API.generateJs({ prompt, bindings })
|
||||
const code = resp.code
|
||||
|
@ -84,8 +62,6 @@
|
|||
? `Unable to generate code: ${e.message}`
|
||||
: "Unable to generate code. Please try again later."
|
||||
)
|
||||
} finally {
|
||||
promptLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,10 +86,9 @@
|
|||
function resetExpand() {
|
||||
expanded = false
|
||||
containerWidth = "auto"
|
||||
promptText = ""
|
||||
suggestedCode = null
|
||||
previousContents = null
|
||||
animateBorder = false
|
||||
// animateBorder = false
|
||||
}
|
||||
|
||||
function calculateExpandedWidth() {
|
||||
|
@ -122,37 +97,35 @@
|
|||
: "300px"
|
||||
}
|
||||
|
||||
function toggleExpand() {
|
||||
if (!expanded) {
|
||||
expanded = true
|
||||
animateBorder = true
|
||||
containerWidth = calculateExpandedWidth()
|
||||
setTimeout(() => {
|
||||
promptInput?.focus()
|
||||
}, 250)
|
||||
} else {
|
||||
resetExpand()
|
||||
}
|
||||
}
|
||||
// function toggleExpand() {
|
||||
// if (!expanded) {
|
||||
// expanded = true
|
||||
// animateBorder = true
|
||||
// containerWidth = calculateExpandedWidth()
|
||||
// setTimeout(() => {
|
||||
// promptInput?.focus()
|
||||
// }, 250)
|
||||
// } else {
|
||||
// resetExpand()
|
||||
// }
|
||||
// }
|
||||
|
||||
function handleKeyPress(event: KeyboardEvent) {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
generateJs(promptText)
|
||||
} else if (event.key === "Escape") {
|
||||
if (!suggestedCode) resetExpand()
|
||||
else {
|
||||
expanded = false
|
||||
containerWidth = "auto"
|
||||
}
|
||||
} else {
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
// function handleKeyPress(event: KeyboardEvent) {
|
||||
// if (event.key === "Enter" && !event.shiftKey) {
|
||||
// event.preventDefault()
|
||||
// generateJs(promptText)
|
||||
// } else if (event.key === "Escape") {
|
||||
// if (!suggestedCode) resetExpand()
|
||||
// else {
|
||||
// expanded = false
|
||||
// containerWidth = "auto"
|
||||
// }
|
||||
// } else {
|
||||
// event.stopPropagation()
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="ai-gen-container" style="--container-width: {containerWidth}">
|
||||
{#if suggestedCode !== null}
|
||||
<div class="floating-actions">
|
||||
|
@ -164,97 +137,13 @@
|
|||
</ActionButton>
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
bind:this={buttonElement}
|
||||
class="spectrum-ActionButton fade"
|
||||
class:expanded
|
||||
class:animate-border={animateBorder}
|
||||
on:click={!expanded ? toggleExpand : undefined}
|
||||
>
|
||||
<div class="button-content-wrapper">
|
||||
<img
|
||||
src={BBAI}
|
||||
alt="AI"
|
||||
class="ai-icon"
|
||||
class:disabled={expanded && disabled}
|
||||
on:click={!expandedOnly
|
||||
? e => {
|
||||
e.stopPropagation()
|
||||
toggleExpand()
|
||||
}
|
||||
: undefined}
|
||||
/>
|
||||
{#if expanded}
|
||||
<input
|
||||
type="text"
|
||||
bind:this={promptInput}
|
||||
bind:value={promptText}
|
||||
class="prompt-input"
|
||||
placeholder="Generate Javascript..."
|
||||
on:keydown={handleKeyPress}
|
||||
{disabled}
|
||||
readonly={suggestedCode !== null}
|
||||
/>
|
||||
{:else}
|
||||
<span class="spectrum-ActionButton-label ai-gen-text">
|
||||
Generate with AI
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if expanded}
|
||||
<div class="action-buttons">
|
||||
{#if !aiEnabled}
|
||||
<Button cta size="S" on:click={() => switchOnAIModal.show()}>
|
||||
Switch on AI
|
||||
</Button>
|
||||
<Modal bind:this={switchOnAIModal}>
|
||||
<ModalContent title="Switch on AI" showConfirmButton={false}>
|
||||
<div class="enable-ai">
|
||||
<p>To enable BB AI:</p>
|
||||
<ul>
|
||||
<li>
|
||||
Add your Budibase license key:
|
||||
<Link href={accountPortal}>Budibase account portal</Link>
|
||||
</li>
|
||||
<li>
|
||||
Go to the portal settings page, click AI and switch on BB AI
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{:else if creditsExceeded}
|
||||
<Button cta size="S" on:click={() => addCreditsModal.show()}>
|
||||
Add AI credits
|
||||
</Button>
|
||||
<Modal bind:this={addCreditsModal}>
|
||||
<ModalContent title="Add AI credits" showConfirmButton={false}>
|
||||
<Body size="S">
|
||||
{#if accountPortalAccess}
|
||||
<Link href={"https://budibase.com/contact/"}
|
||||
>Contact sales</Link
|
||||
> to unlock additional BB AI credits
|
||||
{:else}
|
||||
Contact your account holder to unlock additional BB AI credits
|
||||
{/if}
|
||||
</Body>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{:else}
|
||||
<Icon
|
||||
color={promptLoading
|
||||
? "#6E56FF"
|
||||
: "var(--spectrum-global-color-gray-600)"}
|
||||
size="S"
|
||||
hoverable
|
||||
hoverColor="#6E56FF"
|
||||
name={promptLoading ? "StopCircle" : "PlayCircle"}
|
||||
on:click={() => generateJs(promptText)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<AiInput
|
||||
placeholder="Generate with AI"
|
||||
onSubmit={generateJs}
|
||||
collapseOnEscKey={!suggestedCode}
|
||||
on:escKey={rejectSuggestion}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -269,54 +158,6 @@
|
|||
overflow: visible;
|
||||
}
|
||||
|
||||
.spectrum-ActionButton {
|
||||
--offset: 1px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
padding: var(--spacing-s);
|
||||
border: 1px solid var(--spectrum-alias-border-color);
|
||||
border-radius: 30px;
|
||||
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background-color: var(--spectrum-global-color-gray-75);
|
||||
}
|
||||
|
||||
.spectrum-ActionButton::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
width: calc(100% + 2px);
|
||||
height: calc(100% + 2px);
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(
|
||||
125deg,
|
||||
transparent -10%,
|
||||
#6e56ff 2%,
|
||||
#9f8fff 15%,
|
||||
#9f8fff 25%,
|
||||
transparent 35%,
|
||||
transparent 110%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.spectrum-ActionButton:not(.animate-border)::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.animate-border::before {
|
||||
animation: border-fade-in 1s cubic-bezier(0.17, 0.67, 0.83, 0.67);
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes border-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
@ -326,18 +167,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.spectrum-ActionButton::after {
|
||||
content: "";
|
||||
background: inherit;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
inset: var(--offset);
|
||||
height: calc(100% - 2 * var(--offset));
|
||||
width: calc(100% - 2 * var(--offset));
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.floating-actions {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
|
@ -358,83 +187,4 @@
|
|||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.spectrum-ActionButton:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--spectrum-global-color-gray-75);
|
||||
}
|
||||
|
||||
.spectrum-ActionButton.expanded {
|
||||
border-radius: 30px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: opacity 0.2s ease-out;
|
||||
}
|
||||
|
||||
.fade {
|
||||
transition: all 2s ease-in;
|
||||
}
|
||||
|
||||
.ai-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
cursor: var(--ai-icon-cursor, pointer);
|
||||
}
|
||||
|
||||
.ai-gen-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: opacity 0.2s ease-out;
|
||||
margin-right: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.prompt-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--spectrum-alias-text-color);
|
||||
min-width: 0;
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.prompt-input::placeholder {
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
z-index: 4;
|
||||
flex-shrink: 0;
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
||||
.button-content-wrapper {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
||||
.prompt-input:disabled,
|
||||
.prompt-input[readonly] {
|
||||
color: var(--spectrum-global-color-gray-500);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.ai-icon.disabled {
|
||||
filter: grayscale(1) brightness(1.5);
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,13 +8,11 @@
|
|||
export let onSubmit: (_prompt: string) => Promise<void>
|
||||
export let placeholder: string = ""
|
||||
export let expandedOnly: boolean = false
|
||||
export let collapseOnEscKey: boolean = true
|
||||
|
||||
export let parentWidth: number | null = null
|
||||
export const dispatch = createEventDispatcher<{
|
||||
update: { code: string }
|
||||
accept: void
|
||||
reject: { code: string | null }
|
||||
}>()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let parentWidth: number | null = null
|
||||
|
||||
let promptInput: HTMLInputElement
|
||||
let buttonElement: HTMLButtonElement
|
||||
|
@ -60,13 +58,25 @@
|
|||
function handleKeyPress(event: KeyboardEvent) {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
onSubmit(promptText)
|
||||
onPromptSubmit()
|
||||
} else if (event.key === "Escape") {
|
||||
expanded = false
|
||||
dispatch("escKey")
|
||||
if (collapseOnEscKey) {
|
||||
expanded = false
|
||||
}
|
||||
} else {
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
async function onPromptSubmit() {
|
||||
promptLoading = true
|
||||
try {
|
||||
await onSubmit(promptText)
|
||||
} finally {
|
||||
promptLoading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
|
@ -156,7 +166,7 @@
|
|||
hoverable
|
||||
hoverColor="#6E56FF"
|
||||
name={promptLoading ? "StopCircle" : "PlayCircle"}
|
||||
on:click={() => onSubmit(promptText)}
|
||||
on:click={onPromptSubmit}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue