AI Config CRUD complete
This commit is contained in:
parent
ca4b17bc9b
commit
43135e4274
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import {Body, Label, Icon, Tag} from "@budibase/bbui"
|
import { Body, Label, Icon } from "@budibase/bbui"
|
||||||
import OpenAILogo from "./logos/OpenAI.svelte"
|
import OpenAILogo from "./logos/OpenAI.svelte"
|
||||||
import AnthropicLogo from "./logos/Anthropic.svelte"
|
import AnthropicLogo from "./logos/Anthropic.svelte"
|
||||||
import { Providers } from "./constants"
|
import { Providers } from "./constants"
|
||||||
|
|
|
@ -5,13 +5,10 @@
|
||||||
Input,
|
Input,
|
||||||
Select,
|
Select,
|
||||||
Toggle,
|
Toggle,
|
||||||
Body,
|
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { ConfigMap, Providers } from "./constants"
|
import {ConfigMap, Providers} from "./constants"
|
||||||
import { API } from "api"
|
|
||||||
|
|
||||||
export let defaultConfig = {
|
export let config = {
|
||||||
active: false,
|
active: false,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
}
|
}
|
||||||
|
@ -19,11 +16,10 @@
|
||||||
export let saveHandler
|
export let saveHandler
|
||||||
export let deleteHandler
|
export let deleteHandler
|
||||||
|
|
||||||
let aiConfig = defaultConfig
|
|
||||||
let validation
|
let validation
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
const { provider, defaultModel, name, apiKey } = aiConfig
|
const {provider, defaultModel, name, apiKey} = config
|
||||||
validation = provider && defaultModel && name && apiKey
|
validation = provider && defaultModel && name && apiKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,20 +27,15 @@
|
||||||
const provider = evt.detail
|
const provider = evt.detail
|
||||||
// grab the preset config from the constants for that provider and fill it in
|
// grab the preset config from the constants for that provider and fill it in
|
||||||
if (ConfigMap[provider]) {
|
if (ConfigMap[provider]) {
|
||||||
aiConfig = {
|
config = {
|
||||||
...aiConfig,
|
...config,
|
||||||
...ConfigMap[provider],
|
...ConfigMap[provider],
|
||||||
provider
|
provider,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
aiConfig.provider = provider
|
config.provider = provider
|
||||||
// aiConfig = {
|
|
||||||
// ...aiConfig,
|
|
||||||
// provider
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent
|
<ModalContent
|
||||||
|
@ -57,10 +48,14 @@
|
||||||
title="Custom AI Configuration"
|
title="Custom AI Configuration"
|
||||||
>
|
>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
<div class="form-row">
|
||||||
|
<Label size="M">Name</Label>
|
||||||
|
<Input placeholder={"Test 1"} bind:value={config.name}/>
|
||||||
|
</div>
|
||||||
<Label size="M">Provider</Label>
|
<Label size="M">Provider</Label>
|
||||||
<Select
|
<Select
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
bind:value={aiConfig.provider}
|
bind:value={config.provider}
|
||||||
options={Object.keys(Providers)}
|
options={Object.keys(Providers)}
|
||||||
on:change={prefillConfig}
|
on:change={prefillConfig}
|
||||||
/>
|
/>
|
||||||
|
@ -68,25 +63,21 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="M">Default Model</Label>
|
<Label size="M">Default Model</Label>
|
||||||
<Select
|
<Select
|
||||||
placeholder={aiConfig.provider ? "Choose an option" : "Select a provider first"}
|
placeholder={config.provider ? "Choose an option" : "Select a provider first"}
|
||||||
bind:value={aiConfig.defaultModel}
|
bind:value={config.defaultModel}
|
||||||
options={aiConfig.provider ? Providers[aiConfig.provider].models : []}
|
options={config.provider ? Providers[config.provider].models : []}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
|
||||||
<Label size="M">Name</Label>
|
|
||||||
<Input placeholder={"Test 1"} bind:value={aiConfig.name}/>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="M">Base URL</Label>
|
<Label size="M">Base URL</Label>
|
||||||
<Input placeholder={"www.google.com"} bind:value={aiConfig.baseUrl}/>
|
<Input placeholder={"www.google.com"} bind:value={config.baseUrl}/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="M">API Key</Label>
|
<Label size="M">API Key</Label>
|
||||||
<Input type="password" bind:value={aiConfig.apiKey}/>
|
<Input type="password" bind:value={config.apiKey}/>
|
||||||
</div>
|
</div>
|
||||||
<Toggle text="Active" bind:value={aiConfig.active}/>
|
<Toggle text="Active" bind:value={config.active}/>
|
||||||
<Toggle text="Set as default" bind:value={aiConfig.isDefault}/>
|
<Toggle text="Set as default" bind:value={config.isDefault}/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -22,8 +22,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
let aiConfig
|
let fullAIConfig
|
||||||
let currentlyEditingConfig
|
let editingAIConfig = {}
|
||||||
|
let editingUuid
|
||||||
|
|
||||||
$: isCloud = $admin.cloud
|
$: isCloud = $admin.cloud
|
||||||
$: budibaseAIEnabled = $licensing.budibaseAIEnabled
|
$: budibaseAIEnabled = $licensing.budibaseAIEnabled
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
// Fetch the AI configs
|
// Fetch the AI configs
|
||||||
const aiDoc = await API.getConfig(ConfigTypes.AI)
|
const aiDoc = await API.getConfig(ConfigTypes.AI)
|
||||||
if (aiDoc._id) {
|
if (aiDoc._id) {
|
||||||
aiConfig = aiDoc
|
fullAIConfig = aiDoc
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error fetching AI config")
|
notifications.error("Error fetching AI config")
|
||||||
|
@ -42,22 +43,27 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveConfig() {
|
async function saveConfig() {
|
||||||
// Update the config that was changed
|
// Use existing key or generate new one
|
||||||
const updateConfigs = aiConfig.config
|
const id = editingUuid || Helpers.uuid()
|
||||||
|
|
||||||
const config = {
|
// Creating first custom AI Config
|
||||||
|
if (!fullAIConfig) {
|
||||||
|
fullAIConfig = {
|
||||||
type: ConfigTypes.AI,
|
type: ConfigTypes.AI,
|
||||||
config: [
|
config: {
|
||||||
// TODO: include the ones that are already there, or just handle this in the backend
|
[id]: editingAIConfig
|
||||||
aiConfig,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add new or update existing custom AI Config
|
||||||
|
fullAIConfig.config[id] = editingAIConfig
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isNew = !!aiConfig._rev
|
const savedConfig = await API.saveConfig(fullAIConfig)
|
||||||
const savedConfig = await API.saveConfig(config)
|
fullAIConfig._rev = savedConfig._rev
|
||||||
aiConfig._rev = savedConfig._rev
|
fullAIConfig._id = savedConfig._id
|
||||||
aiConfig._id = savedConfig._id
|
notifications.success(`Successfully saved and activated AI Configuration`)
|
||||||
notifications.success(`Successfully saved and activated ${isNew ? "new" : ""} AI Configuration`)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error(
|
notifications.error(
|
||||||
`Failed to save AI Configuration, reason: ${error?.message || "Unknown"}`
|
`Failed to save AI Configuration, reason: ${error?.message || "Unknown"}`
|
||||||
|
@ -65,15 +71,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteConfig(name) {
|
async function deleteConfig(key) {
|
||||||
// Delete a configuration
|
// Delete a configuration
|
||||||
const idx = aiConfig.config.findIndex(config => config.name === currentlyEditingConfig?.name || name)
|
delete fullAIConfig.config[key]
|
||||||
aiConfig.config.splice(idx, 1)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const savedConfig = await API.saveConfig(aiConfig)
|
const savedConfig = await API.saveConfig(fullAIConfig)
|
||||||
aiConfig._rev = savedConfig._rev
|
fullAIConfig._rev = savedConfig._rev
|
||||||
aiConfig._id = savedConfig._id
|
fullAIConfig._id = savedConfig._id
|
||||||
notifications.success(`Deleted config`)
|
notifications.success(`Deleted config`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error(
|
notifications.error(
|
||||||
|
@ -82,13 +87,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function editConfig(config) {
|
function editConfig(uuid) {
|
||||||
currentlyEditingConfig = config
|
editingUuid = uuid
|
||||||
|
editingAIConfig = fullAIConfig?.config[editingUuid]
|
||||||
modal.show()
|
modal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
function newConfig() {
|
function newConfig() {
|
||||||
currentlyEditingConfig = undefined
|
editingUuid = undefined
|
||||||
|
editingAIConfig = undefined
|
||||||
modal.show()
|
modal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +110,7 @@
|
||||||
<AIConfigModal
|
<AIConfigModal
|
||||||
saveHandler={saveConfig}
|
saveHandler={saveConfig}
|
||||||
deleteHandler={deleteConfig}
|
deleteHandler={deleteConfig}
|
||||||
defaultConfig={currentlyEditingConfig}
|
bind:config={editingAIConfig}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
|
@ -134,12 +141,12 @@
|
||||||
</div>
|
</div>
|
||||||
<Body size="S">Use the following interface to select your preferred AI configuration.</Body>
|
<Body size="S">Use the following interface to select your preferred AI configuration.</Body>
|
||||||
<Body size="S">Select your AI Model:</Body>
|
<Body size="S">Select your AI Model:</Body>
|
||||||
{#if aiConfig}
|
{#if fullAIConfig?.config}
|
||||||
{#each aiConfig.config as config}
|
{#each Object.keys(fullAIConfig.config) as key}
|
||||||
<AIConfigTile
|
<AIConfigTile
|
||||||
{config}
|
config={fullAIConfig.config[key]}
|
||||||
editHandler={() => editConfig(config)}
|
editHandler={() => editConfig(key)}
|
||||||
deleteHandler={modal.show}
|
deleteHandler={() => deleteConfig(key)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -28,7 +28,9 @@ import {
|
||||||
SSOConfig,
|
SSOConfig,
|
||||||
SSOConfigType,
|
SSOConfigType,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
OIDCLogosConfig, AIConfig,
|
OIDCLogosConfig,
|
||||||
|
AIConfig,
|
||||||
|
PASSWORD_REPLACEMENT,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import * as pro from "@budibase/pro"
|
import * as pro from "@budibase/pro"
|
||||||
|
|
||||||
|
@ -197,9 +199,11 @@ async function verifyOIDCConfig(config: OIDCConfigs) {
|
||||||
await verifySSOConfig(ConfigType.OIDC, config.configs[0])
|
await verifySSOConfig(ConfigType.OIDC, config.configs[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verifyAIConfig(config: OIDCConfigs) {
|
async function verifyAIConfig(config: AIConfig, existingConfig?: AIConfig) {
|
||||||
// await verifySSOConfig(ConfigType.OIDC, config.configs[0])
|
// ensure that the redacted API keys are not overwritten in the DB
|
||||||
// Shape should be `config_ai`
|
// for (let aiConfig of existingConfig) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +235,7 @@ export async function save(ctx: UserCtx<Config>) {
|
||||||
await verifyOIDCConfig(config)
|
await verifyOIDCConfig(config)
|
||||||
break
|
break
|
||||||
case ConfigType.AI:
|
case ConfigType.AI:
|
||||||
await verifyAIConfig(config)
|
await verifyAIConfig(config, existingConfig)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
@ -314,8 +318,10 @@ function enrichOIDCLogos(oidcLogos: OIDCLogosConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeAIConfig(aiConfig: AIConfig) {
|
function sanitizeAIConfig(aiConfig: AIConfig) {
|
||||||
for (let providerConfig of aiConfig.config) {
|
for (let key in aiConfig.config) {
|
||||||
delete providerConfig.apiKey
|
if (aiConfig.config[key].apiKey) {
|
||||||
|
aiConfig.config[key].apiKey = PASSWORD_REPLACEMENT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return aiConfig
|
return aiConfig
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,8 @@ function scimValidation() {
|
||||||
|
|
||||||
function aiValidation() {
|
function aiValidation() {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return Joi.array().items(
|
return Joi.object().pattern(
|
||||||
|
Joi.string(),
|
||||||
Joi.object({
|
Joi.object({
|
||||||
provider: Joi.string().required(),
|
provider: Joi.string().required(),
|
||||||
isDefault: Joi.boolean().required(),
|
isDefault: Joi.boolean().required(),
|
||||||
|
@ -77,9 +78,8 @@ function aiValidation() {
|
||||||
apiKey: Joi.string().required(),
|
apiKey: Joi.string().required(),
|
||||||
// TODO: should be enum
|
// TODO: should be enum
|
||||||
defaultModel: Joi.string().optional(),
|
defaultModel: Joi.string().optional(),
|
||||||
|
}).required()
|
||||||
})
|
)
|
||||||
).required()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildConfigSaveValidation() {
|
function buildConfigSaveValidation() {
|
||||||
|
|
Loading…
Reference in New Issue