feat: generate and fix scripts using Autopilot powered by OpenAI #1827)

* feat(frontend): generate simple script from prompt

* feat(backend): add OpenAI key workspace settings

* feat(frontend): add OpenAI key workplace settings

* feat: proxy to backend + support all languages

* fix: openapi spec

* feat: ScriptGen new UI + gpt 4

* fix: openai key sqlx data

* fix: diffEditor optional

* feat: beta + minor improvements

* feat: resource type support

* feat: edit with AI

* feat: AI fix

* feat: minor improvements
content max length, format, env info

* fix: minor
This commit is contained in:
HugoCasa
2023-07-12 17:07:45 +02:00
committed by GitHub
parent 8bfe1771ba
commit 012ea2dc0a
21 changed files with 1651 additions and 72 deletions
@@ -0,0 +1,199 @@
<script lang="ts">
import { Button } from '../common'
import { SUPPORTED_LANGUAGES, editScript, generateScript } from './lib'
import type { SupportedLanguage } from '$lib/common'
import { sendUserToast } from '$lib/toast'
import type Editor from '../Editor.svelte'
import { faCheck, faClose, faMagicWandSparkles } from '@fortawesome/free-solid-svg-icons'
import Popup from '../common/popup/Popup.svelte'
import { fade } from 'svelte/transition'
import { Icon } from 'svelte-awesome'
import { WorkspaceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import type DiffEditor from '../DiffEditor.svelte'
import { scriptLangToEditorLang } from '$lib/scripts'
import type { Selection } from 'monaco-editor/esm/vs/editor/editor.api'
// props
export let iconOnly: boolean = false
export let lang: SupportedLanguage
export let editor: Editor | undefined
export let diffEditor: DiffEditor | undefined
// state
let funcDesc: string = ''
let genLoading: boolean = false
let openAIAvailable: boolean | undefined = undefined
let button: HTMLButtonElement | undefined
let input: HTMLInputElement | undefined
let generatedCode = ''
let selection: Selection | undefined
let isEdit = false
async function onGenerate() {
try {
// close popup ^^
const elem = document.activeElement as HTMLElement
if (elem.blur) {
elem.blur()
}
genLoading = true
if (isEdit && selection) {
const selectedCode = editor?.getSelectedLines() || ''
const originalCode = editor?.getCode() || ''
const selectionGenCode = await editScript({
language: lang,
description: funcDesc,
selectedCode
})
generatedCode = originalCode.replace(selectedCode, selectionGenCode + '\n')
} else {
generatedCode = await generateScript({
language: lang,
description: funcDesc
})
}
funcDesc = ''
} catch (err) {
sendUserToast('Failed to generate code', true)
console.error(err)
} finally {
genLoading = false
}
}
function acceptDiff() {
editor?.setCode(diffEditor?.getModified() || '')
editor?.format()
generatedCode = ''
}
function rejectDiff() {
generatedCode = ''
}
async function checkIfOpenAIAvailable(lang: SupportedLanguage) {
try {
const exists = await WorkspaceService.existsOpenaiKey({ workspace: $workspaceStore! })
openAIAvailable = exists && SUPPORTED_LANGUAGES.has(lang)
} catch (err) {
console.error(err)
sendUserToast('Failed to check if OpenAI is available', true)
}
}
function showDiff() {
diffEditor?.setDiff(editor?.getCode() || '', generatedCode, scriptLangToEditorLang(lang))
diffEditor?.show()
editor?.hide()
}
function hideDiff() {
editor?.show()
diffEditor?.hide()
}
function setSelectionHandler() {
editor?.onDidChangeCursorSelection((e) => {
selection = e.selection
})
}
$: checkIfOpenAIAvailable(lang)
$: input?.focus()
$: lang && (generatedCode = '')
$: generatedCode && showDiff()
$: !generatedCode && hideDiff()
$: editor && setSelectionHandler()
$: selection && (isEdit = !selection.isEmpty())
</script>
{#if openAIAvailable}
{#if generatedCode}
<div class="flex gap-1 px-2">
<Button
title="Discard generated code"
btnClasses="!font-medium"
size="xs"
color="red"
spacingSize="xs2"
on:click={rejectDiff}
variant="contained"
startIcon={{ icon: faClose }}
{iconOnly}
>
Discard
</Button><Button
title="Accept generated code"
btnClasses="!font-medium"
size="xs"
color="green"
spacingSize="xs2"
on:click={acceptDiff}
startIcon={{ icon: faCheck }}
{iconOnly}
>
Accept
</Button>
</div>
{:else}
<Button
title="Generate code from prompt"
btnClasses="!font-medium text-gray-600"
size="xs"
color="light"
spacingSize="md"
bind:element={button}
startIcon={{ icon: faMagicWandSparkles }}
{iconOnly}
loading={genLoading}
>
{isEdit ? 'AI Edit' : 'AI Gen'}
</Button>
{/if}
{#if !generatedCode}
<Popup
ref={button}
options={{ placement: 'top-start' }}
transition={fade}
closeOn={[]}
wrapperClasses="!z-[1002]"
outerClasses="rounded shadow-xl bg-white border p-3 w-96"
>
<label class="block text-gray-900">
<div class="pb-1 text-sm text-gray-600">Prompt</div>
<div class="flex w-full">
<input
type="text"
bind:this={input}
bind:value={funcDesc}
class="!w-auto grow"
on:keypress={({ key }) => {
if (key === 'Enter') {
onGenerate()
}
}}
placeholder={isEdit
? 'Describe the changes you want'
: 'Describe what the script should do'}
/>
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="!p-1 !w-[34px] !ml-1"
aria-label="Generate"
on:click={onGenerate}
>
<Icon data={faMagicWandSparkles} />
</Button>
</div>
</label>
</Popup>
{/if}
{/if}