Merge branch 'master' into dependabot/npm_and_yarn/vite-4.5.13

This commit is contained in:
Adria Navarro 2025-04-22 15:55:26 +02:00 committed by GitHub
commit 39148fae11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 997 additions and 289 deletions

View File

@ -10,7 +10,7 @@
}, },
"dependencies": { "dependencies": {
"bulma": "^0.9.3", "bulma": "^0.9.3",
"next": "14.2.25", "next": "14.2.26",
"node-fetch": "^3.2.10", "node-fetch": "^3.2.10",
"sass": "^1.52.3", "sass": "^1.52.3",
"react": "17.0.2", "react": "17.0.2",

View File

@ -46,10 +46,10 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@next/env@14.2.25": "@next/env@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.25.tgz#936d10b967e103e49a4bcea1e97292d5605278dd" resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.26.tgz#5d55f72d2edb7246607c78f61e7d3ff21516bc2e"
integrity sha512-JnzQ2cExDeG7FxJwqAksZ3aqVJrHjFwZQAEJ9gQZSoEhIow7SNoKZzju/AwQ+PLIR4NY8V0rhcVozx/2izDO0w== integrity sha512-vO//GJ/YBco+H7xdQhzJxF7ub3SUwft76jwaeOyVVQFHCi5DCnkP16WHB+JBylo4vOKPoZBlR94Z8xBxNBdNJA==
"@next/eslint-plugin-next@12.1.0": "@next/eslint-plugin-next@12.1.0":
version "12.1.0" version "12.1.0"
@ -58,50 +58,50 @@
dependencies: dependencies:
glob "7.1.7" glob "7.1.7"
"@next/swc-darwin-arm64@14.2.25": "@next/swc-darwin-arm64@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.25.tgz#7bcccfda0c0ff045c45fbe34c491b7368e373e3d" resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.26.tgz#84b31a22149b2c49f5c5b29cddd7acb3a84d7e1c"
integrity sha512-09clWInF1YRd6le00vt750s3m7SEYNehz9C4PUcSu3bAdCTpjIV4aTYQZ25Ehrr83VR1rZeqtKUPWSI7GfuKZQ== integrity sha512-zDJY8gsKEseGAxG+C2hTMT0w9Nk9N1Sk1qV7vXYz9MEiyRoF5ogQX2+vplyUMIfygnjn9/A04I6yrUTRTuRiyQ==
"@next/swc-darwin-x64@14.2.25": "@next/swc-darwin-x64@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.25.tgz#b489e209d7b405260b73f69a38186ed150fb7a08" resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.26.tgz#50a5eb37972d313951f76f36f1f0b7100d063ebd"
integrity sha512-V+iYM/QR+aYeJl3/FWWU/7Ix4b07ovsQ5IbkwgUK29pTHmq+5UxeDr7/dphvtXEq5pLB/PucfcBNh9KZ8vWbug== integrity sha512-U0adH5ryLfmTDkahLwG9sUQG2L0a9rYux8crQeC92rPhi3jGQEY47nByQHrVrt3prZigadwj/2HZ1LUUimuSbg==
"@next/swc-linux-arm64-gnu@14.2.25": "@next/swc-linux-arm64-gnu@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.25.tgz#ba064fabfdce0190d9859493d8232fffa84ef2e2" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.26.tgz#c4278c157623b05886e37ff17194811aca1c2d00"
integrity sha512-LFnV2899PJZAIEHQ4IMmZIgL0FBieh5keMnriMY1cK7ompR+JUd24xeTtKkcaw8QmxmEdhoE5Mu9dPSuDBgtTg== integrity sha512-SINMl1I7UhfHGM7SoRiw0AbwnLEMUnJ/3XXVmhyptzriHbWvPPbbm0OEVG24uUKhuS1t0nvN/DBvm5kz6ZIqpg==
"@next/swc-linux-arm64-musl@14.2.25": "@next/swc-linux-arm64-musl@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.25.tgz#bf0018267e4e0fbfa1524750321f8cae855144a3" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.26.tgz#5751132764b7a1f13a5a3fe447b03d564eb29705"
integrity sha512-QC5y5PPTmtqFExcKWKYgUNkHeHE/z3lUsu83di488nyP0ZzQ3Yse2G6TCxz6nNsQwgAx1BehAJTZez+UQxzLfw== integrity sha512-s6JaezoyJK2DxrwHWxLWtJKlqKqTdi/zaYigDXUJ/gmx/72CrzdVZfMvUc6VqnZ7YEvRijvYo+0o4Z9DencduA==
"@next/swc-linux-x64-gnu@14.2.25": "@next/swc-linux-x64-gnu@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.25.tgz#64f5a6016a7148297ee80542e0fd788418a32472" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.26.tgz#74312cac45704762faa73e0880be6549027303af"
integrity sha512-y6/ML4b9eQ2D/56wqatTJN5/JR8/xdObU2Fb1RBidnrr450HLCKr6IJZbPqbv7NXmje61UyxjF5kvSajvjye5w== integrity sha512-FEXeUQi8/pLr/XI0hKbe0tgbLmHFRhgXOUiPScz2hk0hSmbGiU8aUqVslj/6C6KA38RzXnWoJXo4FMo6aBxjzg==
"@next/swc-linux-x64-musl@14.2.25": "@next/swc-linux-x64-musl@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.25.tgz#58dc636d7c55828478159546f7b95ab1e902301c" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.26.tgz#5d96464d71d2000ec704e650a1a86bb9d73f760d"
integrity sha512-sPX0TSXHGUOZFvv96GoBXpB3w4emMqKeMgemrSxI7A6l55VBJp/RKYLwZIB9JxSqYPApqiREaIIap+wWq0RU8w== integrity sha512-BUsomaO4d2DuXhXhgQCVt2jjX4B4/Thts8nDoIruEJkhE5ifeQFtvW5c9JkdOtYvE5p2G0hcwQ0UbRaQmQwaVg==
"@next/swc-win32-arm64-msvc@14.2.25": "@next/swc-win32-arm64-msvc@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.25.tgz#93562d447c799bded1e89c1a62d5195a2a8c6c0d" resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.26.tgz#859472b532b11499b8f5c2237f54401456286913"
integrity sha512-ReO9S5hkA1DU2cFCsGoOEp7WJkhFzNbU/3VUF6XxNGUCQChyug6hZdYL/istQgfT/GWE6PNIg9cm784OI4ddxQ== integrity sha512-5auwsMVzT7wbB2CZXQxDctpWbdEnEW/e66DyXO1DcgHxIyhP06awu+rHKshZE+lPLIGiwtjo7bsyeuubewwxMw==
"@next/swc-win32-ia32-msvc@14.2.25": "@next/swc-win32-ia32-msvc@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.25.tgz#ad85a33466be1f41d083211ea21adc0d2c6e6554" resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.26.tgz#e52e9bd0c43b7a469b03eda6d7a07c3d0c28f549"
integrity sha512-DZ/gc0o9neuCDyD5IumyTGHVun2dCox5TfPQI/BJTYwpSNYM3CZDI4i6TOdjeq1JMo+Ug4kPSMuZdwsycwFbAw== integrity sha512-GQWg/Vbz9zUGi9X80lOeGsz1rMH/MtFO/XqigDznhhhTfDlDoynCM6982mPCbSlxJ/aveZcKtTlwfAjwhyxDpg==
"@next/swc-win32-x64-msvc@14.2.25": "@next/swc-win32-x64-msvc@14.2.26":
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.25.tgz#3969c66609e683ec63a6a9f320a855f7be686a08" resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.26.tgz#6f42a3ae16ae15c5c5e36efa9b7e291c86ab1275"
integrity sha512-KSznmS6eFjQ9RJ1nEc66kJvtGIL1iZMYmGEXsZPh2YtnLtqrgdVvKXJY2ScjjoFnG6nGLyPFR0UiEvDwVah4Tw== integrity sha512-2rdB3T1/Gp7bv1eQTTm9d1Y1sv9UuJ2LAwOE0Pe2prHKe32UNscj7YS13fRB37d0GAiGNR+Y7ZcW8YjDI8Ns0w==
"@nodelib/fs.scandir@2.1.5": "@nodelib/fs.scandir@2.1.5":
version "2.1.5" version "2.1.5"
@ -1253,12 +1253,12 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
next@14.2.25: next@14.2.26:
version "14.2.25" version "14.2.26"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.25.tgz#0657551fde6a97f697cf9870e9ccbdaa465c6008" resolved "https://registry.yarnpkg.com/next/-/next-14.2.26.tgz#b918b3fc5c55e1a67aada1347907675713687721"
integrity sha512-N5M7xMc4wSb4IkPvEV5X2BRRXUmhVHNyaXwEM86+voXthSZz8ZiRyQW4p9mwAoAPIm6OzuVZtn7idgEJeAJN3Q== integrity sha512-b81XSLihMwCfwiUVRRja3LphLo4uBBMZEzBBWMaISbKTwOmq3wPknIETy/8000tr7Gq4WmbuFYPS7jOYIf+ZJw==
dependencies: dependencies:
"@next/env" "14.2.25" "@next/env" "14.2.26"
"@swc/helpers" "0.5.5" "@swc/helpers" "0.5.5"
busboy "1.6.0" busboy "1.6.0"
caniuse-lite "^1.0.30001579" caniuse-lite "^1.0.30001579"
@ -1266,15 +1266,15 @@ next@14.2.25:
postcss "8.4.31" postcss "8.4.31"
styled-jsx "5.1.1" styled-jsx "5.1.1"
optionalDependencies: optionalDependencies:
"@next/swc-darwin-arm64" "14.2.25" "@next/swc-darwin-arm64" "14.2.26"
"@next/swc-darwin-x64" "14.2.25" "@next/swc-darwin-x64" "14.2.26"
"@next/swc-linux-arm64-gnu" "14.2.25" "@next/swc-linux-arm64-gnu" "14.2.26"
"@next/swc-linux-arm64-musl" "14.2.25" "@next/swc-linux-arm64-musl" "14.2.26"
"@next/swc-linux-x64-gnu" "14.2.25" "@next/swc-linux-x64-gnu" "14.2.26"
"@next/swc-linux-x64-musl" "14.2.25" "@next/swc-linux-x64-musl" "14.2.26"
"@next/swc-win32-arm64-msvc" "14.2.25" "@next/swc-win32-arm64-msvc" "14.2.26"
"@next/swc-win32-ia32-msvc" "14.2.25" "@next/swc-win32-ia32-msvc" "14.2.26"
"@next/swc-win32-x64-msvc" "14.2.25" "@next/swc-win32-x64-msvc" "14.2.26"
node-domexception@^1.0.0: node-domexception@^1.0.0:
version "1.0.0" version "1.0.0"

View File

@ -1,6 +1,6 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "3.9.0", "version": "3.9.1",
"npmClient": "yarn", "npmClient": "yarn",
"concurrency": 20, "concurrency": 20,
"command": { "command": {

View File

@ -44,7 +44,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bull": "4.10.1", "bull": "4.10.1",
"correlation-id": "4.0.0", "correlation-id": "4.0.0",
"dd-trace": "5.43.0", "dd-trace": "5.47.0",
"dotenv": "16.0.1", "dotenv": "16.0.1",
"google-auth-library": "^8.0.1", "google-auth-library": "^8.0.1",
"google-spreadsheet": "npm:@budibase/google-spreadsheet@4.1.5", "google-spreadsheet": "npm:@budibase/google-spreadsheet@4.1.5",

View File

@ -104,6 +104,7 @@
on:focus on:focus
on:input on:input
on:keyup on:keyup
on:keydown
on:blur={onBlur} on:blur={onBlur}
on:focus={onFocus} on:focus={onFocus}
on:input={onInput} on:input={onInput}

View File

@ -41,6 +41,7 @@
on:blur on:blur
on:focus on:focus
on:keyup on:keyup
on:keydown
> >
<slot /> <slot />
</TextField> </TextField>

View File

@ -0,0 +1,24 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.360872 11.5905L4.90819 16.139C5.10754 16.3384 5.43058 16.3384 5.62993 16.139L7.61005 14.1583C7.78825 13.9801 7.87247 13.7282 7.83748 13.4783L7.30573 9.71929C7.26667 9.44582 7.05186 9.23095 6.77886 9.19228L3.02083 8.6604C2.77143 8.62499 2.5196 8.70923 2.34099 8.88788L0.360872 10.8685C0.161518 11.0679 0.161518 11.3911 0.360872 11.5905Z" fill="url(#paint0_linear_1216_26668)"/>
<path d="M11.0826 16.139L15.6299 11.5905C15.8292 11.3911 15.8292 11.068 15.6299 10.8686L13.6498 8.88794C13.4716 8.70969 13.2197 8.62545 12.9699 8.66045L9.21188 9.19234C8.93847 9.23141 8.72366 9.44628 8.68501 9.71934L8.15326 13.4784C8.11787 13.7278 8.20208 13.9797 8.38069 14.1584L10.3608 16.139C10.5602 16.3384 10.8832 16.3384 11.0826 16.139Z" fill="url(#paint1_linear_1216_26668)"/>
<path d="M15.6391 5.40954L11.0918 0.861023C10.8925 0.661616 10.5694 0.661614 10.3701 0.861021L8.38995 2.84166C8.21175 3.01991 8.12753 3.27181 8.16252 3.52168L8.69427 7.28071C8.73333 7.55418 8.94814 7.76905 9.22114 7.80772L12.9792 8.3396C13.2286 8.37501 13.4804 8.29077 13.659 8.11212L15.6391 6.13148C15.8385 5.93207 15.8385 5.60895 15.6391 5.40954Z" fill="url(#paint2_linear_1216_26668)"/>
<path d="M4.91745 0.860967L0.370132 5.40948C0.170778 5.60889 0.170776 5.93201 0.370131 6.13142L2.35025 8.11206C2.52845 8.29031 2.78028 8.37455 3.03009 8.33955L6.78812 7.80766C7.06152 7.76859 7.27634 7.55372 7.31499 7.28066L7.84674 3.52163C7.88213 3.27217 7.79792 3.02026 7.61931 2.84161L5.63919 0.860967C5.43984 0.66156 5.1168 0.66156 4.91745 0.860967Z" fill="url(#paint3_linear_1216_26668)"/>
<defs>
<linearGradient id="paint0_linear_1216_26668" x1="4.94623" y1="15.6582" x2="7.43064" y2="9.91942" gradientUnits="userSpaceOnUse">
<stop stop-color="#6E56FF"/>
<stop offset="1" stop-color="#9F8FFF"/>
</linearGradient>
<linearGradient id="paint1_linear_1216_26668" x1="15.1492" y1="11.5525" x2="9.411" y2="9.06961" gradientUnits="userSpaceOnUse">
<stop stop-color="#6E56FF"/>
<stop offset="1" stop-color="#9F8FFF"/>
</linearGradient>
<linearGradient id="paint2_linear_1216_26668" x1="11.0538" y1="1.34184" x2="8.56936" y2="7.08058" gradientUnits="userSpaceOnUse">
<stop stop-color="#6E56FF"/>
<stop offset="1" stop-color="#9F8FFF"/>
</linearGradient>
<linearGradient id="paint3_linear_1216_26668" x1="0.850819" y1="5.44754" x2="6.589" y2="7.93039" gradientUnits="userSpaceOnUse">
<stop stop-color="#6E56FF"/>
<stop offset="1" stop-color="#9F8FFF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -3,6 +3,8 @@ export const Events = {
COMPONENT_UPDATED: "component:updated", COMPONENT_UPDATED: "component:updated",
APP_VIEW_PUBLISHED: "app:view_published", APP_VIEW_PUBLISHED: "app:view_published",
BLOCK_EJECTED: "block:ejected", BLOCK_EJECTED: "block:ejected",
AI_JS_ACCEPTED: "ai_js:accepted",
AI_JS_REJECTED: "ai_js:rejected",
} }
export const EventSource = { export const EventSource = {

View File

@ -1,4 +1,4 @@
import { FieldType } from "@budibase/types" import { FieldType, FormulaType } from "@budibase/types"
import { FIELDS } from "@/constants/backend" import { FIELDS } from "@/constants/backend"
import { tables } from "@/stores/builder" import { tables } from "@/stores/builder"
import { get as svelteGet } from "svelte/store" import { get as svelteGet } from "svelte/store"
@ -8,7 +8,6 @@ import { makeReadableKeyPropSafe } from "@/dataBinding"
const MAX_DEPTH = 1 const MAX_DEPTH = 1
const TYPES_TO_SKIP = [ const TYPES_TO_SKIP = [
FieldType.FORMULA,
FieldType.AI, FieldType.AI,
FieldType.LONGFORM, FieldType.LONGFORM,
FieldType.SIGNATURE_SINGLE, FieldType.SIGNATURE_SINGLE,
@ -17,6 +16,18 @@ const TYPES_TO_SKIP = [
FieldType.INTERNAL, FieldType.INTERNAL,
] ]
const shouldSkipFieldSchema = fieldSchema => {
// Skip some types always
if (TYPES_TO_SKIP.includes(fieldSchema.type)) {
return true
}
// Skip dynamic formula fields
return (
fieldSchema.type === FieldType.FORMULA &&
fieldSchema.formulaType === FormulaType.DYNAMIC
)
}
export function getBindings({ export function getBindings({
table, table,
path = null, path = null,
@ -32,7 +43,7 @@ export function getBindings({
// skip relationships after a certain depth and types which // skip relationships after a certain depth and types which
// can't bind to // can't bind to
if ( if (
TYPES_TO_SKIP.includes(schema.type) || shouldSkipFieldSchema(schema) ||
(isRelationship && depth >= MAX_DEPTH) (isRelationship && depth >= MAX_DEPTH)
) { ) {
continue continue

View File

@ -0,0 +1,153 @@
<script lang="ts">
import { ActionButton, notifications } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { API } from "@/api"
import type { EnrichedBinding } from "@budibase/types"
import analytics, { Events } from "@/analytics"
import AiInput from "../ai/AIInput.svelte"
export let bindings: EnrichedBinding[] = []
export let value: string | null = ""
export let expandedOnly: boolean = false
export let parentWidth: number | null = null
const dispatch = createEventDispatcher<{
update: { code: string }
accept: void
reject: { code: string | null }
}>()
let suggestedCode: string | null = null
let previousContents: string | null = null
let expanded = false
let promptText = ""
const thresholdExpansionWidth = 350
$: expanded =
expandedOnly ||
(parentWidth !== null && parentWidth > thresholdExpansionWidth)
? true
: expanded
$: containerWidth = expanded ? calculateExpandedWidth() : "auto"
async function generateJs(prompt: string) {
promptText = ""
if (!prompt.trim()) return
previousContents = value
promptText = prompt
try {
const resp = await API.generateJs({ prompt, bindings })
const code = resp.code
if (code === "") {
throw new Error("We didn't understand your prompt. Please rephrase it.")
}
suggestedCode = code
dispatch("update", { code })
} catch (e) {
console.error(e)
notifications.error(
e instanceof Error
? `Unable to generate code: ${e.message}`
: "Unable to generate code. Please try again later."
)
}
}
function acceptSuggestion() {
analytics.captureEvent(Events.AI_JS_ACCEPTED, {
code: suggestedCode,
prompt: promptText,
})
dispatch("accept")
reset()
}
function rejectSuggestion() {
analytics.captureEvent(Events.AI_JS_REJECTED, {
code: suggestedCode,
prompt: promptText,
})
dispatch("reject", { code: previousContents })
reset()
}
function reset() {
suggestedCode = null
previousContents = null
}
function calculateExpandedWidth() {
return parentWidth
? `${Math.min(Math.max(parentWidth * 0.8, 300), 600)}px`
: "300px"
}
</script>
<div class="ai-gen-container" style="--container-width: {containerWidth}">
{#if suggestedCode !== null}
<div class="floating-actions">
<ActionButton size="S" icon="CheckmarkCircle" on:click={acceptSuggestion}>
Accept
</ActionButton>
<ActionButton size="S" icon="Delete" on:click={rejectSuggestion}>
Reject
</ActionButton>
</div>
{/if}
<AiInput
placeholder="Generate with AI"
onSubmit={generateJs}
bind:expanded
on:collapse={rejectSuggestion}
readonly={!!suggestedCode}
{expandedOnly}
/>
</div>
<style>
.ai-gen-container {
height: 40px;
--container-width: auto;
position: absolute;
right: 10px;
bottom: 10px;
width: var(--container-width);
display: flex;
overflow: visible;
}
@keyframes border-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.floating-actions {
position: absolute;
display: flex;
gap: var(--spacing-s);
bottom: calc(100% + 5px);
left: 5px;
z-index: 2;
animation: fade-in 0.2s ease-out forwards;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -6,13 +6,7 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { import { Label } from "@budibase/bbui"
Button,
Label,
notifications,
Popover,
TextArea,
} from "@budibase/bbui"
import { onMount, createEventDispatcher, onDestroy } from "svelte" import { onMount, createEventDispatcher, onDestroy } from "svelte"
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates" import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
@ -69,8 +63,7 @@
import { validateHbsTemplate } from "./validator/hbs" import { validateHbsTemplate } from "./validator/hbs"
import { validateJsTemplate } from "./validator/js" import { validateJsTemplate } from "./validator/js"
import { featureFlag } from "@/helpers" import { featureFlag } from "@/helpers"
import { API } from "@/api" import AIGen from "./AIGen.svelte"
import Spinner from "../Spinner.svelte"
export let label: string | undefined = undefined export let label: string | undefined = undefined
export let completions: BindingCompletion[] = [] export let completions: BindingCompletion[] = []
@ -94,15 +87,19 @@
let mounted = false let mounted = false
let isEditorInitialised = false let isEditorInitialised = false
let queuedRefresh = false let queuedRefresh = false
let editorWidth: number | null = null
// Theming! // Theming!
let currentTheme = $themeStore?.theme let currentTheme = $themeStore?.theme
let isDark = !currentTheme.includes("light") let isDark = !currentTheme.includes("light")
let themeConfig = new Compartment() let themeConfig = new Compartment()
let popoverAnchor: HTMLElement const updateEditorWidth = () => {
let popover: Popover if (editorEle) {
let promptInput: TextArea editorWidth = editorEle.offsetWidth
}
}
$: aiGenEnabled = $: aiGenEnabled =
featureFlag.isEnabled(FeatureFlag.AI_JS_GENERATION) && featureFlag.isEnabled(FeatureFlag.AI_JS_GENERATION) &&
mode.name === "javascript" && mode.name === "javascript" &&
@ -161,68 +158,6 @@
} }
} }
$: promptLoading = false
let popoverWidth = 300
let suggestedCode: string | null = null
let previousContents: string | null = null
const generateJs = async (prompt: string) => {
previousContents = editor.state.doc.toString()
promptLoading = true
popoverWidth = 30
let code = ""
try {
const resp = await API.generateJs({ prompt, bindings })
code = resp.code
if (code === "") {
throw new Error(
"we didn't understand your prompt, please phrase your request in another way"
)
}
} catch (e) {
console.error(e)
if (e instanceof Error) {
notifications.error(`Unable to generate code: ${e.message}`)
} else {
notifications.error("Unable to generate code, please try again later.")
}
code = previousContents
promptLoading = false
resetPopover()
return
}
value = code
editor.dispatch({
changes: { from: 0, to: editor.state.doc.length, insert: code },
})
suggestedCode = code
popoverWidth = 100
promptLoading = false
}
const acceptSuggestion = () => {
suggestedCode = null
previousContents = null
resetPopover()
dispatch("change", editor.state.doc.toString())
dispatch("blur", editor.state.doc.toString())
}
const rejectSuggestion = () => {
suggestedCode = null
value = previousContents || ""
editor.dispatch({
changes: { from: 0, to: editor.state.doc.length, insert: value },
})
previousContents = null
resetPopover()
}
const resetPopover = () => {
popover.hide()
popoverWidth = 300
}
// Export a function to expose caret position // Export a function to expose caret position
export const getCaretPosition = () => { export const getCaretPosition = () => {
const selection_range = editor.state.selection.ranges[0] const selection_range = editor.state.selection.ranges[0]
@ -487,12 +422,31 @@
}) })
} }
onMount(async () => { // Handle AI generation code updates
const handleAICodeUpdate = (event: CustomEvent<{ code: string }>) => {
const { code } = event.detail
value = code
editor.dispatch({
changes: { from: 0, to: editor.state.doc.length, insert: code },
})
}
onMount(() => {
mounted = true mounted = true
// Capture scrolling // Capture scrolling
editorEle.addEventListener("wheel", e => { editorEle.addEventListener("wheel", e => {
e.stopPropagation() e.stopPropagation()
}) })
// Need to get the width of the drawer to pass to the prompt component
updateEditorWidth()
const resizeObserver = new ResizeObserver(() => {
updateEditorWidth()
})
resizeObserver.observe(editorEle)
return () => {
resizeObserver.disconnect()
}
}) })
onDestroy(() => { onDestroy(() => {
@ -513,52 +467,23 @@
</div> </div>
{#if aiGenEnabled} {#if aiGenEnabled}
<button <AIGen
bind:this={popoverAnchor} {bindings}
class="ai-gen" {value}
on:click={() => { parentWidth={editorWidth}
popover.show() on:update={handleAICodeUpdate}
setTimeout(() => { on:accept={() => {
promptInput.focus() dispatch("change", editor.state.doc.toString())
}, 100) dispatch("blur", editor.state.doc.toString())
}} }}
> on:reject={event => {
Generate with AI ✨ const { code } = event.detail
</button> value = code || ""
editor.dispatch({
<Popover changes: { from: 0, to: editor.state.doc.length, insert: code || "" },
bind:this={popover} })
minWidth={popoverWidth}
anchor={popoverAnchor}
on:close={() => {
if (suggestedCode) {
acceptSuggestion()
}
}} }}
align="left-outside" />
>
{#if promptLoading}
<div class="prompt-spinner">
<Spinner size="20" color="white" />
</div>
{:else if suggestedCode !== null}
<Button on:click={acceptSuggestion}>Accept</Button>
<Button on:click={rejectSuggestion}>Reject</Button>
{:else}
<TextArea
bind:this={promptInput}
placeholder="Type your prompt then press enter..."
on:keypress={event => {
if (event.getModifierState("Shift")) {
return
}
if (event.key === "Enter") {
generateJs(promptInput.contents())
}
}}
/>
{/if}
</Popover>
{/if} {/if}
<style> <style>
@ -766,34 +691,4 @@
text-overflow: ellipsis !important; text-overflow: ellipsis !important;
white-space: nowrap !important; white-space: nowrap !important;
} }
.ai-gen {
right: 1px;
bottom: 1px;
position: absolute;
justify-content: center;
align-items: center;
display: flex;
flex-direction: row;
box-sizing: border-box;
padding: var(--spacing-s);
border-left: 1px solid var(--spectrum-alias-border-color);
border-top: 1px solid var(--spectrum-alias-border-color);
border-top-left-radius: var(--spectrum-alias-border-radius-regular);
color: var(--spectrum-global-color-blue-700);
background-color: var(--spectrum-global-color-gray-75);
transition: background-color
var(--spectrum-global-animation-duration-100, 130ms),
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
border-color var(--spectrum-global-animation-duration-100, 130ms);
height: calc(var(--spectrum-alias-item-height-m) - 2px);
}
.ai-gen:hover {
cursor: pointer;
color: var(--spectrum-alias-text-color-hover);
background-color: var(--spectrum-global-color-gray-50);
border-color: var(--spectrum-alias-border-color-hover);
}
.prompt-spinner {
padding: var(--spacing-m);
}
</style> </style>

View File

@ -0,0 +1,327 @@
<script lang="ts">
import { Icon, Button, Modal, ModalContent, Body, Link } from "@budibase/bbui"
import { auth, admin, licensing } from "@/stores/portal"
import { createEventDispatcher } from "svelte"
import BBAI from "assets/bb-ai.svg"
export let onSubmit: (_prompt: string) => Promise<void>
export let placeholder: string = ""
export let expanded: boolean = false
export let expandedOnly: boolean = false
export let readonly: boolean = false
export let value: string = ""
export const submit = onPromptSubmit
$: expanded = expandedOnly || expanded
const dispatch = createEventDispatcher()
let promptInput: HTMLInputElement
let buttonElement: HTMLButtonElement
let promptLoading = false
let switchOnAIModal: Modal
let addCreditsModal: Modal
$: accountPortalAccess = $auth?.user?.accountPortalAccess
$: accountPortal = $admin.accountPortalUrl
$: aiEnabled = $auth?.user?.llm
$: creditsExceeded = $licensing.aiCreditsExceeded
$: disabled = !aiEnabled || creditsExceeded || readonly || promptLoading
$: animateBorder = !disabled && expanded
function collapse() {
dispatch("collapse")
expanded = expandedOnly
value = ""
animateBorder = false
}
function toggleExpand() {
if (!expanded) {
expanded = true
animateBorder = true
setTimeout(() => {
promptInput?.focus()
}, 250)
} else {
collapse()
}
}
function handleKeyPress(event: KeyboardEvent) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault()
onPromptSubmit()
} else if (event.key === "Escape") {
collapse()
} else {
event.stopPropagation()
}
}
async function onPromptSubmit() {
if (readonly) {
return
}
promptLoading = true
try {
await onSubmit(value)
} finally {
promptLoading = false
}
}
</script>
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<button
bind:this={buttonElement}
class="spectrum-ActionButton fade"
class:expanded
class:animate-border={animateBorder}
on:click={!expanded ? toggleExpand : undefined}
>
<div class="button-content-wrapper">
<img
src={BBAI}
alt="AI"
class="ai-icon"
class:disabled={expanded && disabled}
on:click={e => {
e.stopPropagation()
toggleExpand()
}}
/>
{#if expanded}
<input
type="text"
bind:this={promptInput}
bind:value
class="prompt-input"
{placeholder}
on:keydown={handleKeyPress}
{disabled}
/>
{:else}
<span class="spectrum-ActionButton-label ai-gen-text">
{placeholder}
</span>
{/if}
</div>
{#if expanded}
<div class="action-buttons">
{#if !aiEnabled}
<Button cta size="S" on:click={() => switchOnAIModal.show()}>
Switch on AI
</Button>
<Modal bind:this={switchOnAIModal}>
<ModalContent title="Switch on AI" showConfirmButton={false}>
<div class="enable-ai">
<p>To enable BB AI:</p>
<ul>
<li>
Add your Budibase license key:
<Link href={accountPortal}>Budibase account portal</Link>
</li>
<li>
Go to the portal settings page, click AI and switch on BB AI
</li>
</ul>
</div>
</ModalContent>
</Modal>
{:else if creditsExceeded}
<Button cta size="S" on:click={() => addCreditsModal.show()}>
Add AI credits
</Button>
<Modal bind:this={addCreditsModal}>
<ModalContent title="Add AI credits" showConfirmButton={false}>
<Body size="S">
{#if accountPortalAccess}
<Link href={"https://budibase.com/contact/"}>Contact sales</Link
> to unlock additional BB AI credits
{:else}
Contact your account holder to unlock additional BB AI credits
{/if}
</Body>
</ModalContent>
</Modal>
{:else}
<Icon
color={promptLoading
? "#6E56FF"
: "var(--spectrum-global-color-gray-600)"}
size="S"
hoverable={!readonly}
hoverColor="#6E56FF"
name={promptLoading ? "StopCircle" : "PlayCircle"}
on:click={onPromptSubmit}
/>
{/if}
</div>
{/if}
</button>
<style>
.spectrum-ActionButton {
--offset: 1px;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: var(--spacing-s);
border: 1px solid var(--spectrum-alias-border-color);
border-radius: 30px;
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
width: 100%;
height: 40px;
overflow: hidden;
cursor: pointer;
background-color: var(--spectrum-global-color-gray-75);
}
.spectrum-ActionButton::before {
content: "";
position: absolute;
top: -1px;
left: -1px;
width: calc(100% + 2px);
height: calc(100% + 2px);
border-radius: inherit;
background: linear-gradient(
125deg,
transparent -10%,
#6e56ff 2%,
#9f8fff 15%,
#9f8fff 25%,
transparent 35%,
transparent 110%
);
pointer-events: none;
z-index: 0;
}
.spectrum-ActionButton:not(.animate-border)::before {
content: none;
}
.animate-border::before {
animation: border-fade-in 1s cubic-bezier(0.17, 0.67, 0.83, 0.67);
animation-fill-mode: forwards;
}
@keyframes border-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.spectrum-ActionButton::after {
content: "";
background: inherit;
position: absolute;
top: 50%;
left: 50%;
inset: var(--offset);
height: calc(100% - 2 * var(--offset));
width: calc(100% - 2 * var(--offset));
border-radius: inherit;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.spectrum-ActionButton:hover {
cursor: pointer;
background-color: var(--spectrum-global-color-gray-75);
}
.spectrum-ActionButton.expanded {
border-radius: 30px;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease-out;
}
.fade {
transition: all 2s ease-in;
}
.ai-icon {
width: 18px;
height: 18px;
margin-right: 8px;
flex-shrink: 0;
cursor: var(--ai-icon-cursor, pointer);
}
.ai-gen-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease-out;
margin-right: var(--spacing-xs);
}
.prompt-input {
font-size: 14px;
flex: 1;
border: none;
background: transparent;
outline: none;
font-family: var(--font-sans);
color: var(--spectrum-alias-text-color);
min-width: 0;
resize: none;
overflow: hidden;
}
.prompt-input::placeholder {
color: var(--spectrum-global-color-gray-600);
font-family: var(--font-sans);
}
.action-buttons {
display: flex;
gap: var(--spacing-s);
z-index: 4;
flex-shrink: 0;
margin-right: var(--spacing-s);
}
.button-content-wrapper {
position: relative;
z-index: 1;
display: flex;
align-items: center;
overflow: hidden;
flex-grow: 1;
min-width: 0;
margin-right: var(--spacing-s);
}
.prompt-input:disabled {
color: var(--spectrum-global-color-gray-500);
cursor: not-allowed;
}
.ai-icon.disabled {
filter: grayscale(1) brightness(1.5);
opacity: 0.5;
}
</style>

View File

@ -1317,6 +1317,25 @@ const shouldReplaceBinding = (currentValue, from, convertTo, binding) => {
return !invalids.find(invalid => noSpaces?.includes(invalid)) return !invalids.find(invalid => noSpaces?.includes(invalid))
} }
// If converting readable to runtime we need to ensure we don't replace words
// which are substrings of other words - e.g. a binding of `a` would turn
// `hah` into `h[a]h` which is obviously wrong. To avoid this we can remove all
// expanded versions of the binding to be replaced.
const excludeReadableExtensions = (string, binding) => {
// Escape any special chars in the binding so we can treat it as a literal
// string match in the regexes below
const escaped = binding.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
// Regex to find prefixed bindings (e.g. exclude xfoo for foo)
const regex1 = new RegExp(`[a-zA-Z0-9-_]+${escaped}[a-zA-Z0-9-_]*`, "g")
// Regex to find prefixed bindings (e.g. exclude foox for foo)
const regex2 = new RegExp(`[a-zA-Z0-9-_]*${escaped}[a-zA-Z0-9-_]+`, "g")
const matches = [...string.matchAll(regex1), ...string.matchAll(regex2)]
for (const match of matches) {
string = string.replace(match[0], new Array(match[0].length + 1).join("*"))
}
return string
}
/** /**
* Utility function which replaces a string between given indices. * Utility function which replaces a string between given indices.
*/ */
@ -1361,6 +1380,11 @@ const bindingReplacement = (
// in the search, working from longest to shortest so always use best match first // in the search, working from longest to shortest so always use best match first
let searchString = newBoundValue let searchString = newBoundValue
for (let from of convertFromProps) { for (let from of convertFromProps) {
// If converting readable > runtime, blank out all extensions of this
// string to avoid partial matches
if (convertTo === "runtimeBinding") {
searchString = excludeReadableExtensions(searchString, from)
}
const binding = bindableProperties.find(el => el[convertFrom] === from) const binding = bindableProperties.find(el => el[convertFrom] === from)
if ( if (
isJS || isJS ||

View File

@ -72,6 +72,27 @@ describe("Builder dataBinding", () => {
runtimeBinding: "count", runtimeBinding: "count",
type: "context", type: "context",
}, },
{
category: "Bindings",
icon: "Brackets",
readableBinding: "location",
runtimeBinding: "[location]",
type: "context",
},
{
category: "Bindings",
icon: "Brackets",
readableBinding: "foo.[bar]",
runtimeBinding: "[foo].[qwe]",
type: "context",
},
{
category: "Bindings",
icon: "Brackets",
readableBinding: "foo.baz",
runtimeBinding: "[foo].[baz]",
type: "context",
},
] ]
it("should convert a readable binding to a runtime one", () => { it("should convert a readable binding to a runtime one", () => {
const textWithBindings = `Hello {{ Current User.firstName }}! The count is {{ Binding.count }}.` const textWithBindings = `Hello {{ Current User.firstName }}! The count is {{ Binding.count }}.`
@ -83,6 +104,28 @@ describe("Builder dataBinding", () => {
) )
).toEqual(`Hello {{ [user].[firstName] }}! The count is {{ count }}.`) ).toEqual(`Hello {{ [user].[firstName] }}! The count is {{ count }}.`)
}) })
it("should not convert a partial match", () => {
const textWithBindings = `location {{ _location Zlocation location locationZ _location_ }}`
expect(
readableToRuntimeBinding(
bindableProperties,
textWithBindings,
"runtimeBinding"
)
).toEqual(
`location {{ _location Zlocation [location] locationZ _location_ }}`
)
})
it("should handle special characters in the readable binding", () => {
const textWithBindings = `{{ foo.baz }}`
expect(
readableToRuntimeBinding(
bindableProperties,
textWithBindings,
"runtimeBinding"
)
).toEqual(`{{ [foo].[baz] }}`)
})
}) })
describe("updateReferencesInObject", () => { describe("updateReferencesInObject", () => {

View File

@ -0,0 +1,65 @@
<script lang="ts">
import { API } from "@/api"
import AiInput from "@/components/common/ai/AIInput.svelte"
import { auth, licensing } from "@/stores/portal"
import { ActionButton, notifications } from "@budibase/bbui"
let promptText = ""
$: isEnabled = $auth?.user?.llm && !$licensing.aiCreditsExceeded
async function submitPrompt(message: string) {
await API.generateTables(message)
notifications.success("Tables created successfully!")
}
const examplePrompts = [
"Create me a table for managing IT tickets",
"Create a table called students with name and address fields",
]
</script>
<div class="ai-generation">
<div class="ai-generation-prompt">
<AiInput
bind:value={promptText}
placeholder="Generate data using AI..."
onSubmit={submitPrompt}
expandedOnly
/>
</div>
<div class="ai-generation-examples">
{#if isEnabled}
{#each examplePrompts as prompt}
<ActionButton on:click={() => (promptText = prompt)}
>{prompt}</ActionButton
>
{/each}
{/if}
</div>
</div>
<style>
.ai-generation {
margin-bottom: 24px;
display: flex;
flex-direction: column;
gap: 10px;
}
.ai-generation-examples {
display: grid;
gap: 10px;
}
@media (min-width: 833px) {
.ai-generation-examples {
grid-auto-flow: column;
}
}
.ai-generation :global(.spectrum-Textfield-input),
.ai-generation :global(.spectrum-ActionButton) {
background: #1d1d1d;
border-radius: 20px;
}
</style>

View File

@ -15,6 +15,9 @@
import IntegrationIcon from "@/components/backend/DatasourceNavigator/IntegrationIcon.svelte" import IntegrationIcon from "@/components/backend/DatasourceNavigator/IntegrationIcon.svelte"
import CreationPage from "@/components/common/CreationPage.svelte" import CreationPage from "@/components/common/CreationPage.svelte"
import ICONS from "@/components/backend/DatasourceNavigator/icons/index.js" import ICONS from "@/components/backend/DatasourceNavigator/icons/index.js"
import AiTableGeneration from "./_components/AITableGeneration.svelte"
import { featureFlag } from "@/helpers"
import { FeatureFlag } from "@budibase/types"
let internalTableModal let internalTableModal
let externalDatasourceModal let externalDatasourceModal
@ -24,6 +27,10 @@
$: disabled = sampleDataLoading || externalDatasourceLoading $: disabled = sampleDataLoading || externalDatasourceLoading
$: aiTableGenerationEnabled = featureFlag.isEnabled(
FeatureFlag.AI_TABLE_GENERATION
)
const createSampleData = async () => { const createSampleData = async () => {
sampleDataLoading = true sampleDataLoading = true
@ -58,7 +65,12 @@
</AbsTooltip> </AbsTooltip>
</div> </div>
<div class="options"> <div class="options bb-options">
{#if aiTableGenerationEnabled}
<div class="ai-generation">
<AiTableGeneration />
</div>
{/if}
<DatasourceOption <DatasourceOption
on:click={internalTableModal.show} on:click={internalTableModal.show}
title="Create new table" title="Create new table"
@ -111,7 +123,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 12px; margin-top: 12px;
margin-bottom: 36px; margin-bottom: 24px;
gap: 8px; gap: 8px;
} }
.subHeading :global(p) { .subHeading :global(p) {
@ -127,4 +139,10 @@
margin-bottom: 48px; margin-bottom: 48px;
max-width: 1050px; max-width: 1050px;
} }
.bb-options {
max-width: calc(3 * 235px + 2 * 24px); /* 3 columns + 2 gaps */
}
.options .ai-generation {
grid-column: 1 / -1;
}
</style> </style>

View File

@ -32,6 +32,7 @@
const oneDayInSeconds = 86400 const oneDayInSeconds = 86400
const EXCLUDE_QUOTAS = { const EXCLUDE_QUOTAS = {
["Day Passes"]: () => true,
Queries: () => true, Queries: () => true,
Users: license => { Users: license => {
return license.plan.model !== PlanModel.PER_USER return license.plan.model !== PlanModel.PER_USER

View File

@ -1,4 +1,4 @@
<script> <script lang="ts">
import { Body, Label, Icon } from "@budibase/bbui" import { Body, Label, Icon } from "@budibase/bbui"
import BudibaseLogo from "./logos/Budibase.svelte" import BudibaseLogo from "./logos/Budibase.svelte"
import OpenAILogo from "./logos/OpenAI.svelte" import OpenAILogo from "./logos/OpenAI.svelte"
@ -6,7 +6,7 @@
import TogetherAILogo from "./logos/TogetherAI.svelte" import TogetherAILogo from "./logos/TogetherAI.svelte"
import AzureOpenAILogo from "./logos/AzureOpenAI.svelte" import AzureOpenAILogo from "./logos/AzureOpenAI.svelte"
import { Providers } from "./constants" import { Providers } from "./constants"
import type { ProviderConfig } from "@budibase/types"
const logos = { const logos = {
["Budibase AI"]: BudibaseLogo, ["Budibase AI"]: BudibaseLogo,
[Providers.OpenAI.name]: OpenAILogo, [Providers.OpenAI.name]: OpenAILogo,
@ -15,11 +15,11 @@
[Providers.AzureOpenAI.name]: AzureOpenAILogo, [Providers.AzureOpenAI.name]: AzureOpenAILogo,
} }
export let config export let config: ProviderConfig
export let disabled export let disabled: boolean | null = null
export let editHandler export let editHandler: (() => void) | null
export let deleteHandler export let deleteHandler: (() => void) | null
</script> </script>
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->

View File

@ -1,4 +1,4 @@
<script> <script lang="ts">
import { onMount } from "svelte" import { onMount } from "svelte"
import { import {
Button, Button,
@ -16,22 +16,23 @@
import { API } from "@/api" import { API } from "@/api"
import AIConfigModal from "./ConfigModal.svelte" import AIConfigModal from "./ConfigModal.svelte"
import AIConfigTile from "./AIConfigTile.svelte" import AIConfigTile from "./AIConfigTile.svelte"
import {
type AIConfig,
ConfigType,
type ProviderConfig,
} from "@budibase/types"
const ConfigTypes = { let modal: Modal
AI: "ai", let fullAIConfig: AIConfig
} let editingAIConfig: ProviderConfig | undefined
let editingUuid: string | undefined
let modal
let fullAIConfig
let editingAIConfig = {}
let editingUuid
$: isCloud = $admin.cloud $: isCloud = $admin.cloud
$: customAIConfigsEnabled = $licensing.customAIConfigsEnabled $: customAIConfigsEnabled = $licensing.customAIConfigsEnabled
async function fetchAIConfig() { async function fetchAIConfig() {
try { try {
fullAIConfig = await API.getConfig(ConfigTypes.AI) fullAIConfig = (await API.getConfig(ConfigType.AI)) as AIConfig
} catch (error) { } catch (error) {
notifications.error("Error fetching AI config") notifications.error("Error fetching AI config")
} }
@ -42,9 +43,9 @@
const id = editingUuid || Helpers.uuid() const id = editingUuid || Helpers.uuid()
// Creating first custom AI Config // Creating first custom AI Config
if (!fullAIConfig) { if (!fullAIConfig && editingAIConfig) {
fullAIConfig = { fullAIConfig = {
type: ConfigTypes.AI, type: ConfigType.AI,
config: { config: {
[id]: editingAIConfig, [id]: editingAIConfig,
}, },
@ -54,7 +55,7 @@
delete fullAIConfig.config.budibase_ai delete fullAIConfig.config.budibase_ai
// unset the default value from other configs if default is set // unset the default value from other configs if default is set
if (editingAIConfig.isDefault) { if (editingAIConfig?.isDefault) {
for (let key in fullAIConfig.config) { for (let key in fullAIConfig.config) {
if (key !== id) { if (key !== id) {
fullAIConfig.config[key].isDefault = false fullAIConfig.config[key].isDefault = false
@ -62,8 +63,10 @@
} }
} }
// Add new or update existing custom AI Config // Add new or update existing custom AI Config
fullAIConfig.config[id] = editingAIConfig if (editingAIConfig) {
fullAIConfig.type = ConfigTypes.AI fullAIConfig.config[id] = editingAIConfig
}
fullAIConfig.type = ConfigType.AI
} }
try { try {
@ -72,7 +75,7 @@
} catch (error) { } catch (error) {
notifications.error( notifications.error(
`Failed to save AI Configuration, reason: ${ `Failed to save AI Configuration, reason: ${
error?.message || "Unknown" error instanceof Error ? error.message : "Unknown"
}` }`
) )
} finally { } finally {
@ -80,7 +83,7 @@
} }
} }
async function deleteConfig(key) { async function deleteConfig(key: string) {
// We don't store the default BB AI config in the DB // We don't store the default BB AI config in the DB
delete fullAIConfig.config.budibase_ai delete fullAIConfig.config.budibase_ai
// Delete the configuration // Delete the configuration
@ -91,14 +94,16 @@
notifications.success(`Deleted config`) notifications.success(`Deleted config`)
} catch (error) { } catch (error) {
notifications.error( notifications.error(
`Failed to delete config, reason: ${error?.message || "Unknown"}` `Failed to delete config, reason: ${
error instanceof Error ? error.message : "Unknown"
}`
) )
} finally { } finally {
await fetchAIConfig() await fetchAIConfig()
} }
} }
function editConfig(uuid) { function editConfig(uuid: string) {
editingUuid = uuid editingUuid = uuid
editingAIConfig = fullAIConfig?.config[editingUuid] editingAIConfig = fullAIConfig?.config[editingUuid]
modal.show() modal.show()
@ -136,7 +141,10 @@
</Tags> </Tags>
{/if} {/if}
</div> </div>
<Body>Configure your AI settings within this section:</Body> <Body
>Connect an LLM to enable AI features. You can only enable one LLM at a
time.</Body
>
</Layout> </Layout>
<Divider /> <Divider />
<div style={`opacity: ${customAIConfigsEnabled ? 1 : 0.5}`}> <div style={`opacity: ${customAIConfigsEnabled ? 1 : 0.5}`}>

View File

@ -56,7 +56,9 @@ interface LicensingState {
// user limits // user limits
userCount?: number userCount?: number
userLimit?: number userLimit?: number
aiCreditsLimit?: number
userLimitReached: boolean userLimitReached: boolean
aiCreditsExceeded: boolean
errUserLimit: boolean errUserLimit: boolean
} }
@ -102,6 +104,8 @@ class LicensingStore extends BudiStore<LicensingState> {
userLimit: undefined, userLimit: undefined,
userLimitReached: false, userLimitReached: false,
errUserLimit: false, errUserLimit: false,
// AI Limits
aiCreditsExceeded: false,
}) })
} }
@ -119,6 +123,16 @@ class LicensingStore extends BudiStore<LicensingState> {
return userCount > userLimit return userCount > userLimit
} }
aiCreditsExceeded(
aiCredits: number,
aiCreditsLimit = get(this.store).aiCreditsLimit
) {
if (aiCreditsLimit === UNLIMITED || aiCreditsLimit === undefined) {
return false
}
return aiCredits > aiCreditsLimit
}
async isCloud() { async isCloud() {
let adminStore = get(admin) let adminStore = get(admin)
if (!adminStore.loaded) { if (!adminStore.loaded) {
@ -291,9 +305,15 @@ class LicensingStore extends BudiStore<LicensingState> {
const userQuota = license.quotas.usage.static.users const userQuota = license.quotas.usage.static.users
const userLimit = userQuota.value const userLimit = userQuota.value
const aiCreditsQuota = license.quotas.usage.monthly.budibaseAICredits
const aiCreditsLimit = aiCreditsQuota.value
const userCount = usage.usageQuota.users const userCount = usage.usageQuota.users
const userLimitReached = this.usersLimitReached(userCount, userLimit) const userLimitReached = this.usersLimitReached(userCount, userLimit)
const userLimitExceeded = this.usersLimitExceeded(userCount, userLimit) const userLimitExceeded = this.usersLimitExceeded(userCount, userLimit)
const aiCreditsExceeded = this.aiCreditsExceeded(
usage.monthly.current.budibaseAICredits,
aiCreditsLimit
)
const isCloudAccount = await this.isCloud() const isCloudAccount = await this.isCloud()
const errUserLimit = const errUserLimit =
isCloudAccount && isCloudAccount &&
@ -315,6 +335,8 @@ class LicensingStore extends BudiStore<LicensingState> {
userLimit, userLimit,
userLimitReached, userLimitReached,
errUserLimit, errUserLimit,
aiCreditsLimit,
aiCreditsExceeded,
} }
}) })
} }

View File

@ -1,9 +1,11 @@
import { GenerateJsRequest, GenerateJsResponse } from "@budibase/types" import { GenerateJsRequest, GenerateJsResponse } from "@budibase/types"
import { BaseAPIClient } from "./types" import { BaseAPIClient } from "./types"
import { sleep } from "../utils/utils"
export interface AIEndpoints { export interface AIEndpoints {
generateCronExpression: (prompt: string) => Promise<{ message: string }> generateCronExpression: (prompt: string) => Promise<{ message: string }>
generateJs: (req: GenerateJsRequest) => Promise<GenerateJsResponse> generateJs: (req: GenerateJsRequest) => Promise<GenerateJsResponse>
generateTables: (prompt: string) => Promise<void>
} }
export const buildAIEndpoints = (API: BaseAPIClient): AIEndpoints => ({ export const buildAIEndpoints = (API: BaseAPIClient): AIEndpoints => ({
@ -23,4 +25,8 @@ export const buildAIEndpoints = (API: BaseAPIClient): AIEndpoints => ({
body: req, body: req,
}) })
}, },
generateTables: async prompt => {
console.warn({ prompt })
await sleep(1000)
},
}) })

@ -1 +1 @@
Subproject commit af8e1f1e53b9cb9a7d4b773e68b5f0344a895788 Subproject commit 6c9ccbb8a5737733448f6b0e23696de1ed343015

View File

@ -86,7 +86,7 @@
"csvtojson": "2.0.10", "csvtojson": "2.0.10",
"curlconverter": "3.21.0", "curlconverter": "3.21.0",
"dayjs": "^1.10.8", "dayjs": "^1.10.8",
"dd-trace": "5.43.0", "dd-trace": "5.47.0",
"dotenv": "8.2.0", "dotenv": "8.2.0",
"extract-zip": "^2.0.1", "extract-zip": "^2.0.1",
"form-data": "4.0.0", "form-data": "4.0.0",

View File

@ -1,3 +1,4 @@
import { z } from "zod"
import { mockChatGPTResponse } from "../../../tests/utilities/mocks/ai/openai" import { mockChatGPTResponse } from "../../../tests/utilities/mocks/ai/openai"
import TestConfiguration from "../../../tests/utilities/TestConfiguration" import TestConfiguration from "../../../tests/utilities/TestConfiguration"
import nock from "nock" import nock from "nock"
@ -10,12 +11,13 @@ import {
PlanModel, PlanModel,
PlanType, PlanType,
ProviderConfig, ProviderConfig,
StructuredOutput,
} from "@budibase/types" } from "@budibase/types"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { mocks } from "@budibase/backend-core/tests" import { generator, mocks } from "@budibase/backend-core/tests"
import { ai, quotas } from "@budibase/pro"
import { MockLLMResponseFn } from "../../../tests/utilities/mocks/ai" import { MockLLMResponseFn } from "../../../tests/utilities/mocks/ai"
import { mockAnthropicResponse } from "../../../tests/utilities/mocks/ai/anthropic" import { mockAnthropicResponse } from "../../../tests/utilities/mocks/ai/anthropic"
import { quotas } from "@budibase/pro"
function dedent(str: string) { function dedent(str: string) {
return str return str
@ -285,7 +287,8 @@ describe("BudibaseAI", () => {
envCleanup() envCleanup()
}) })
beforeEach(() => { beforeEach(async () => {
await config.newTenant()
nock.cleanAll() nock.cleanAll()
const license: License = { const license: License = {
plan: { plan: {
@ -366,5 +369,66 @@ describe("BudibaseAI", () => {
} }
) )
}) })
it("handles text format", async () => {
let usage = await getQuotaUsage()
expect(usage._id).toBe(`quota_usage_${config.getTenantId()}`)
expect(usage.monthly.current.budibaseAICredits).toBe(0)
const gptResponse = generator.word()
mockChatGPTResponse(gptResponse, { format: "text" })
const { message } = await config.api.ai.chat({
messages: [{ role: "user", content: "Hello!" }],
format: "text",
licenseKey: licenseKey,
})
expect(message).toBe(gptResponse)
usage = await getQuotaUsage()
expect(usage.monthly.current.budibaseAICredits).toBeGreaterThan(0)
})
it("handles json format", async () => {
let usage = await getQuotaUsage()
expect(usage._id).toBe(`quota_usage_${config.getTenantId()}`)
expect(usage.monthly.current.budibaseAICredits).toBe(0)
const gptResponse = JSON.stringify({
[generator.word()]: generator.word(),
})
mockChatGPTResponse(gptResponse, { format: "json" })
const { message } = await config.api.ai.chat({
messages: [{ role: "user", content: "Hello!" }],
format: "json",
licenseKey: licenseKey,
})
expect(message).toBe(gptResponse)
usage = await getQuotaUsage()
expect(usage.monthly.current.budibaseAICredits).toBeGreaterThan(0)
})
it("handles structured outputs", async () => {
let usage = await getQuotaUsage()
expect(usage._id).toBe(`quota_usage_${config.getTenantId()}`)
expect(usage.monthly.current.budibaseAICredits).toBe(0)
const gptResponse = generator.guid()
const structuredOutput = generator.word() as unknown as StructuredOutput
ai.structuredOutputs[structuredOutput] = {
key: generator.word(),
validator: z.object({ name: z.string() }),
}
mockChatGPTResponse(gptResponse, { format: structuredOutput })
const { message } = await config.api.ai.chat({
messages: [{ role: "user", content: "Hello!" }],
format: structuredOutput,
licenseKey: licenseKey,
})
expect(message).toBe(gptResponse)
usage = await getQuotaUsage()
expect(usage.monthly.current.budibaseAICredits).toBeGreaterThan(0)
})
}) })
}) })

View File

@ -1,7 +1,9 @@
import { ResponseFormat } from "@budibase/types"
import { Scope } from "nock" import { Scope } from "nock"
export interface MockLLMResponseOpts { export interface MockLLMResponseOpts {
host?: string host?: string
format?: ResponseFormat
} }
export type MockLLMResponseFn = ( export type MockLLMResponseFn = (

View File

@ -1,5 +1,7 @@
import nock from "nock" import nock from "nock"
import { MockLLMResponseFn, MockLLMResponseOpts } from "." import { MockLLMResponseFn, MockLLMResponseOpts } from "."
import _ from "lodash"
import { ai } from "@budibase/pro"
let chatID = 1 let chatID = 1
const SPACE_REGEX = /\s+/g const SPACE_REGEX = /\s+/g
@ -48,8 +50,15 @@ export const mockChatGPTResponse: MockLLMResponseFn = (
answer: string | ((prompt: string) => string), answer: string | ((prompt: string) => string),
opts?: MockLLMResponseOpts opts?: MockLLMResponseOpts
) => { ) => {
let body: any = undefined
if (opts?.format) {
body = _.matches({
response_format: ai.openai.parseResponseFormat(opts.format),
})
}
return nock(opts?.host || "https://api.openai.com") return nock(opts?.host || "https://api.openai.com")
.post("/v1/chat/completions") .post("/v1/chat/completions", body)
.reply((uri: string, body: nock.Body) => { .reply((uri: string, body: nock.Body) => {
const req = body as ChatCompletionRequest const req = body as ChatCompletionRequest
const messages = req.messages const messages = req.messages

View File

@ -5,12 +5,18 @@ export interface Message {
content: string content: string
} }
export enum StructuredOutput {}
export type ResponseFormat = "text" | "json" | StructuredOutput
export interface ChatCompletionRequest { export interface ChatCompletionRequest {
messages: Message[] messages: Message[]
format?: ResponseFormat
} }
export interface ChatCompletionResponse { export interface ChatCompletionResponse {
message?: string message?: string
tokensUsed?: number
} }
export interface GenerateJsRequest { export interface GenerateJsRequest {

View File

@ -1,4 +1,4 @@
import { License } from "../../../sdk" import { License, LLMProviderConfig } from "../../../sdk"
import { Account, DevInfo, User } from "../../../documents" import { Account, DevInfo, User } from "../../../documents"
import { FeatureFlags } from "@budibase/types" import { FeatureFlags } from "@budibase/types"
@ -11,6 +11,7 @@ export interface FetchAPIKeyResponse extends DevInfo {}
export interface GetGlobalSelfResponse extends User { export interface GetGlobalSelfResponse extends User {
flags?: FeatureFlags flags?: FeatureFlags
llm?: Omit<LLMProviderConfig, "apiKey">
account?: Account account?: Account
license: License license: License
budibaseAccess: boolean budibaseAccess: boolean

View File

@ -1,3 +1,5 @@
import { AIProvider } from "../documents"
export enum AIOperationEnum { export enum AIOperationEnum {
SUMMARISE_TEXT = "SUMMARISE_TEXT", SUMMARISE_TEXT = "SUMMARISE_TEXT",
CLEAN_DATA = "CLEAN_DATA", CLEAN_DATA = "CLEAN_DATA",
@ -89,3 +91,13 @@ export type AIColumnSchema =
| SentimentAnalysisSchema | SentimentAnalysisSchema
| PromptSchema | PromptSchema
| SearchWebSchema | SearchWebSchema
export interface LLMConfigOptions {
model: string
apiKey: string
measureUsage: boolean
}
export interface LLMProviderConfig extends LLMConfigOptions {
provider: AIProvider
}

View File

@ -1,6 +1,7 @@
export enum FeatureFlag { export enum FeatureFlag {
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR", USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
AI_JS_GENERATION = "AI_JS_GENERATION", AI_JS_GENERATION = "AI_JS_GENERATION",
AI_TABLE_GENERATION = "AI_TABLE_GENERATION",
// Account-portal // Account-portal
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL", DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
@ -9,6 +10,7 @@ export enum FeatureFlag {
export const FeatureFlagDefaults: Record<FeatureFlag, boolean> = { export const FeatureFlagDefaults: Record<FeatureFlag, boolean> = {
[FeatureFlag.USE_ZOD_VALIDATOR]: false, [FeatureFlag.USE_ZOD_VALIDATOR]: false,
[FeatureFlag.AI_JS_GENERATION]: false, [FeatureFlag.AI_JS_GENERATION]: false,
[FeatureFlag.AI_TABLE_GENERATION]: false,
// Account-portal // Account-portal
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false, [FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,

View File

@ -50,7 +50,7 @@
"bcrypt": "5.1.0", "bcrypt": "5.1.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bull": "4.10.1", "bull": "4.10.1",
"dd-trace": "5.43.0", "dd-trace": "5.47.0",
"dotenv": "8.6.0", "dotenv": "8.6.0",
"email-validator": "^2.0.4", "email-validator": "^2.0.4",
"global-agent": "3.0.0", "global-agent": "3.0.0",

View File

@ -8,7 +8,7 @@ import {
auth as authCore, auth as authCore,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import env from "../../../environment" import env from "../../../environment"
import { groups } from "@budibase/pro" import { ai, groups } from "@budibase/pro"
import { import {
DevInfo, DevInfo,
FetchAPIKeyResponse, FetchAPIKeyResponse,
@ -115,11 +115,20 @@ export async function getSelf(ctx: UserCtx<void, GetGlobalSelfResponse>) {
// add the feature flags for this tenant // add the feature flags for this tenant
const flags = await features.flags.fetch() const flags = await features.flags.fetch()
const llmConfig = await ai.getLLMConfig()
const sanitisedLLMConfig = llmConfig
? {
provider: llmConfig.provider,
model: llmConfig.model,
measureUsage: llmConfig.measureUsage,
}
: undefined
ctx.body = { ctx.body = {
...enrichedUser, ...enrichedUser,
...sessionAttributes, ...sessionAttributes,
flags, flags,
llm: sanitisedLLMConfig,
} }
} }

View File

@ -3028,45 +3028,37 @@
resolved "https://registry.yarnpkg.com/@dagrejs/graphlib/-/graphlib-2.2.4.tgz#d77bfa9ff49e2307c0c6e6b8b26b5dd3c05816c4" resolved "https://registry.yarnpkg.com/@dagrejs/graphlib/-/graphlib-2.2.4.tgz#d77bfa9ff49e2307c0c6e6b8b26b5dd3c05816c4"
integrity sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw== integrity sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==
"@datadog/libdatadog@^0.5.0": "@datadog/libdatadog@^0.5.1":
version "0.5.0" version "0.5.1"
resolved "https://registry.yarnpkg.com/@datadog/libdatadog/-/libdatadog-0.5.0.tgz#0ef2a2a76bb9505a0e7e5bc9be1415b467dbf368" resolved "https://registry.yarnpkg.com/@datadog/libdatadog/-/libdatadog-0.5.1.tgz#fe5c101c457998b74cb66f555f63197b34cad4ba"
integrity sha512-YvLUVOhYVjJssm0f22/RnDQMc7ZZt/w1bA0nty1vvjyaDz5EWaHfWaaV4GYpCt5MRvnGjCBxIwwbRivmGseKeQ== integrity sha512-KsdOxTUmtjoygaZInSS5U0+KnqoxPKGpcBjGgOHR9NDKfXzmbpy5AmoaPL7JxmMxQzwknpxSi7qzBOSB3yMoJg==
"@datadog/native-appsec@8.5.0": "@datadog/native-appsec@8.5.2":
version "8.5.0" version "8.5.2"
resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-8.5.0.tgz#cf4eea74a07085a0dc9f3e98c130736b38cd61c9" resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-8.5.2.tgz#93a2c15c71c2a90e19e12506fbbdec9ccbc91541"
integrity sha512-95y+fm7jd+3iknzuu57pWEPw9fcK9uSBCPiB4kSPHszHu3bESlZM553tc4ANsz+X3gMkYGVg2pgSydG77nSDJw== integrity sha512-lETBaVhBk+9o0pc+LDnXvp2ImDyT8K2deuqLf8A6q4/QjzCCXyR/yZO9R5+Kdoc93jZMRTWV9Pr4pBwHEdJSVA==
dependencies: dependencies:
node-gyp-build "^3.9.0" node-gyp-build "^3.9.0"
"@datadog/native-iast-rewriter@2.8.0": "@datadog/native-iast-taint-tracking@3.3.1":
version "2.8.0" version "3.3.1"
resolved "https://registry.yarnpkg.com/@datadog/native-iast-rewriter/-/native-iast-rewriter-2.8.0.tgz#8a7eddf5e33266643afcdfb920ff5ccb30e1894a" resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-3.3.1.tgz#71d2c9bdb102b4482fea145d3f22ed5453628500"
integrity sha512-DKmtvlmCld9RIJwDcPKWNkKYWYQyiuOrOtynmBppJiUv/yfCOuZtsQV4Zepj40H33sLiQyi5ct6dbWl53vxqkA== integrity sha512-TgXpoX/CDgPfYAKu9qLmEyb9UXvRVC00D71islcSb70MCFmxQwkgXGl/gAk6YA6/NmZ4j8+cgY1lSNqStGvOMg==
dependencies:
lru-cache "^7.14.0"
node-gyp-build "^4.5.0"
"@datadog/native-iast-taint-tracking@3.3.0":
version "3.3.0"
resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-3.3.0.tgz#5a9c87e07376e7c5a4b4d4985f140a60388eee00"
integrity sha512-OzmjOncer199ATSYeCAwSACCRyQimo77LKadSHDUcxa/n9FYU+2U/bYQTYsK3vquSA2E47EbSVq9rytrlTdvnA==
dependencies: dependencies:
node-gyp-build "^3.9.0" node-gyp-build "^3.9.0"
"@datadog/native-metrics@^3.1.0": "@datadog/native-metrics@^3.1.1":
version "3.1.0" version "3.1.1"
resolved "https://registry.yarnpkg.com/@datadog/native-metrics/-/native-metrics-3.1.0.tgz#c2378841accd9fdd6866d0e49bdf6e3d76e79f22" resolved "https://registry.yarnpkg.com/@datadog/native-metrics/-/native-metrics-3.1.1.tgz#4e5c9775751af13e353e64e573ab724104538cee"
integrity sha512-yOBi4x0OQRaGNPZ2bx9TGvDIgEdQ8fkudLTFAe7gEM1nAlvFmbE5YfpH8WenEtTSEBwojSau06m2q7axtEEmCg== integrity sha512-MU1gHrolwryrU4X9g+fylA1KPH3S46oqJPEtVyrO+3Kh29z80fegmtyrU22bNt8LigPUK/EdPCnSbMe88QbnxQ==
dependencies: dependencies:
node-addon-api "^6.1.0" node-addon-api "^6.1.0"
node-gyp-build "^3.9.0" node-gyp-build "^3.9.0"
"@datadog/pprof@5.6.0": "@datadog/pprof@5.7.1":
version "5.6.0" version "5.7.1"
resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-5.6.0.tgz#b6f5c566512ba5e55c6dbf46e9f0f020cfd5c6b5" resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-5.7.1.tgz#3ed62372af7331c37de401319bde9e3d4dc5a8c0"
integrity sha512-x7yN0s4wMnRqv3PWQ6eXKH5XE5qvCOwWbOsXqpT2Irbsc7Wcl5w5JrJUcbPCdSJGihpIh6kAeIrS6w/ZCcHy2Q== integrity sha512-D5XTxsaPG36x41vZZn8hsAeC7QQDx0rv1a1Uhxo5xCXUB/9rc19+I7iCnjgJS5aH0ShXdPVOWRClo16hOSKKSw==
dependencies: dependencies:
delay "^5.0.0" delay "^5.0.0"
node-gyp-build "<4.0" node-gyp-build "<4.0"
@ -3079,6 +3071,16 @@
resolved "https://registry.yarnpkg.com/@datadog/sketches-js/-/sketches-js-2.1.0.tgz#8c7e8028a5fc22ad102fa542b0a446c956830455" resolved "https://registry.yarnpkg.com/@datadog/sketches-js/-/sketches-js-2.1.0.tgz#8c7e8028a5fc22ad102fa542b0a446c956830455"
integrity sha512-smLocSfrt3s53H/XSVP3/1kP42oqvrkjUPtyaFd1F79ux24oE31BKt+q0c6lsa6hOYrFzsIwyc5GXAI5JmfOew== integrity sha512-smLocSfrt3s53H/XSVP3/1kP42oqvrkjUPtyaFd1F79ux24oE31BKt+q0c6lsa6hOYrFzsIwyc5GXAI5JmfOew==
"@datadog/wasm-js-rewriter@4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@datadog/wasm-js-rewriter/-/wasm-js-rewriter-4.0.0.tgz#46963ffa39365f4bc0aff6114bba6545635f9c69"
integrity sha512-atw1uocrNlG3Fi7usLG5irTpasoz519YTZUyAxp7ZCppDNA+iLHpK1NksV9E2uCGMzF6dGifGmDaui0nK12nYg==
dependencies:
js-yaml "^4.1.0"
lru-cache "^7.14.0"
module-details-from-path "^1.0.3"
node-gyp-build "^4.5.0"
"@elastic/elasticsearch@7.10.0": "@elastic/elasticsearch@7.10.0":
version "7.10.0" version "7.10.0"
resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-7.10.0.tgz#da105a9c1f14146f9f2cab4e7026cb7949121b8d" resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-7.10.0.tgz#da105a9c1f14146f9f2cab4e7026cb7949121b8d"
@ -10000,28 +10002,28 @@ dayjs@^1.10.8:
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
dc-polyfill@^0.1.4: dc-polyfill@0.1.8:
version "0.1.6" version "0.1.8"
resolved "https://registry.yarnpkg.com/dc-polyfill/-/dc-polyfill-0.1.6.tgz#c2940fa68ffb24a7bf127cc6cfdd15b39f0e7f02" resolved "https://registry.yarnpkg.com/dc-polyfill/-/dc-polyfill-0.1.8.tgz#2d91dd4dd0f2e3575ce038d013f346161f5a413a"
integrity sha512-UV33cugmCC49a5uWAApM+6Ev9ZdvIUMTrtCO9fj96TPGOQiea54oeO3tiEVdVeo3J9N2UdJEmbS4zOkkEA35uQ== integrity sha512-F9+06papa9GOFUMjxGiqM1bS98pOkinZpBF3Sygb46owrXaHdR2uLkftE6nygrqNcAurdwKjLAtX+0GJkSwIFQ==
dd-trace@5.43.0: dd-trace@5.47.0:
version "5.43.0" version "5.47.0"
resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-5.43.0.tgz#f321debd74c01d9e1e6d63b99539e7247d89f10f" resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-5.47.0.tgz#c9da1469f752c002ea9947fa6535538eb496d0f5"
integrity sha512-WtPUSZfEosSHYVBFR48FqfYBFor8QchKwAKo+LYtbgTPtFzYKyBV/FJUqYE6sDF15Raf4sJVt/LOscywgj2zEw== integrity sha512-1IrJ3LYPS/ylQdeOKpc8VscshgfngK1Ht7ZWJpNI6KlSZ6JB1rQmuDdGmhUYt7tXTTKRStTmaC4uSMJKADu8Xg==
dependencies: dependencies:
"@datadog/libdatadog" "^0.5.0" "@datadog/libdatadog" "^0.5.1"
"@datadog/native-appsec" "8.5.0" "@datadog/native-appsec" "8.5.2"
"@datadog/native-iast-rewriter" "2.8.0" "@datadog/native-iast-taint-tracking" "3.3.1"
"@datadog/native-iast-taint-tracking" "3.3.0" "@datadog/native-metrics" "^3.1.1"
"@datadog/native-metrics" "^3.1.0" "@datadog/pprof" "5.7.1"
"@datadog/pprof" "5.6.0"
"@datadog/sketches-js" "^2.1.0" "@datadog/sketches-js" "^2.1.0"
"@datadog/wasm-js-rewriter" "4.0.0"
"@isaacs/ttlcache" "^1.4.1" "@isaacs/ttlcache" "^1.4.1"
"@opentelemetry/api" ">=1.0.0 <1.9.0" "@opentelemetry/api" ">=1.0.0 <1.9.0"
"@opentelemetry/core" "^1.14.0" "@opentelemetry/core" "^1.14.0"
crypto-randomuuid "^1.0.0" crypto-randomuuid "^1.0.0"
dc-polyfill "^0.1.4" dc-polyfill "0.1.8"
ignore "^5.2.4" ignore "^5.2.4"
import-in-the-middle "1.13.1" import-in-the-middle "1.13.1"
istanbul-lib-coverage "3.2.0" istanbul-lib-coverage "3.2.0"