feat: use openai resource for windmill AI (#1902)
* feat: add inline code gen flow * feat(frontend): add script gen to flow and app builders * fix(backend): allow all users to use openai * feat: use openai resource for windmill AI --------- Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE workspace_settings DROP COLUMN openai_resource_path;
|
||||
ALTER TABLE workspace_settings ADD COLUMN openai_key VARCHAR(255);
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE workspace_settings ADD COLUMN openai_resource_path VARCHAR(1000);
|
||||
ALTER TABLE workspace_settings DROP COLUMN openai_key;
|
||||
@@ -971,7 +971,7 @@ paths:
|
||||
type: string
|
||||
deploy_to:
|
||||
type: string
|
||||
openai_key:
|
||||
openai_resource_path:
|
||||
type: string
|
||||
error_handler:
|
||||
type: string
|
||||
@@ -1126,24 +1126,24 @@ paths:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/edit_openai_key:
|
||||
|
||||
/w/{workspace}/workspaces/edit_openai_resource_path:
|
||||
post:
|
||||
summary: edit OpenAI key
|
||||
operationId: editOpenaiKey
|
||||
summary: edit OpenAI resource path
|
||||
operationId: editOpenaiResourcePath
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: WorkspaceOpenAIKey
|
||||
description: WorkspaceOpenaiResourcePath
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
openai_key:
|
||||
openai_resource_path:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
@@ -1153,10 +1153,10 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/exists_openai_key:
|
||||
/w/{workspace}/workspaces/exists_openai_resource_path:
|
||||
get:
|
||||
summary: OpenAI key exists
|
||||
operationId: existsOpenaiKey
|
||||
summary: OpenAI resource path exists
|
||||
operationId: existsOpenaiResourcePath
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{db::DB, users::Authed, HTTP_CLIENT};
|
||||
use crate::{db::DB, users::Authed, variables::build_crypt, HTTP_CLIENT};
|
||||
|
||||
use axum::{
|
||||
body::{Bytes, StreamBody},
|
||||
@@ -8,19 +8,23 @@ use axum::{
|
||||
routing::post,
|
||||
Router,
|
||||
};
|
||||
use magic_crypt::MagicCryptTrait;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::error::{to_anyhow, Error};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
let router = Router::new().route("/proxy/*openai_path", post(proxy));
|
||||
|
||||
router
|
||||
}
|
||||
|
||||
struct OpenAIKey {
|
||||
openai_key: Option<String>,
|
||||
#[derive(Deserialize)]
|
||||
struct OpenaiResource {
|
||||
api_key: String,
|
||||
organisation: Option<String>,
|
||||
}
|
||||
|
||||
async fn proxy(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -28,35 +32,80 @@ async fn proxy(
|
||||
body: Bytes,
|
||||
) -> impl IntoResponse {
|
||||
let mut tx = db.begin().await?;
|
||||
let settings = sqlx::query_as!(
|
||||
OpenAIKey,
|
||||
"SELECT openai_key FROM workspace_settings WHERE workspace_id = $1",
|
||||
let openai_resource_path = sqlx::query_scalar!(
|
||||
"SELECT openai_resource_path FROM workspace_settings WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("getting openai_key: {e}")))?;
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
let openai_key = match settings.openai_key {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
return Err(Error::BadRequest(
|
||||
"openai_key is not set for this workspace".to_string(),
|
||||
))
|
||||
}
|
||||
if openai_resource_path.is_none() {
|
||||
return Err(Error::InternalErr(
|
||||
"OpenAI resource not configured".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let openai_resource_path = openai_resource_path.unwrap();
|
||||
|
||||
tx = db.begin().await?;
|
||||
let resource = sqlx::query_scalar!(
|
||||
"SELECT value
|
||||
FROM resource
|
||||
WHERE path = $1 AND workspace_id = $2",
|
||||
&openai_resource_path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
if resource.is_none() {
|
||||
return Err(Error::InternalErr(
|
||||
"OpenAI resource missing value".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut resource: OpenaiResource = serde_json::from_value(resource.unwrap())
|
||||
.map_err(|e| Error::InternalErr(format!("validating openai resource {e}")))?;
|
||||
|
||||
let openai_api_key_path = if resource.api_key.starts_with("$var:") {
|
||||
resource.api_key.strip_prefix("$var:").unwrap().to_string()
|
||||
} else {
|
||||
return Err(Error::InternalErr(
|
||||
"OpenAI resource api key must be a variable".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let resp = HTTP_CLIENT
|
||||
tx = db.begin().await?;
|
||||
resource.api_key = sqlx::query_scalar!(
|
||||
"SELECT value
|
||||
FROM variable
|
||||
WHERE path = $1 AND workspace_id = $2",
|
||||
&openai_api_key_path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
let mc = build_crypt(&mut tx, &w_id).await?;
|
||||
tx.commit().await?;
|
||||
resource.api_key = mc
|
||||
.decrypt_base64_to_string(resource.api_key)
|
||||
.map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
|
||||
let mut request = HTTP_CLIENT
|
||||
.post(String::from("https://api.openai.com/v1/") + &openai_path)
|
||||
.header("content-type", "application/json")
|
||||
.header("authorization", format!("Bearer {}", openai_key))
|
||||
.body(body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
.header("authorization", format!("Bearer {}", resource.api_key))
|
||||
.body(body);
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
if resource.organisation.is_some() {
|
||||
request = request.header("OpenAI-Organization", resource.organisation.unwrap());
|
||||
}
|
||||
|
||||
let resp = request.send().await.map_err(to_anyhow)?;
|
||||
|
||||
tx = db.begin().await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
|
||||
@@ -67,8 +67,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_key", post(edit_openai_key))
|
||||
.route("/exists_openai_key", get(exists_openai_key) )
|
||||
.route("/edit_openai_resource_path", post(edit_openai_resource_path))
|
||||
.route("/exists_openai_resource_path", get(exists_openai_resource_path) )
|
||||
.route("/edit_error_handler", post(edit_error_handler));
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
@@ -116,7 +116,7 @@ pub struct WorkspaceSettings {
|
||||
pub plan: Option<String>,
|
||||
pub webhook: Option<String>,
|
||||
pub deploy_to: Option<String>,
|
||||
pub openai_key: Option<String>,
|
||||
pub openai_resource_path: Option<String>,
|
||||
pub error_handler: Option<String>,
|
||||
}
|
||||
|
||||
@@ -158,8 +158,8 @@ struct EditWebhook {
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EditOpenAIKey {
|
||||
openai_key: Option<String>,
|
||||
struct EditOpenaiResourcePath {
|
||||
openai_resource_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -661,28 +661,28 @@ async fn edit_webhook(
|
||||
Ok(format!("Edit webhook for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
async fn edit_openai_key(
|
||||
async fn edit_openai_resource_path(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
Authed { is_admin, username, .. }: Authed,
|
||||
Json(eo): Json<EditOpenAIKey>,
|
||||
Json(eo): Json<EditOpenaiResourcePath>,
|
||||
) -> Result<String> {
|
||||
require_admin(is_admin, &username)?;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
if let Some(openai_key) = &eo.openai_key {
|
||||
if let Some(openai_resource_path) = &eo.openai_resource_path {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET openai_key = $1 WHERE workspace_id = $2",
|
||||
openai_key,
|
||||
"UPDATE workspace_settings SET openai_resource_path = $1 WHERE workspace_id = $2",
|
||||
openai_resource_path,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET openai_key = NULL WHERE workspace_id = $1",
|
||||
"UPDATE workspace_settings SET openai_resource_path = NULL WHERE workspace_id = $1",
|
||||
&w_id,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
@@ -691,35 +691,35 @@ async fn edit_openai_key(
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
"workspaces.edit_openai_key",
|
||||
"workspaces.edit_openai_resource_path",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email),
|
||||
Some([("openai_key", &format!("{:?}", eo.openai_key)[..])].into()),
|
||||
Some([("openai_resource_path", &format!("{:?}", eo.openai_resource_path)[..])].into()),
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("Edit openai_key for workspace {}", &w_id))
|
||||
Ok(format!("Edit openai_resource_path for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
|
||||
async fn exists_openai_key(
|
||||
async fn exists_openai_resource_path(
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
) -> JsonResult<bool> {
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
let openai_key = sqlx::query_scalar!(
|
||||
"SELECT openai_key FROM workspace_settings WHERE workspace_id = $1",
|
||||
let openai_resource_path = sqlx::query_scalar!(
|
||||
"SELECT openai_resource_path FROM workspace_settings WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("getting openai_key: {e}")))?;
|
||||
.map_err(|e| Error::InternalErr(format!("getting openai_resource_path: {e}")))?;
|
||||
tx.commit().await?;
|
||||
|
||||
let exists = openai_key.is_some();
|
||||
let exists = openai_resource_path.is_some();
|
||||
|
||||
Ok(Json(exists))
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<div class="relative w-full">
|
||||
<div class="absolute inset-y-0 right-0 flex items-center px-2">
|
||||
<input class="hidden js-password-toggle" id="toggle" type="checkbox" />
|
||||
<input class="!hidden js-password-toggle" id="toggle" type="checkbox" />
|
||||
<label
|
||||
class="bg-gray-300 hover:bg-gray-400 rounded px-2 py-1 text-sm text-gray-600 font-mono cursor-pointer js-password-label"
|
||||
for="toggle">show</label
|
||||
|
||||
@@ -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 { existsOpenaiKeyStore } from '$lib/stores'
|
||||
import { existsOpenaiResourcePath } from '$lib/stores'
|
||||
import type DiffEditor from '../DiffEditor.svelte'
|
||||
import { scriptLangToEditorLang } from '$lib/scripts'
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
// state
|
||||
let genLoading: boolean = false
|
||||
let openAIAvailable: boolean | undefined = undefined
|
||||
let openaiAvailable: boolean | undefined = undefined
|
||||
let generatedCode = ''
|
||||
|
||||
async function onFix() {
|
||||
@@ -57,14 +57,11 @@
|
||||
generatedCode = ''
|
||||
}
|
||||
|
||||
async function checkIfOpenAIAvailable(lang: SupportedLanguage) {
|
||||
try {
|
||||
const exists = $existsOpenaiKeyStore
|
||||
openAIAvailable = exists && SUPPORTED_LANGUAGES.has(lang)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
sendUserToast('Failed to check if OpenAI is available', true)
|
||||
}
|
||||
function checkIfOpenaiAvailable(
|
||||
lang: SupportedLanguage | 'frontend',
|
||||
existsOpenaiResourcePath: boolean
|
||||
) {
|
||||
openaiAvailable = existsOpenaiResourcePath && SUPPORTED_LANGUAGES.has(lang)
|
||||
}
|
||||
|
||||
function showDiff() {
|
||||
@@ -78,7 +75,7 @@
|
||||
diffEditor?.hide()
|
||||
}
|
||||
|
||||
$: checkIfOpenAIAvailable(lang)
|
||||
$: checkIfOpenaiAvailable(lang, $existsOpenaiResourcePath)
|
||||
|
||||
$: lang && (generatedCode = '')
|
||||
|
||||
@@ -87,7 +84,7 @@
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
{#if openAIAvailable}
|
||||
{#if openaiAvailable}
|
||||
<div class="mt-2">
|
||||
{#if generatedCode}
|
||||
<div class="flex gap-1">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Popup from '../common/popup/Popup.svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import { existsOpenaiKeyStore } from '$lib/stores'
|
||||
import { existsOpenaiResourcePath } 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'
|
||||
@@ -25,7 +25,7 @@
|
||||
// state
|
||||
let funcDesc: string = ''
|
||||
let genLoading: boolean = false
|
||||
let openAIAvailable: boolean | undefined = undefined
|
||||
let openaiAvailable: boolean | undefined = undefined
|
||||
let button: HTMLButtonElement | undefined
|
||||
let input: HTMLInputElement | undefined
|
||||
let generatedCode = ''
|
||||
@@ -33,6 +33,9 @@
|
||||
let isEdit = false
|
||||
|
||||
async function onGenerate() {
|
||||
if (funcDesc.length <= 0) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// close popup ^^
|
||||
const elem = document.activeElement as HTMLElement
|
||||
@@ -75,14 +78,11 @@
|
||||
generatedCode = ''
|
||||
}
|
||||
|
||||
async function checkIfOpenAIAvailable(lang: SupportedLanguage | 'frontend') {
|
||||
try {
|
||||
const exists = $existsOpenaiKeyStore
|
||||
openAIAvailable = exists && SUPPORTED_LANGUAGES.has(lang)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
sendUserToast('Failed to check if OpenAI is available', true)
|
||||
}
|
||||
function checkIfOpenaiAvailable(
|
||||
lang: SupportedLanguage | 'frontend',
|
||||
existsOpenaiResourcePath: boolean
|
||||
) {
|
||||
openaiAvailable = existsOpenaiResourcePath && SUPPORTED_LANGUAGES.has(lang)
|
||||
}
|
||||
|
||||
function showDiff() {
|
||||
@@ -106,7 +106,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
$: checkIfOpenAIAvailable(lang)
|
||||
$: checkIfOpenaiAvailable(lang, $existsOpenaiResourcePath)
|
||||
|
||||
$: input?.focus()
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
$: selection && (isEdit = !selection.isEmpty())
|
||||
</script>
|
||||
|
||||
{#if openAIAvailable}
|
||||
{#if openaiAvailable}
|
||||
{#if generatedCode}
|
||||
{#if inlineScript}
|
||||
<div class="flex gap-1">
|
||||
@@ -210,7 +210,7 @@
|
||||
bind:value={funcDesc}
|
||||
class="!w-auto grow"
|
||||
on:keypress={({ key }) => {
|
||||
if (key === 'Enter') {
|
||||
if (key === 'Enter' && funcDesc.length > 0) {
|
||||
onGenerate()
|
||||
}
|
||||
}}
|
||||
@@ -225,6 +225,7 @@
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Generate"
|
||||
on:click={onGenerate}
|
||||
disabled={funcDesc.length <= 0}
|
||||
>
|
||||
<Icon data={faMagicWandSparkles} />
|
||||
</Button>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { OpenAI } from 'openai'
|
||||
import { OpenAPI } from '../../gen/core/OpenAPI'
|
||||
import { ResourceService, Script, WorkspaceService } from '../../gen'
|
||||
|
||||
import { existsOpenaiKeyStore, workspaceStore } from '$lib/stores'
|
||||
import { existsOpenaiResourcePath, workspaceStore } from '$lib/stores'
|
||||
import { formatResourceTypes } from './utils'
|
||||
import { scriptLangToEditorLang } from '$lib/scripts'
|
||||
|
||||
@@ -45,10 +45,10 @@ workspaceStore.subscribe(async (value) => {
|
||||
workspace = value
|
||||
if (workspace) {
|
||||
try {
|
||||
existsOpenaiKeyStore.set(await WorkspaceService.existsOpenaiKey({ workspace }))
|
||||
existsOpenaiResourcePath.set(await WorkspaceService.existsOpenaiResourcePath({ workspace }))
|
||||
} catch (err) {
|
||||
existsOpenaiKeyStore.set(false)
|
||||
console.error('Could not get if openai key exists')
|
||||
existsOpenaiResourcePath.set(false)
|
||||
console.error('Could not get if OpenAI resource exists')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -65,7 +65,7 @@ export const hubScripts = writable<
|
||||
}>
|
||||
| undefined
|
||||
>(undefined)
|
||||
export const existsOpenaiKeyStore = writable<boolean>(false)
|
||||
export const existsOpenaiResourcePath = writable<boolean>(false)
|
||||
|
||||
export function switchWorkspace(workspace: string | undefined) {
|
||||
localStorage.removeItem('flow')
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import DeployToSetting from '$lib/components/DeployToSetting.svelte'
|
||||
import InviteUser from '$lib/components/InviteUser.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
import SearchItems from '$lib/components/SearchItems.svelte'
|
||||
import Slider from '$lib/components/Slider.svelte'
|
||||
@@ -27,7 +28,7 @@
|
||||
} from '$lib/gen'
|
||||
import {
|
||||
enterpriseLicense,
|
||||
existsOpenaiKeyStore,
|
||||
existsOpenaiResourcePath,
|
||||
superadmin,
|
||||
userStore,
|
||||
usersWorkspaceStore,
|
||||
@@ -54,10 +55,10 @@
|
||||
let customer_id: string | undefined = undefined
|
||||
let webhook: string | undefined = undefined
|
||||
let workspaceToDeployTo: string | undefined = undefined
|
||||
let openAIKey: string | undefined = undefined
|
||||
let errorHandlerInitialPath: string
|
||||
let errorHandlerScriptPath: string
|
||||
let errorHandlerItemKind: 'script' = 'script'
|
||||
let openaiResourceInitialPath: string | undefined = undefined
|
||||
let tab =
|
||||
($page.url.searchParams.get('tab') as
|
||||
| 'users'
|
||||
@@ -127,22 +128,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function editOpenAIKey(): Promise<void> {
|
||||
async function editOpenaiResourcePath(openaiResourcePath: string): Promise<void> {
|
||||
// in JS, an empty string is also falsy
|
||||
if (openAIKey) {
|
||||
await WorkspaceService.editOpenaiKey({
|
||||
openaiResourceInitialPath = openaiResourcePath
|
||||
if (openaiResourcePath) {
|
||||
await WorkspaceService.editOpenaiResourcePath({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: { openai_key: openAIKey }
|
||||
requestBody: { openai_resource_path: openaiResourcePath }
|
||||
})
|
||||
existsOpenaiKeyStore.set(true)
|
||||
sendUserToast('OpenAI key set')
|
||||
existsOpenaiResourcePath.set(true)
|
||||
sendUserToast('OpenAI resource set')
|
||||
} else {
|
||||
await WorkspaceService.editOpenaiKey({
|
||||
await WorkspaceService.editOpenaiResourcePath({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: { openai_key: undefined }
|
||||
requestBody: { openai_resource_path: undefined }
|
||||
})
|
||||
existsOpenaiKeyStore.set(false)
|
||||
sendUserToast(`OpenAI key removed`)
|
||||
existsOpenaiResourcePath.set(false)
|
||||
sendUserToast(`OpenAI resource removed`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +162,7 @@
|
||||
customer_id = settings.customer_id
|
||||
workspaceToDeployTo = settings.deploy_to
|
||||
webhook = settings.webhook
|
||||
openAIKey = settings.openai_key
|
||||
openaiResourceInitialPath = settings.openai_resource_path
|
||||
errorHandlerScriptPath = (settings.error_handler ?? '').split('/').slice(1).join('/')
|
||||
errorHandlerInitialPath = errorHandlerScriptPath
|
||||
}
|
||||
@@ -300,7 +302,7 @@
|
||||
|
||||
<Tab size="md" value="openai">
|
||||
<div class="flex gap-2 items-center my-1"
|
||||
>OpenAI Credentials <span class="text-white px-2 py-1 rounded-full text-xs bg-red-500"
|
||||
>Windmill AI <span class="text-white px-2 py-1 rounded-full text-xs bg-red-500"
|
||||
>Beta</span
|
||||
></div
|
||||
>
|
||||
@@ -888,15 +890,22 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else if tab == 'openai'}
|
||||
<PageHeader title="OpenAI Credentials" primary={false} />
|
||||
<PageHeader title="Windmill AI" primary={false} />
|
||||
<div class="mt-2"
|
||||
><Alert type="info" title="Experimental feature"
|
||||
>Enter your OpenAI api key to unlock Windmill's AI features!</Alert
|
||||
>
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<input type="text" placeholder="Secret GPT-4 API key" bind:value={openAIKey} />
|
||||
<Button size="md" on:click={editOpenAIKey}>Save</Button>
|
||||
>Select an OpenAI resource to unlock Windmill AI features!</Alert
|
||||
></div
|
||||
>
|
||||
<div class="mt-5">
|
||||
{#key openaiResourceInitialPath}
|
||||
<ResourcePicker
|
||||
resourceType="openai"
|
||||
initialValue={openaiResourceInitialPath}
|
||||
on:change={(ev) => {
|
||||
editOpenaiResourcePath(ev.detail)
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
|
||||
Reference in New Issue
Block a user