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