feat: ai code completion (#2361)

This commit is contained in:
HugoCasa
2023-09-29 16:04:28 +02:00
committed by GitHub
parent 03805b77d3
commit bff09d45e9
30 changed files with 328 additions and 81 deletions

View File

@@ -1,15 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET openai_resource_path = $1 WHERE workspace_id = $2",
"query": "UPDATE workspace_settings SET openai_resource_path = $1, code_completion_enabled = $2 WHERE workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Bool",
"Text"
]
},
"nullable": []
},
"hash": "d7d368f9ef2587c6abd3daaec93401615a53e3d7d949b65ebf01ef8760ba46b8"
"hash": "034583442e6f8ae38d6c4e4aac26f17c8d9d0e657f28276228fc90d3e22e1304"
}

View File

@@ -37,7 +37,6 @@
"bash",
"postgresql",
"nativets",
"Nativets",
"bun",
"mysql",
"bigquery",

View File

@@ -67,6 +67,11 @@
"ordinal": 12,
"name": "openai_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 13,
"name": "code_completion_enabled",
"type_info": "Bool"
}
],
"parameters": {
@@ -87,7 +92,8 @@
true,
true,
true,
true
true,
false
]
},
"hash": "1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597"

View File

@@ -67,7 +67,6 @@
"bash",
"postgresql",
"nativets",
"Nativets",
"bun",
"mysql",
"bigquery",

View File

@@ -28,7 +28,6 @@
"bash",
"postgresql",
"nativets",
"Nativets",
"bun",
"mysql",
"bigquery",

View File

@@ -67,6 +67,11 @@
"ordinal": 12,
"name": "openai_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 13,
"name": "code_completion_enabled",
"type_info": "Bool"
}
],
"parameters": {
@@ -87,7 +92,8 @@
true,
true,
true,
true
true,
false
]
},
"hash": "5445083864b2b092b012e894bff7630a1d7b9deb8d33e9f909061f351f96844e"

View File

@@ -42,7 +42,6 @@
"bash",
"postgresql",
"nativets",
"Nativets",
"bun",
"mysql",
"bigquery",

View File

@@ -1,14 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET openai_resource_path = NULL WHERE workspace_id = $1",
"query": "UPDATE workspace_settings SET openai_resource_path = NULL, code_completion_enabled = $1 WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Bool",
"Text"
]
},
"nullable": []
},
"hash": "28eef409e9f25e4c037dd716d6515e96e8962acb59d5e64f9cc3227e0f5fd058"
"hash": "6268eabd561502a44e273d6391102278974623f7a7bcad6891d7abadf4d3ea03"
}

View File

@@ -42,7 +42,6 @@
"bash",
"postgresql",
"nativets",
"Nativets",
"bun",
"mysql",
"bigquery",

View File

@@ -46,7 +46,6 @@
"bash",
"postgresql",
"nativets",
"Nativets",
"bun",
"mysql",
"bigquery",

View File

@@ -60,7 +60,6 @@
"bash",
"postgresql",
"nativets",
"Nativets",
"bun",
"mysql",
"bigquery",

View File

@@ -42,7 +42,6 @@
"bash",
"postgresql",
"nativets",
"Nativets",
"bun",
"mysql",
"bigquery",

View File

@@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "SELECT openai_resource_path, code_completion_enabled FROM workspace_settings WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "openai_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "code_completion_enabled",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true,
false
]
},
"hash": "ceb97024ebf1a1c00ea1ed4952f66b229ca4622c537cc252d8ed52b4d24270ee"
}

View File

@@ -0,0 +1,2 @@
-- Add down migration script here
ALTER TABLE workspace_settings DROP COLUMN code_completion_enabled;

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TABLE workspace_settings ADD COLUMN code_completion_enabled BOOLEAN NOT NULL DEFAULT false;

View File

@@ -1155,8 +1155,12 @@ paths:
type: string
openai_resource_path:
type: string
code_completion_enabled:
type: boolean
error_handler:
type: string
required:
- code_completion_enabled
/w/{workspace}/workspaces/get_deploy_to:
get:
@@ -1309,24 +1313,28 @@ paths:
schema:
type: string
/w/{workspace}/workspaces/edit_openai_resource_path:
/w/{workspace}/workspaces/edit_copilot_config:
post:
summary: edit OpenAI resource path
operationId: editOpenaiResourcePath
summary: edit copilot config
operationId: editCopilotConfig
tags:
- workspace
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: WorkspaceOpenaiResourcePath
description: WorkspaceCopilotConfig
required: true
content:
application/json:
schema:
type: object
required:
- code_completion_enabled
properties:
openai_resource_path:
type: string
code_completion_enabled:
type: boolean
responses:
"200":
description: status
@@ -1335,10 +1343,10 @@ paths:
schema:
type: string
/w/{workspace}/workspaces/exists_openai_resource_path:
/w/{workspace}/workspaces/get_copilot_info:
get:
summary: OpenAI resource path exists
operationId: existsOpenaiResourcePath
summary: get copilot info
operationId: getCopilotInfo
tags:
- workspace
parameters:
@@ -1350,7 +1358,15 @@ paths:
content:
text/plain:
schema:
type: boolean
type: object
properties:
exists_openai_resource_path:
type: boolean
code_completion_enabled:
type: boolean
required:
- exists_openai_resource_path
- code_completion_enabled
/w/{workspace}/workspaces/edit_error_handler:
post:
@@ -6883,7 +6899,7 @@ components:
- "workspaces.edit_deploy_to"
- "workspaces.edit_auto_invite_domain"
- "workspaces.edit_webhook"
- "workspaces.edit_openai_resource_path"
- "workspaces.edit_copilot_config"
- "workspaces.edit_error_handler"
- "workspaces.create"
- "workspaces.update"

View File

@@ -69,8 +69,8 @@ pub fn workspaced_service() -> Router {
.route("/edit_deploy_to", post(edit_deploy_to))
.route("/tarball", get(tarball_workspace))
.route("/premium_info", get(premium_info))
.route("/edit_openai_resource_path", post(edit_openai_resource_path))
.route("/exists_openai_resource_path", get(exists_openai_resource_path) )
.route("/edit_copilot_config", post(edit_copilot_config))
.route("/get_copilot_info", get(get_copilot_info) )
.route("/edit_error_handler", post(edit_error_handler));
#[cfg(feature = "enterprise")]
@@ -125,6 +125,7 @@ pub struct WorkspaceSettings {
pub webhook: Option<String>,
pub deploy_to: Option<String>,
pub openai_resource_path: Option<String>,
pub code_completion_enabled: bool,
pub error_handler: Option<String>,
}
@@ -166,8 +167,9 @@ struct EditWebhook {
}
#[derive(Deserialize)]
struct EditOpenaiResourcePath {
struct EditCopilotConfig {
openai_resource_path: Option<String>,
code_completion_enabled: bool,
}
#[derive(Deserialize)]
@@ -670,12 +672,12 @@ async fn edit_webhook(
Ok(format!("Edit webhook for workspace {}", &w_id))
}
async fn edit_openai_resource_path(
async fn edit_copilot_config(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
ApiAuthed { is_admin, username, .. }: ApiAuthed,
Json(eo): Json<EditOpenaiResourcePath>,
Json(eo): Json<EditCopilotConfig>,
) -> Result<String> {
require_admin(is_admin, &username)?;
@@ -683,15 +685,17 @@ async fn edit_openai_resource_path(
if let Some(openai_resource_path) = &eo.openai_resource_path {
sqlx::query!(
"UPDATE workspace_settings SET openai_resource_path = $1 WHERE workspace_id = $2",
"UPDATE workspace_settings SET openai_resource_path = $1, code_completion_enabled = $2 WHERE workspace_id = $3",
openai_resource_path,
eo.code_completion_enabled,
&w_id
)
.execute(&mut *tx)
.await?;
} else {
sqlx::query!(
"UPDATE workspace_settings SET openai_resource_path = NULL WHERE workspace_id = $1",
"UPDATE workspace_settings SET openai_resource_path = NULL, code_completion_enabled = $1 WHERE workspace_id = $2",
eo.code_completion_enabled,
&w_id,
)
.execute(&mut *tx)
@@ -700,37 +704,43 @@ async fn edit_openai_resource_path(
audit_log(
&mut *tx,
&authed.username,
"workspaces.edit_openai_resource_path",
"workspaces.edit_copilot_config",
ActionKind::Update,
&w_id,
Some(&authed.email),
Some([("openai_resource_path", &format!("{:?}", eo.openai_resource_path)[..])].into()),
Some([("openai_resource_path", &format!("{:?}", eo.openai_resource_path)[..]), ("code_completion_enabled", &format!("{:?}", eo.code_completion_enabled)[..])].into()),
)
.await?;
tx.commit().await?;
Ok(format!("Edit openai_resource_path for workspace {}", &w_id))
Ok(format!("Edit copilot config for workspace {}", &w_id))
}
async fn exists_openai_resource_path(
#[derive(Serialize)]
struct CopilotInfo {
pub exists_openai_resource_path: bool,
pub code_completion_enabled: bool,
}
async fn get_copilot_info(
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
) -> JsonResult<bool> {
) -> JsonResult<CopilotInfo> {
let mut tx = db.begin().await?;
let openai_resource_path = sqlx::query_scalar!(
"SELECT openai_resource_path FROM workspace_settings WHERE workspace_id = $1",
let record = sqlx::query!(
"SELECT openai_resource_path, code_completion_enabled FROM workspace_settings WHERE workspace_id = $1",
&w_id
)
.fetch_one(&mut *tx)
.await
.map_err(|e| Error::InternalErr(format!("getting openai_resource_path: {e}")))?;
.map_err(|e| Error::InternalErr(format!("getting openai_resource_path and code_completion_enabled: {e}")))?;
tx.commit().await?;
let exists = openai_resource_path.is_some();
Ok(Json(exists))
Ok(Json(CopilotInfo {
exists_openai_resource_path: record.openai_resource_path.is_some(),
code_completion_enabled: record.code_completion_enabled,
}))
}

View File

@@ -30,7 +30,7 @@
import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'
import { CloseAction, ErrorAction, RequestType, NotificationType } from 'vscode-languageclient'
import { MonacoBinding } from 'y-monaco'
import { dbSchemas, type DBSchema } from '$lib/stores'
import { dbSchemas, type DBSchema, copilotInfo } from '$lib/stores'
import {
createHash as randomHash,
@@ -47,6 +47,8 @@
import type { Text } from 'yjs'
import { initializeMode } from 'monaco-graphql/esm/initializeMode'
import type { MonacoGraphQLAPI } from 'monaco-graphql/esm/api'
import { sleep } from '$lib/utils'
import { editorCodeCompletion } from './copilot/completion'
let divEl: HTMLDivElement | null = null
let editor: meditor.IStandaloneCodeEditor
@@ -354,6 +356,78 @@
}
}
let copilotCompletor: Disposable | undefined = undefined
let copilotTs = Date.now()
let abortController: AbortController | undefined = undefined
function addCopilotSuggestions() {
if (copilotCompletor) {
copilotCompletor.dispose()
}
copilotCompletor = languages.registerInlineCompletionsProvider(
{ pattern: '**' },
{
freeInlineCompletions(completions) {},
async provideInlineCompletions(model, position, context, token) {
abortController?.abort()
const textUntilPosition = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
})
let items: languages.InlineCompletions<languages.InlineCompletion>['items'] = []
const lastChar = textUntilPosition[textUntilPosition.length - 1]
if (textUntilPosition.trim().length > 5 && lastChar.match(/[\(\{\s:=]/)) {
const textAfterPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: position.column,
endLineNumber: model.getLineCount() + 1,
endColumn: 1
})
const thisTs = Date.now()
copilotTs = thisTs
await sleep(500)
if (copilotTs === thisTs) {
abortController?.abort()
abortController = new AbortController()
const insertText = await editorCodeCompletion(
textUntilPosition,
textAfterPosition,
lang,
abortController
)
if (insertText) {
items = [
{
insertText,
range: {
startLineNumber: position.lineNumber,
startColumn: position.column,
endLineNumber: position.lineNumber,
endColumn: position.column
},
completeBracketPairs: false
}
]
}
}
}
return {
items,
commands: []
}
}
}
)
}
$: $copilotInfo.exists_openai_resource_path &&
$copilotInfo.code_completion_enabled &&
addCopilotSuggestions()
const outputChannel = {
name: 'Language Server Client',
appendLine: (msg: string) => {
@@ -922,6 +996,7 @@
disposeMethod && disposeMethod()
websocketInterval && clearInterval(websocketInterval)
sqlSchemaCompletor && sqlSchemaCompletor.dispose()
copilotCompletor && copilotCompletor.dispose()
})
</script>

View File

@@ -11,8 +11,8 @@
} from '$lib/gen'
import { initHistory, push, redo, undo } from '$lib/history'
import {
copilotInfo,
enterpriseLicense,
existsOpenaiResourcePath,
hubScripts,
userStore,
workspaceStore
@@ -638,7 +638,11 @@
try {
if (flowModule.value.type === 'rawscript') {
const stepSchema: Schema = JSON.parse(JSON.stringify($flowStateStore[module.id].schema)) // deep copy
if (module.source === 'hub' && pastModule !== undefined && $existsOpenaiResourcePath) {
if (
module.source === 'hub' &&
pastModule !== undefined &&
$copilotInfo.exists_openai_resource_path
) {
// ask AI to set step inputs
abortController = new AbortController()
const inputs = await glueCopilot(
@@ -677,7 +681,11 @@
}
})
} else {
if (module.source === 'hub' && pastModule !== undefined && !$existsOpenaiResourcePath) {
if (
module.source === 'hub' &&
pastModule !== undefined &&
!$copilotInfo.exists_openai_resource_path
) {
sendUserToast(
'For better input generation, enable Windmill AI in the workspace settings',
true

View File

@@ -244,7 +244,7 @@
WORKSPACES_EDIT_DEPLOY_TO: 'workspaces.edit_deploy_to',
WORKSPACES_EDIT_AUTO_INVITE_DOMAIN: 'workspaces.edit_auto_invite_domain',
WORKSPACES_EDIT_WEBHOOK: 'workspaces.edit_webhook',
WORKSPACES_EDIT_OPENAI_RESOURCE_PATH: 'workspaces.edit_openai_resource_path',
WORKSPACES_EDIT_COPILOT_CONFIG: 'workspaces.edit_copilot_config',
WORKSPACES_EDIT_ERROR_HANDLER: 'workspaces.edit_error_handler',
WORKSPACES_CREATE: 'workspaces.create',
WORKSPACES_UPDATE: 'workspaces.update',

View File

@@ -6,7 +6,7 @@
import type { FlowCopilotContext } from './flow'
import { faMagicWandSparkles } from '@fortawesome/free-solid-svg-icons'
import { charsToNumber } from '../flows/idUtils'
import { existsOpenaiResourcePath } from '$lib/stores'
import { copilotInfo } from '$lib/stores'
import Popup from '../common/popup/Popup.svelte'
import type { FlowModule } from '$lib/gen'
import type { FlowEditorContext } from '../flows/types'
@@ -30,7 +30,7 @@
$: copilotStatus && copilotPopover?.refresh()
</script>
{#if $existsOpenaiResourcePath}
{#if $copilotInfo.exists_openai_resource_path}
<ManualPopover bind:this={copilotPopover}>
<Button
size="xs"

View File

@@ -6,7 +6,7 @@
import { sendUserToast } from '$lib/toast'
import type Editor from '../Editor.svelte'
import { faCheck, faClose, faMagicWandSparkles } from '@fortawesome/free-solid-svg-icons'
import { dbSchemas, existsOpenaiResourcePath, type DBSchema } from '$lib/stores'
import { dbSchemas, copilotInfo, type DBSchema } from '$lib/stores'
import type DiffEditor from '../DiffEditor.svelte'
import { scriptLangToEditorLang } from '$lib/scripts'
import Popover from '../Popover.svelte'
@@ -187,12 +187,12 @@
</Button>
</svelte:fragment>
{@const fixAction = (_) => {
if ($existsOpenaiResourcePath) {
if ($copilotInfo.exists_openai_resource_path) {
onFix(() => close(null))
}
}}
<div use:fixAction>
{#if $existsOpenaiResourcePath}
{#if $copilotInfo.exists_openai_resource_path}
<div class="w-[42rem] min-h-[3rem] max-h-[34rem] overflow-y-scroll">
{#if $generatedCode.length > 0}
<div class="overflow-x-scroll">

View File

@@ -13,7 +13,7 @@
} from '@fortawesome/free-solid-svg-icons'
import Popup from '../common/popup/Popup.svelte'
import { Icon } from 'svelte-awesome'
import { dbSchemas, existsOpenaiResourcePath, type DBSchema } from '$lib/stores'
import { dbSchemas, copilotInfo, type DBSchema } 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'
@@ -269,7 +269,7 @@
<LoadingIcon />
{/if}
</div>
{:else if $existsOpenaiResourcePath}
{:else if $copilotInfo.exists_openai_resource_path}
<div class="flex w-96">
<input
type="text"

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { faMagicWandSparkles } from '@fortawesome/free-solid-svg-icons'
import { Icon } from 'svelte-awesome'
import { existsOpenaiResourcePath, hubScripts } from '$lib/stores'
import { copilotInfo, hubScripts } from '$lib/stores'
import { getContext } from 'svelte'
import type { FlowEditorContext } from '../flows/types'
import type { FlowCopilotContext, FlowCopilotModule } from './flow'
@@ -52,7 +52,7 @@
}
async function onGenerate() {
if (!selectedCompletion && !$existsOpenaiResourcePath) {
if (!selectedCompletion && !$copilotInfo.exists_openai_resource_path) {
sendUserToast(
'Windmill AI is not enabled, you can activate it in the workspace settings',
true

View File

@@ -0,0 +1,61 @@
import type { CreateChatCompletionRequestMessage } from 'openai/resources/chat'
import { getNonStreamingCompletion } from './lib'
const systemPrompt =
'You are a code completion assistant, return the code that should go inside the <completion></completion> tags. If you add a line break, take into account the indentation of the code. You can also not return anything if you think the code is already complete.'
const prompt = `complete the following code:
\`\`\`{language}
{before}<completion></completion>{after}
\`\`\``
const exampleUser = prompt
.replace('{language}', 'python')
.replace(
'{before}',
`def main(number):
# print the number and divide it by 4
`
)
.replace('{after}', `\n\n return number`)
const exampleAssistant = `<completion>print(number)
number = number / 4</completion>`
export async function editorCodeCompletion(
before: string,
after: string,
lang: string,
abortController: AbortController
) {
const messages: CreateChatCompletionRequestMessage[] = [
{
role: 'system',
content: systemPrompt
},
{
role: 'user',
content: exampleUser
},
{
role: 'assistant',
content: exampleAssistant
},
{
role: 'user',
content: prompt
.replace('{language}', lang)
.replace('{before}', before)
.replace('{after}', after)
}
]
try {
const result = await getNonStreamingCompletion(messages, abortController, 'gpt-3.5-turbo')
const completion = result.match(/<completion>(.*)<\/completion>/s)?.[1] || ''
return completion
} catch (err) {
if (err.message !== 'Request was aborted.') {
console.log(err)
}
}
}

View File

@@ -3,7 +3,7 @@ import { OpenAPI } from '../../gen/core/OpenAPI'
import { ResourceService, Script, WorkspaceService } from '../../gen'
import type { Writable } from 'svelte/store'
import { existsOpenaiResourcePath, workspaceStore, type DBSchema } from '$lib/stores'
import { copilotInfo, workspaceStore, type DBSchema } from '$lib/stores'
import { formatResourceTypes } from './utils'
import { EDIT_CONFIG, FIX_CONFIG, GEN_CONFIG } from './prompts'
@@ -39,12 +39,13 @@ workspaceStore.subscribe(async (value) => {
})
if (value) {
try {
existsOpenaiResourcePath.set(
await WorkspaceService.existsOpenaiResourcePath({ workspace: value })
)
copilotInfo.set(await WorkspaceService.getCopilotInfo({ workspace: value }))
} catch (err) {
existsOpenaiResourcePath.set(false)
console.error('Could not get if OpenAI resource exists')
copilotInfo.set({
exists_openai_resource_path: false,
code_completion_enabled: false
})
console.error('Could not get copilot info')
}
}
})
@@ -175,7 +176,8 @@ const PROMPTS_CONFIGS = {
export async function getNonStreamingCompletion(
messages: CreateChatCompletionRequestMessage[],
abortController: AbortController
abortController: AbortController,
model: string = 'gpt-4'
) {
if (!openai) {
throw new Error('OpenAI not initialized')
@@ -185,13 +187,19 @@ export async function getNonStreamingCompletion(
{
...openaiConfig,
messages,
stream: false
stream: false,
model
},
{
signal: abortController.signal
}
)
// if (completion.usage) {
// const { prompt_tokens, completion_tokens } = completion.usage
// console.log('Cost: ', (prompt_tokens * 0.0015 + completion_tokens * 0.002) / 1000)
// }
return completion.choices[0]?.message.content || ''
}

View File

@@ -8,7 +8,7 @@
import { Icon } from 'svelte-awesome'
import InsertModuleButton from './InsertModuleButton.svelte'
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import { existsOpenaiResourcePath } from '$lib/stores'
import { copilotInfo } from '$lib/stores'
import Menu from '$lib/components/common/menu/Menu.svelte'
import InsertTriggerButton from './InsertTriggerButton.svelte'
@@ -149,7 +149,7 @@
<Menu pointerDown noMinW placement="bottom-center" let:close bind:show={openNoCopilot}>
<button
title="AI Flow Builder"
on:pointerdown={$existsOpenaiResourcePath
on:pointerdown={$copilotInfo.exists_openai_resource_path
? (ev) => {
ev.preventDefault()
ev.stopPropagation()
@@ -162,7 +162,7 @@
>
<Icon data={faMagicWandSparkles} scale={1} />
</button>
{#if !$existsOpenaiResourcePath}
{#if !$copilotInfo.exists_openai_resource_path}
<div class="text-primary p-4">
<p class="text-sm w-80"
>Enable Windmill AI in the <a

View File

@@ -68,7 +68,13 @@ export const hubScripts = writable<
}>
| undefined
>(undefined)
export const existsOpenaiResourcePath = writable<boolean>(false)
export const copilotInfo = writable<{
exists_openai_resource_path: boolean
code_completion_enabled: boolean
}>({
exists_openai_resource_path: false,
code_completion_enabled: false
})
type SQLBaseSchema = {
[schemaKey: string]: {

View File

@@ -67,9 +67,9 @@
resource_type: 'openai'
}
})
await WorkspaceService.editOpenaiResourcePath({
await WorkspaceService.editCopilotConfig({
workspace: id,
requestBody: { openai_resource_path: path }
requestBody: { openai_resource_path: path, code_completion_enabled: false }
})
}

View File

@@ -16,7 +16,7 @@
import { OauthService, Script, WorkspaceService } from '$lib/gen'
import {
enterpriseLicense,
existsOpenaiResourcePath,
copilotInfo,
superadmin,
userStore,
usersWorkspaceStore,
@@ -29,6 +29,7 @@
import { Slack } from 'lucide-svelte'
import PremiumInfo from '$lib/components/settings/PremiumInfo.svelte'
import Toggle from '$lib/components/Toggle.svelte'
let initialPath: string
let scriptPath: string
@@ -42,6 +43,7 @@
let errorHandlerScriptPath: string
let errorHandlerItemKind: 'script' = 'script'
let openaiResourceInitialPath: string | undefined = undefined
let codeCompletionEnabled: boolean = false
let tab =
($page.url.searchParams.get('tab') as
| 'users'
@@ -111,24 +113,35 @@
}
}
async function editOpenaiResourcePath(openaiResourcePath: string): Promise<void> {
async function editCopilotConfig(openaiResourcePath: string): Promise<void> {
// in JS, an empty string is also falsy
openaiResourceInitialPath = openaiResourcePath
if (openaiResourcePath) {
await WorkspaceService.editOpenaiResourcePath({
await WorkspaceService.editCopilotConfig({
workspace: $workspaceStore!,
requestBody: { openai_resource_path: openaiResourcePath }
requestBody: {
openai_resource_path: openaiResourcePath,
code_completion_enabled: codeCompletionEnabled
}
})
copilotInfo.set({
exists_openai_resource_path: true,
code_completion_enabled: codeCompletionEnabled
})
existsOpenaiResourcePath.set(true)
sendUserToast('OpenAI resource set')
} else {
await WorkspaceService.editOpenaiResourcePath({
await WorkspaceService.editCopilotConfig({
workspace: $workspaceStore!,
requestBody: { openai_resource_path: undefined }
requestBody: {
openai_resource_path: undefined,
code_completion_enabled: codeCompletionEnabled
}
})
copilotInfo.set({
exists_openai_resource_path: true,
code_completion_enabled: codeCompletionEnabled
})
existsOpenaiResourcePath.set(false)
sendUserToast(`OpenAI resource removed`)
}
sendUserToast(`Copilot settings updated`)
}
async function loadSettings(): Promise<void> {
@@ -147,6 +160,7 @@
openaiResourceInitialPath = settings.openai_resource_path
errorHandlerScriptPath = (settings.error_handler ?? '').split('/').slice(1).join('/')
errorHandlerInitialPath = errorHandlerScriptPath
codeCompletionEnabled = settings.code_completion_enabled
}
$: {
@@ -447,7 +461,8 @@
<PageHeader title="Windmill AI" primary={false} />
<div class="mt-2">
<Alert type="info" title="Select an OpenAI resource to unlock Windmill AI features!">
Windmill AI currently only supports OpenAI's GPT-4.
Windmill AI uses OpenAI's GPT-3.5-turbo for code completion and GPT-4 for all other AI
features.
</Alert>
</div>
<div class="mt-5">
@@ -456,11 +471,21 @@
resourceType="openai"
initialValue={openaiResourceInitialPath}
on:change={(ev) => {
editOpenaiResourcePath(ev.detail)
editCopilotConfig(ev.detail)
}}
/>
{/key}
</div>
<div class="mt-3">
<Toggle
class="mr-2"
bind:checked={codeCompletionEnabled}
options={{ right: 'Enable code completion' }}
on:change={() => {
editCopilotConfig(openaiResourceInitialPath || '')
}}
/>
</div>
{/if}
{:else}
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4" role="alert">