Updated UX for the AI JS work
This commit is contained in:
parent
44ef7168b9
commit
c4f8a608a4
|
@ -0,0 +1,24 @@
|
||||||
|
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.360872 11.5905L4.90819 16.139C5.10754 16.3384 5.43058 16.3384 5.62993 16.139L7.61005 14.1583C7.78825 13.9801 7.87247 13.7282 7.83748 13.4783L7.30573 9.71929C7.26667 9.44582 7.05186 9.23095 6.77886 9.19228L3.02083 8.6604C2.77143 8.62499 2.5196 8.70923 2.34099 8.88788L0.360872 10.8685C0.161518 11.0679 0.161518 11.3911 0.360872 11.5905Z" fill="url(#paint0_linear_1216_26668)"/>
|
||||||
|
<path d="M11.0826 16.139L15.6299 11.5905C15.8292 11.3911 15.8292 11.068 15.6299 10.8686L13.6498 8.88794C13.4716 8.70969 13.2197 8.62545 12.9699 8.66045L9.21188 9.19234C8.93847 9.23141 8.72366 9.44628 8.68501 9.71934L8.15326 13.4784C8.11787 13.7278 8.20208 13.9797 8.38069 14.1584L10.3608 16.139C10.5602 16.3384 10.8832 16.3384 11.0826 16.139Z" fill="url(#paint1_linear_1216_26668)"/>
|
||||||
|
<path d="M15.6391 5.40954L11.0918 0.861023C10.8925 0.661616 10.5694 0.661614 10.3701 0.861021L8.38995 2.84166C8.21175 3.01991 8.12753 3.27181 8.16252 3.52168L8.69427 7.28071C8.73333 7.55418 8.94814 7.76905 9.22114 7.80772L12.9792 8.3396C13.2286 8.37501 13.4804 8.29077 13.659 8.11212L15.6391 6.13148C15.8385 5.93207 15.8385 5.60895 15.6391 5.40954Z" fill="url(#paint2_linear_1216_26668)"/>
|
||||||
|
<path d="M4.91745 0.860967L0.370132 5.40948C0.170778 5.60889 0.170776 5.93201 0.370131 6.13142L2.35025 8.11206C2.52845 8.29031 2.78028 8.37455 3.03009 8.33955L6.78812 7.80766C7.06152 7.76859 7.27634 7.55372 7.31499 7.28066L7.84674 3.52163C7.88213 3.27217 7.79792 3.02026 7.61931 2.84161L5.63919 0.860967C5.43984 0.66156 5.1168 0.66156 4.91745 0.860967Z" fill="url(#paint3_linear_1216_26668)"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_1216_26668" x1="4.94623" y1="15.6582" x2="7.43064" y2="9.91942" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6E56FF"/>
|
||||||
|
<stop offset="1" stop-color="#9F8FFF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_1216_26668" x1="15.1492" y1="11.5525" x2="9.411" y2="9.06961" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6E56FF"/>
|
||||||
|
<stop offset="1" stop-color="#9F8FFF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear_1216_26668" x1="11.0538" y1="1.34184" x2="8.56936" y2="7.08058" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6E56FF"/>
|
||||||
|
<stop offset="1" stop-color="#9F8FFF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear_1216_26668" x1="0.850819" y1="5.44754" x2="6.589" y2="7.93039" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6E56FF"/>
|
||||||
|
<stop offset="1" stop-color="#9F8FFF"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,285 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { ActionButton, Icon, 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"
|
||||||
|
|
||||||
|
export let bindings: EnrichedBinding[] = []
|
||||||
|
export let value: string | null = ""
|
||||||
|
export let parentWidth: number | null = null
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let buttonContainer: HTMLElement
|
||||||
|
let promptInput: HTMLTextAreaElement
|
||||||
|
let buttonElement: HTMLButtonElement
|
||||||
|
let promptLoading = false
|
||||||
|
let suggestedCode: string | null = null
|
||||||
|
let previousContents: string | null = null
|
||||||
|
let expanded = false
|
||||||
|
let containerWidth = "auto"
|
||||||
|
let containerHeight = "35px"
|
||||||
|
let promptText = ""
|
||||||
|
|
||||||
|
function adjustContainerHeight() {
|
||||||
|
if (promptInput && buttonElement) {
|
||||||
|
promptInput.style.height = "0px"
|
||||||
|
const newHeight = Math.min(promptInput.scrollHeight, 100)
|
||||||
|
promptInput.style.height = `${newHeight}px`
|
||||||
|
containerHeight = `${Math.max(40, newHeight + 20)}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (promptInput && promptText) adjustContainerHeight()
|
||||||
|
|
||||||
|
async function generateJs(prompt: string) {
|
||||||
|
if (!prompt.trim()) return
|
||||||
|
|
||||||
|
previousContents = value
|
||||||
|
promptLoading = true
|
||||||
|
try {
|
||||||
|
const resp = await API.generateJs({ prompt, bindings })
|
||||||
|
const code = resp.code
|
||||||
|
if (code === "") {
|
||||||
|
throw new Error("We didn't understand your prompt. Please rephrase it.")
|
||||||
|
}
|
||||||
|
suggestedCode = code
|
||||||
|
dispatch("update", { code })
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
notifications.error(
|
||||||
|
e instanceof Error
|
||||||
|
? `Unable to generate code: ${e.message}`
|
||||||
|
: "Unable to generate code. Please try again later."
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
promptLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function acceptSuggestion() {
|
||||||
|
dispatch("accept")
|
||||||
|
resetExpand()
|
||||||
|
}
|
||||||
|
|
||||||
|
function rejectSuggestion() {
|
||||||
|
dispatch("reject", { code: previousContents })
|
||||||
|
resetExpand()
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetExpand() {
|
||||||
|
expanded = false
|
||||||
|
containerWidth = "auto"
|
||||||
|
containerHeight = "40px"
|
||||||
|
promptText = ""
|
||||||
|
suggestedCode = null
|
||||||
|
previousContents = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleExpand() {
|
||||||
|
if (!expanded) {
|
||||||
|
expanded = true
|
||||||
|
// Dynamic width based on parent size, with minimum and maximum constraints
|
||||||
|
containerWidth = parentWidth
|
||||||
|
? `${Math.min(Math.max(parentWidth * 0.8, 300), 600)}px`
|
||||||
|
: "300px"
|
||||||
|
containerHeight = "40px"
|
||||||
|
setTimeout(() => {
|
||||||
|
promptInput?.focus()
|
||||||
|
adjustContainerHeight()
|
||||||
|
}, 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ai-gen-container"
|
||||||
|
style="--container-width: {containerWidth}; --container-height: {containerHeight}"
|
||||||
|
bind:this={buttonContainer}
|
||||||
|
>
|
||||||
|
{#if suggestedCode !== null}
|
||||||
|
<div class="floating-actions">
|
||||||
|
<ActionButton size="S" icon="CheckmarkCircle" on:click={acceptSuggestion}>
|
||||||
|
Accept
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton size="S" icon="Delete" on:click={rejectSuggestion}>
|
||||||
|
Reject
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button
|
||||||
|
bind:this={buttonElement}
|
||||||
|
class="spectrum-ActionButton fade"
|
||||||
|
class:expanded
|
||||||
|
on:click={!expanded ? toggleExpand : undefined}
|
||||||
|
>
|
||||||
|
<img src={BBAI} alt="AI" class="ai-icon" />
|
||||||
|
{#if expanded}
|
||||||
|
<textarea
|
||||||
|
bind:this={promptInput}
|
||||||
|
bind:value={promptText}
|
||||||
|
class="prompt-input"
|
||||||
|
placeholder="Generate Javascript..."
|
||||||
|
on:keydown={handleKeyPress}
|
||||||
|
on:input={adjustContainerHeight}
|
||||||
|
disabled={suggestedCode !== null}
|
||||||
|
readonly={suggestedCode !== null}
|
||||||
|
rows="1"
|
||||||
|
/>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<Icon
|
||||||
|
color={promptLoading
|
||||||
|
? "#6E56FF"
|
||||||
|
: "var(--spectrum-global-color-gray-600)"}
|
||||||
|
size="S"
|
||||||
|
hoverable
|
||||||
|
name={promptLoading ? "StopCircle" : "PlayCircle"}
|
||||||
|
on:click={() => generateJs(promptText)}
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
name="Close"
|
||||||
|
on:click={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (!suggestedCode && !promptLoading) toggleExpand()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<span class="spectrum-ActionButton-label ai-gen-text">
|
||||||
|
Generate with AI
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ai-gen-container {
|
||||||
|
--container-width: auto;
|
||||||
|
--container-height: 40px;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
width: var(--container-width);
|
||||||
|
height: var(--container-height);
|
||||||
|
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-end;
|
||||||
|
overflow: visible;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-actions {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
bottom: calc(100% + 5px);
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
animation: fade-in 0.2s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spectrum-ActionButton {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--spacing-s);
|
||||||
|
border: 1px solid var(--spectrum-alias-border-color);
|
||||||
|
border-radius: var(--spectrum-alias-border-radius-regular);
|
||||||
|
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spectrum-ActionButton:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var(--spectrum-global-color-gray-75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spectrum-ActionButton.expanded {
|
||||||
|
border-radius: var(--spectrum-alias-border-radius-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade {
|
||||||
|
transition: all 2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-gen-text {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: opacity 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
outline: none;
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
color: var(--spectrum-alias-text-color);
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
min-width: 0;
|
||||||
|
resize: none;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
line-height: 1.4;
|
||||||
|
min-height: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-input::placeholder {
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
padding-right: var(--spacing-s);
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -6,13 +6,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { Label } from "@budibase/bbui"
|
||||||
Button,
|
|
||||||
Label,
|
|
||||||
notifications,
|
|
||||||
Popover,
|
|
||||||
TextArea,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
||||||
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
|
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
|
||||||
|
|
||||||
|
@ -69,8 +63,7 @@
|
||||||
import { validateHbsTemplate } from "./validator/hbs"
|
import { validateHbsTemplate } from "./validator/hbs"
|
||||||
import { validateJsTemplate } from "./validator/js"
|
import { validateJsTemplate } from "./validator/js"
|
||||||
import { featureFlag } from "@/helpers"
|
import { featureFlag } from "@/helpers"
|
||||||
import { API } from "@/api"
|
import AIGen from "./AIGen.svelte"
|
||||||
import Spinner from "../Spinner.svelte"
|
|
||||||
|
|
||||||
export let label: string | undefined = undefined
|
export let label: string | undefined = undefined
|
||||||
export let completions: BindingCompletion[] = []
|
export let completions: BindingCompletion[] = []
|
||||||
|
@ -94,15 +87,19 @@
|
||||||
let mounted = false
|
let mounted = false
|
||||||
let isEditorInitialised = false
|
let isEditorInitialised = false
|
||||||
let queuedRefresh = false
|
let queuedRefresh = false
|
||||||
|
let editorWidth: number | null = null
|
||||||
|
|
||||||
// Theming!
|
// Theming!
|
||||||
let currentTheme = $themeStore?.theme
|
let currentTheme = $themeStore?.theme
|
||||||
let isDark = !currentTheme.includes("light")
|
let isDark = !currentTheme.includes("light")
|
||||||
let themeConfig = new Compartment()
|
let themeConfig = new Compartment()
|
||||||
|
|
||||||
let popoverAnchor: HTMLElement
|
const updateEditorWidth = () => {
|
||||||
let popover: Popover
|
if (editorEle) {
|
||||||
let promptInput: TextArea
|
editorWidth = editorEle.offsetWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: aiGenEnabled =
|
$: aiGenEnabled =
|
||||||
featureFlag.isEnabled(FeatureFlag.AI_JS_GENERATION) &&
|
featureFlag.isEnabled(FeatureFlag.AI_JS_GENERATION) &&
|
||||||
mode.name === "javascript" &&
|
mode.name === "javascript" &&
|
||||||
|
@ -161,68 +158,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: promptLoading = false
|
|
||||||
let popoverWidth = 300
|
|
||||||
let suggestedCode: string | null = null
|
|
||||||
let previousContents: string | null = null
|
|
||||||
const generateJs = async (prompt: string) => {
|
|
||||||
previousContents = editor.state.doc.toString()
|
|
||||||
promptLoading = true
|
|
||||||
popoverWidth = 30
|
|
||||||
let code = ""
|
|
||||||
try {
|
|
||||||
const resp = await API.generateJs({ prompt, bindings })
|
|
||||||
code = resp.code
|
|
||||||
|
|
||||||
if (code === "") {
|
|
||||||
throw new Error(
|
|
||||||
"we didn't understand your prompt, please phrase your request in another way"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
if (e instanceof Error) {
|
|
||||||
notifications.error(`Unable to generate code: ${e.message}`)
|
|
||||||
} else {
|
|
||||||
notifications.error("Unable to generate code, please try again later.")
|
|
||||||
}
|
|
||||||
code = previousContents
|
|
||||||
promptLoading = false
|
|
||||||
resetPopover()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
value = code
|
|
||||||
editor.dispatch({
|
|
||||||
changes: { from: 0, to: editor.state.doc.length, insert: code },
|
|
||||||
})
|
|
||||||
suggestedCode = code
|
|
||||||
popoverWidth = 100
|
|
||||||
promptLoading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const acceptSuggestion = () => {
|
|
||||||
suggestedCode = null
|
|
||||||
previousContents = null
|
|
||||||
resetPopover()
|
|
||||||
dispatch("change", editor.state.doc.toString())
|
|
||||||
dispatch("blur", editor.state.doc.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
const rejectSuggestion = () => {
|
|
||||||
suggestedCode = null
|
|
||||||
value = previousContents || ""
|
|
||||||
editor.dispatch({
|
|
||||||
changes: { from: 0, to: editor.state.doc.length, insert: value },
|
|
||||||
})
|
|
||||||
previousContents = null
|
|
||||||
resetPopover()
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetPopover = () => {
|
|
||||||
popover.hide()
|
|
||||||
popoverWidth = 300
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export a function to expose caret position
|
// Export a function to expose caret position
|
||||||
export const getCaretPosition = () => {
|
export const getCaretPosition = () => {
|
||||||
const selection_range = editor.state.selection.ranges[0]
|
const selection_range = editor.state.selection.ranges[0]
|
||||||
|
@ -487,12 +422,32 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
// Handle AI generation code updates
|
||||||
|
const handleAICodeUpdate = (event: CustomEvent<{ code: string }>) => {
|
||||||
|
const { code } = event.detail
|
||||||
|
value = code
|
||||||
|
editor.dispatch({
|
||||||
|
changes: { from: 0, to: editor.state.doc.length, insert: code },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
mounted = true
|
mounted = true
|
||||||
// Capture scrolling
|
// Capture scrolling
|
||||||
editorEle.addEventListener("wheel", e => {
|
editorEle.addEventListener("wheel", e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Need to get the width of the drawer to pass to the prompt component
|
||||||
|
updateEditorWidth()
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
updateEditorWidth()
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.observe(editorEle)
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
|
@ -513,52 +468,23 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if aiGenEnabled}
|
{#if aiGenEnabled}
|
||||||
<button
|
<AIGen
|
||||||
bind:this={popoverAnchor}
|
{bindings}
|
||||||
class="ai-gen"
|
{value}
|
||||||
on:click={() => {
|
parentWidth={editorWidth}
|
||||||
popover.show()
|
on:update={handleAICodeUpdate}
|
||||||
setTimeout(() => {
|
on:accept={() => {
|
||||||
promptInput.focus()
|
dispatch("change", editor.state.doc.toString())
|
||||||
}, 100)
|
dispatch("blur", editor.state.doc.toString())
|
||||||
}}
|
}}
|
||||||
>
|
on:reject={event => {
|
||||||
Generate with AI ✨
|
const { code } = event.detail
|
||||||
</button>
|
value = code
|
||||||
|
editor.dispatch({
|
||||||
<Popover
|
changes: { from: 0, to: editor.state.doc.length, insert: code },
|
||||||
bind:this={popover}
|
})
|
||||||
minWidth={popoverWidth}
|
|
||||||
anchor={popoverAnchor}
|
|
||||||
on:close={() => {
|
|
||||||
if (suggestedCode) {
|
|
||||||
acceptSuggestion()
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
align="left-outside"
|
/>
|
||||||
>
|
|
||||||
{#if promptLoading}
|
|
||||||
<div class="prompt-spinner">
|
|
||||||
<Spinner size="20" color="white" />
|
|
||||||
</div>
|
|
||||||
{:else if suggestedCode !== null}
|
|
||||||
<Button on:click={acceptSuggestion}>Accept</Button>
|
|
||||||
<Button on:click={rejectSuggestion}>Reject</Button>
|
|
||||||
{:else}
|
|
||||||
<TextArea
|
|
||||||
bind:this={promptInput}
|
|
||||||
placeholder="Type your prompt then press enter..."
|
|
||||||
on:keypress={event => {
|
|
||||||
if (event.getModifierState("Shift")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.key === "Enter") {
|
|
||||||
generateJs(promptInput.contents())
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</Popover>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -766,34 +692,4 @@
|
||||||
text-overflow: ellipsis !important;
|
text-overflow: ellipsis !important;
|
||||||
white-space: nowrap !important;
|
white-space: nowrap !important;
|
||||||
}
|
}
|
||||||
.ai-gen {
|
|
||||||
right: 1px;
|
|
||||||
bottom: 1px;
|
|
||||||
position: absolute;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: var(--spacing-s);
|
|
||||||
border-left: 1px solid var(--spectrum-alias-border-color);
|
|
||||||
border-top: 1px solid var(--spectrum-alias-border-color);
|
|
||||||
border-top-left-radius: var(--spectrum-alias-border-radius-regular);
|
|
||||||
color: var(--spectrum-global-color-blue-700);
|
|
||||||
background-color: var(--spectrum-global-color-gray-75);
|
|
||||||
transition: background-color
|
|
||||||
var(--spectrum-global-animation-duration-100, 130ms),
|
|
||||||
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
|
|
||||||
border-color var(--spectrum-global-animation-duration-100, 130ms);
|
|
||||||
height: calc(var(--spectrum-alias-item-height-m) - 2px);
|
|
||||||
}
|
|
||||||
.ai-gen:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--spectrum-alias-text-color-hover);
|
|
||||||
background-color: var(--spectrum-global-color-gray-50);
|
|
||||||
border-color: var(--spectrum-alias-border-color-hover);
|
|
||||||
}
|
|
||||||
.prompt-spinner {
|
|
||||||
padding: var(--spacing-m);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit dcf65b2361ae31aab0ccfadd8222d3aa3c421181
|
Subproject commit 234babe3609467eb090846881be58c096415ec73
|
Loading…
Reference in New Issue