rough v1
This commit is contained in:
parent
b1462b4c19
commit
2d21afbe10
|
@ -1141,7 +1141,8 @@ class InternalBuilder {
|
||||||
schema.constraints?.presence === true ||
|
schema.constraints?.presence === true ||
|
||||||
schema.type === FieldType.FORMULA ||
|
schema.type === FieldType.FORMULA ||
|
||||||
schema.type === FieldType.AUTO ||
|
schema.type === FieldType.AUTO ||
|
||||||
schema.type === FieldType.LINK
|
schema.type === FieldType.LINK ||
|
||||||
|
schema.type === FieldType.AI
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import SchemaBuilder = Knex.SchemaBuilder
|
||||||
import CreateTableBuilder = Knex.CreateTableBuilder
|
import CreateTableBuilder = Knex.CreateTableBuilder
|
||||||
|
|
||||||
function isIgnoredType(type: FieldType) {
|
function isIgnoredType(type: FieldType) {
|
||||||
const ignored = [FieldType.LINK, FieldType.FORMULA]
|
const ignored = [FieldType.LINK, FieldType.FORMULA, FieldType.AI]
|
||||||
return ignored.indexOf(type) !== -1
|
return ignored.indexOf(type) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +144,9 @@ function generateSchema(
|
||||||
case FieldType.FORMULA:
|
case FieldType.FORMULA:
|
||||||
// This is allowed, but nothing to do on the external datasource
|
// This is allowed, but nothing to do on the external datasource
|
||||||
break
|
break
|
||||||
|
case FieldType.AI:
|
||||||
|
// This is allowed, but nothing to do on the external datasource
|
||||||
|
break
|
||||||
case FieldType.ATTACHMENTS:
|
case FieldType.ATTACHMENTS:
|
||||||
case FieldType.ATTACHMENT_SINGLE:
|
case FieldType.ATTACHMENT_SINGLE:
|
||||||
case FieldType.SIGNATURE_SINGLE:
|
case FieldType.SIGNATURE_SINGLE:
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import { getAutoColumnInformation, buildAutoColumn } from "helpers/utils"
|
import { getAutoColumnInformation, buildAutoColumn } from "helpers/utils"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import AIFieldConfiguration from "components/common/AIFieldConfiguration.svelte"
|
||||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||||
import { getBindings } from "components/backend/DataTable/formula"
|
import { getBindings } from "components/backend/DataTable/formula"
|
||||||
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
const NUMBER_TYPE = FieldType.NUMBER
|
const NUMBER_TYPE = FieldType.NUMBER
|
||||||
const JSON_TYPE = FieldType.JSON
|
const JSON_TYPE = FieldType.JSON
|
||||||
const DATE_TYPE = FieldType.DATETIME
|
const DATE_TYPE = FieldType.DATETIME
|
||||||
|
const AI_TYPE = FieldType.AI
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const { dispatch: gridDispatch, rows } = getContext("grid")
|
const { dispatch: gridDispatch, rows } = getContext("grid")
|
||||||
|
@ -421,6 +423,7 @@
|
||||||
FIELDS.ATTACHMENT_SINGLE,
|
FIELDS.ATTACHMENT_SINGLE,
|
||||||
FIELDS.ATTACHMENTS,
|
FIELDS.ATTACHMENTS,
|
||||||
FIELDS.FORMULA,
|
FIELDS.FORMULA,
|
||||||
|
FIELDS.AI,
|
||||||
FIELDS.JSON,
|
FIELDS.JSON,
|
||||||
FIELDS.BARCODEQR,
|
FIELDS.BARCODEQR,
|
||||||
FIELDS.SIGNATURE_SINGLE,
|
FIELDS.SIGNATURE_SINGLE,
|
||||||
|
@ -732,6 +735,13 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if editableColumn.type === AI_TYPE}
|
||||||
|
<AIFieldConfiguration
|
||||||
|
aiField={editableColumn}
|
||||||
|
context={rowGoldenSample}
|
||||||
|
bindings={getBindings({ table })}
|
||||||
|
schema={table.schema}
|
||||||
|
/>
|
||||||
{:else if editableColumn.type === JSON_TYPE}
|
{:else if editableColumn.type === JSON_TYPE}
|
||||||
<Button primary text on:click={openJsonSchemaEditor}
|
<Button primary text on:click={openJsonSchemaEditor}
|
||||||
>Open schema editor</Button
|
>Open schema editor</Button
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
<script>
|
||||||
|
import { Input, Multiselect, Select, TextArea } from "@budibase/bbui"
|
||||||
|
import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte"
|
||||||
|
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||||
|
|
||||||
|
const AIOperations = {
|
||||||
|
SUMMARISE_TEXT: {
|
||||||
|
label: "Summarise Text",
|
||||||
|
value: "SUMMARISE_TEXT"
|
||||||
|
},
|
||||||
|
CLEAN_DATA: {
|
||||||
|
label: "Clean Data",
|
||||||
|
value: "CLEAN_DATA"
|
||||||
|
},
|
||||||
|
TRANSLATE: {
|
||||||
|
label: "Translate",
|
||||||
|
value: "TRANSLATE"
|
||||||
|
},
|
||||||
|
CATEGORISE_TEXT: {
|
||||||
|
label: "Categorise Text",
|
||||||
|
value: "CATEGORISE_TEXT"
|
||||||
|
},
|
||||||
|
SENTIMENT_ANALYSIS: {
|
||||||
|
label: "Sentiment Analysis",
|
||||||
|
value: "SENTIMENT_ANALYSIS"
|
||||||
|
},
|
||||||
|
PROMPT: {
|
||||||
|
label: "Prompt",
|
||||||
|
value: "PROMPT"
|
||||||
|
},
|
||||||
|
SEARCH_WEB: {
|
||||||
|
label: "Search Web",
|
||||||
|
value: "SEARCH_WEB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const OperationFieldTypes = {
|
||||||
|
MULTI_COLUMN: "columns",
|
||||||
|
COLUMN: "column",
|
||||||
|
BINDABLE_TEXT: "prompt",
|
||||||
|
// LANGUAGE: "language",
|
||||||
|
}
|
||||||
|
|
||||||
|
const OperationFields = {
|
||||||
|
SUMMARISE_TEXT: {
|
||||||
|
columns: OperationFieldTypes.MULTI_COLUMN,
|
||||||
|
prompt: OperationFieldTypes.BINDABLE_TEXT,
|
||||||
|
},
|
||||||
|
CLEAN_DATA: {
|
||||||
|
columns: OperationFieldTypes.MULTI_COLUMN,
|
||||||
|
prompt: OperationFieldTypes.BINDABLE_TEXT,
|
||||||
|
},
|
||||||
|
TRANSLATE: {
|
||||||
|
columns: OperationFieldTypes.MULTI_COLUMN,
|
||||||
|
language: OperationFieldTypes.BINDABLE_TEXT,
|
||||||
|
prompt: OperationFieldTypes.BINDABLE_TEXT,
|
||||||
|
},
|
||||||
|
CATEGORISE_TEXT: {
|
||||||
|
columns: OperationFieldTypes.MULTI_COLUMN,
|
||||||
|
categories: OperationFieldTypes.BINDABLE_TEXT,
|
||||||
|
prompt: OperationFieldTypes.BINDABLE_TEXT,
|
||||||
|
},
|
||||||
|
SENTIMENT_ANALYSIS: {
|
||||||
|
column: OperationFieldTypes.COLUMN,
|
||||||
|
prompt: OperationFieldTypes.BINDABLE_TEXT,
|
||||||
|
},
|
||||||
|
PROMPT: {
|
||||||
|
prompt: OperationFieldTypes.BINDABLE_TEXT,
|
||||||
|
},
|
||||||
|
SEARCH_WEB: {
|
||||||
|
columns: OperationFieldTypes.MULTI_COLUMN,
|
||||||
|
prompt: OperationFieldTypes.BINDABLE_TEXT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AIFieldConfigOptions = Object.keys(AIOperations).map(key => ({
|
||||||
|
label: AIOperations[key].label,
|
||||||
|
value: AIOperations[key].value
|
||||||
|
}))
|
||||||
|
|
||||||
|
export let bindings
|
||||||
|
export let context
|
||||||
|
export let schema
|
||||||
|
export let aiField = {}
|
||||||
|
|
||||||
|
$: OperationField = OperationFields[aiField.operation] || null
|
||||||
|
$: console.log(aiField)
|
||||||
|
$: console.log(schema)
|
||||||
|
$: schemaWithoutRelations = Object.keys(schema).filter(key => schema[key].type !== "link")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
options={AIFieldConfigOptions}
|
||||||
|
bind:value={aiField.operation}
|
||||||
|
/>
|
||||||
|
{#if aiField.operation}
|
||||||
|
{#each Object.keys(OperationField) as key}
|
||||||
|
{#if OperationField[key] === OperationFieldTypes.BINDABLE_TEXT}
|
||||||
|
<ModalBindableInput
|
||||||
|
label={key}
|
||||||
|
panel={ServerBindingPanel}
|
||||||
|
title="Prompt"
|
||||||
|
on:change={e => aiField[key] = e.detail}
|
||||||
|
value={aiField[key]}
|
||||||
|
{bindings}
|
||||||
|
allowJS
|
||||||
|
{context}
|
||||||
|
/>
|
||||||
|
{:else if OperationField[key] === OperationFieldTypes.MULTI_COLUMN}
|
||||||
|
<Multiselect
|
||||||
|
bind:value={aiField[key]}
|
||||||
|
label={key}
|
||||||
|
options={schemaWithoutRelations}
|
||||||
|
/>
|
||||||
|
{:else if OperationField[key] === OperationFieldTypes.COLUMN}
|
||||||
|
<Select
|
||||||
|
bind:value={aiField[key]}
|
||||||
|
label={key}
|
||||||
|
options={schemaWithoutRelations}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
|
@ -159,6 +159,12 @@ export const FIELDS = {
|
||||||
icon: TypeIconMap[FieldType.FORMULA],
|
icon: TypeIconMap[FieldType.FORMULA],
|
||||||
constraints: {},
|
constraints: {},
|
||||||
},
|
},
|
||||||
|
AI: {
|
||||||
|
name: "AI",
|
||||||
|
type: FieldType.AI,
|
||||||
|
icon: TypeIconMap[FieldType.AI],
|
||||||
|
constraints: {},
|
||||||
|
},
|
||||||
JSON: {
|
JSON: {
|
||||||
name: "JSON",
|
name: "JSON",
|
||||||
type: FieldType.JSON,
|
type: FieldType.JSON,
|
||||||
|
|
|
@ -262,7 +262,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if allowBindings && filter.field && filter.valueType === "Binding"}
|
{#if allowBindings && filter.field && filter.valueType === "Binding"}
|
||||||
<slot name="binding" {filter} />
|
<slot name="binding" {filter} />
|
||||||
{:else if [FieldType.STRING, FieldType.LONGFORM, FieldType.NUMBER, FieldType.BIGINT, FieldType.FORMULA].includes(filter.type)}
|
{:else if [FieldType.STRING, FieldType.LONGFORM, FieldType.NUMBER, FieldType.BIGINT, FieldType.FORMULA, FieldType.AI].includes(filter.type)}
|
||||||
<Input disabled={filter.noValue} bind:value={filter.value} />
|
<Input disabled={filter.noValue} bind:value={filter.value} />
|
||||||
{:else if filter.type === FieldType.ARRAY || (filter.type === FieldType.OPTIONS && filter.operator === ArrayOperator.ONE_OF)}
|
{:else if filter.type === FieldType.ARRAY || (filter.type === FieldType.OPTIONS && filter.operator === ArrayOperator.ONE_OF)}
|
||||||
<Multiselect
|
<Multiselect
|
||||||
|
|
|
@ -95,7 +95,8 @@
|
||||||
const { type, formulaType } = col.schema
|
const { type, formulaType } = col.schema
|
||||||
return (
|
return (
|
||||||
searchableTypes.includes(type) ||
|
searchableTypes.includes(type) ||
|
||||||
(type === FieldType.FORMULA && formulaType === FormulaType.STATIC)
|
(type === FieldType.FORMULA && formulaType === FormulaType.STATIC) ||
|
||||||
|
type === FieldType.AI
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,8 @@ const TypeComponentMap = {
|
||||||
[FieldType.ATTACHMENT_SINGLE]: AttachmentSingleCell,
|
[FieldType.ATTACHMENT_SINGLE]: AttachmentSingleCell,
|
||||||
[FieldType.LINK]: RelationshipCell,
|
[FieldType.LINK]: RelationshipCell,
|
||||||
[FieldType.FORMULA]: FormulaCell,
|
[FieldType.FORMULA]: FormulaCell,
|
||||||
|
// TODO: fix
|
||||||
|
[FieldType.AI]: FormulaCell,
|
||||||
[FieldType.JSON]: JSONCell,
|
[FieldType.JSON]: JSONCell,
|
||||||
[FieldType.BB_REFERENCE]: BBReferenceCell,
|
[FieldType.BB_REFERENCE]: BBReferenceCell,
|
||||||
[FieldType.BB_REFERENCE_SINGLE]: BBReferenceSingleCell,
|
[FieldType.BB_REFERENCE_SINGLE]: BBReferenceSingleCell,
|
||||||
|
|
|
@ -160,6 +160,7 @@ export const TypeIconMap = {
|
||||||
[FieldType.ATTACHMENT_SINGLE]: "DocumentFragment",
|
[FieldType.ATTACHMENT_SINGLE]: "DocumentFragment",
|
||||||
[FieldType.LINK]: "DataCorrelated",
|
[FieldType.LINK]: "DataCorrelated",
|
||||||
[FieldType.FORMULA]: "Calculator",
|
[FieldType.FORMULA]: "Calculator",
|
||||||
|
[FieldType.AI]: "MagicWand",
|
||||||
[FieldType.JSON]: "Brackets",
|
[FieldType.JSON]: "Brackets",
|
||||||
[FieldType.BIGINT]: "TagBold",
|
[FieldType.BIGINT]: "TagBold",
|
||||||
[FieldType.AUTO]: "MagicWand",
|
[FieldType.AUTO]: "MagicWand",
|
||||||
|
|
|
@ -154,7 +154,8 @@ function isEditableColumn(column: FieldSchema) {
|
||||||
column.autoReason !== AutoReason.FOREIGN_KEY &&
|
column.autoReason !== AutoReason.FOREIGN_KEY &&
|
||||||
column.subtype !== AutoFieldSubType.AUTO_ID
|
column.subtype !== AutoFieldSubType.AUTO_ID
|
||||||
const isFormula = column.type === FieldType.FORMULA
|
const isFormula = column.type === FieldType.FORMULA
|
||||||
return !(isExternalAutoColumn || isFormula)
|
const isAIColumn = column.type === FieldType.AI
|
||||||
|
return !(isExternalAutoColumn || isFormula || isAIColumn)
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExternalRequest<T extends Operation> {
|
export class ExternalRequest<T extends Operation> {
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { getRowParams } from "../../../db/utils"
|
||||||
|
import {
|
||||||
|
outputProcessing,
|
||||||
|
processAIColumns,
|
||||||
|
} from "../../../utilities/rowProcessor"
|
||||||
|
import { context } from "@budibase/backend-core"
|
||||||
|
import { Table, Row } from "@budibase/types"
|
||||||
|
import isEqual from "lodash/isEqual"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
|
export async function updateAllAIColumnsInTable(table: Table) {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
// start by getting the raw rows (which will be written back to DB after update)
|
||||||
|
let rows = (
|
||||||
|
await db.allDocs<Row>(
|
||||||
|
getRowParams(table._id, null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).rows.map(row => row.doc!)
|
||||||
|
// now enrich the rows, note the clone so that we have the base state of the
|
||||||
|
// rows so that we don't write any of the enriched information back
|
||||||
|
let enrichedRows = await outputProcessing(table, cloneDeep(rows), {
|
||||||
|
squash: false,
|
||||||
|
})
|
||||||
|
const updatedRows = []
|
||||||
|
for (let row of rows) {
|
||||||
|
// find the enriched row, if found process the formulas
|
||||||
|
const enrichedRow = enrichedRows.find(
|
||||||
|
(enriched: Row) => enriched._id === row._id
|
||||||
|
)
|
||||||
|
if (enrichedRow) {
|
||||||
|
let processed = await processAIColumns(table, cloneDeep(row), {
|
||||||
|
contextRows: [enrichedRow],
|
||||||
|
})
|
||||||
|
// values have changed, need to add to bulk docs to update
|
||||||
|
if (!isEqual(processed, row)) {
|
||||||
|
updatedRows.push(processed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await db.bulkDocs(updatedRows)
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { getRowParams } from "../../../db/utils"
|
import { getRowParams } from "../../../db/utils"
|
||||||
import {
|
import {
|
||||||
outputProcessing,
|
outputProcessing, processAIColumns,
|
||||||
processFormulas,
|
processFormulas,
|
||||||
} from "../../../utilities/rowProcessor"
|
} from "../../../utilities/rowProcessor"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
|
@ -101,7 +101,7 @@ export async function updateAllFormulasInTable(table: Table) {
|
||||||
(enriched: Row) => enriched._id === row._id
|
(enriched: Row) => enriched._id === row._id
|
||||||
)
|
)
|
||||||
if (enrichedRow) {
|
if (enrichedRow) {
|
||||||
const processed = await processFormulas(table, cloneDeep(row), {
|
let processed = await processFormulas(table, cloneDeep(row), {
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
contextRows: [enrichedRow],
|
contextRows: [enrichedRow],
|
||||||
})
|
})
|
||||||
|
@ -142,6 +142,10 @@ export async function finaliseRow(
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
contextRows: [enrichedRow],
|
contextRows: [enrichedRow],
|
||||||
})
|
})
|
||||||
|
row = await processAIColumns(table, row, {
|
||||||
|
contextRows: [enrichedRow],
|
||||||
|
})
|
||||||
|
|
||||||
// don't worry about rev, tables handle rev/lastID updates
|
// don't worry about rev, tables handle rev/lastID updates
|
||||||
// if another row has been written since processing this will
|
// if another row has been written since processing this will
|
||||||
// handle the auto ID clash
|
// handle the auto ID clash
|
||||||
|
@ -154,6 +158,10 @@ export async function finaliseRow(
|
||||||
enrichedRow = await processFormulas(table, enrichedRow, {
|
enrichedRow = await processFormulas(table, enrichedRow, {
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
})
|
})
|
||||||
|
enrichedRow = await processAIColumns(table, row, {
|
||||||
|
contextRows: [enrichedRow],
|
||||||
|
})
|
||||||
|
|
||||||
// this updates the related formulas in other rows based on the relations to this row
|
// this updates the related formulas in other rows based on the relations to this row
|
||||||
if (updateFormula) {
|
if (updateFormula) {
|
||||||
await updateRelatedFormula(table, enrichedRow)
|
await updateRelatedFormula(table, enrichedRow)
|
||||||
|
|
|
@ -119,6 +119,7 @@ export function buildSqlFieldList(
|
||||||
([columnName, column]) =>
|
([columnName, column]) =>
|
||||||
column.type !== FieldType.LINK &&
|
column.type !== FieldType.LINK &&
|
||||||
column.type !== FieldType.FORMULA &&
|
column.type !== FieldType.FORMULA &&
|
||||||
|
column.type !== FieldType.AI &&
|
||||||
!existing.find((field: string) => field === columnName)
|
!existing.find((field: string) => field === columnName)
|
||||||
)
|
)
|
||||||
.map(column => `${table.name}.${column[0]}`)
|
.map(column => `${table.name}.${column[0]}`)
|
||||||
|
|
|
@ -5,15 +5,10 @@ import isEqual from "lodash/isEqual"
|
||||||
import uniq from "lodash/uniq"
|
import uniq from "lodash/uniq"
|
||||||
import { updateAllFormulasInTable } from "../row/staticFormula"
|
import { updateAllFormulasInTable } from "../row/staticFormula"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import {
|
import { FieldSchema, FieldType, FormulaFieldMetadata, FormulaType, Table, } from "@budibase/types"
|
||||||
FormulaType,
|
|
||||||
FieldSchema,
|
|
||||||
FieldType,
|
|
||||||
FormulaFieldMetadata,
|
|
||||||
Table,
|
|
||||||
} from "@budibase/types"
|
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import { isRelationshipColumn } from "../../../db/utils"
|
import { isRelationshipColumn } from "../../../db/utils"
|
||||||
|
import { updateAllAIColumnsInTable } from "../row/aiColumn"
|
||||||
|
|
||||||
function isStaticFormula(
|
function isStaticFormula(
|
||||||
column: FieldSchema
|
column: FieldSchema
|
||||||
|
@ -198,3 +193,21 @@ export async function runStaticFormulaChecks(
|
||||||
await checkIfFormulaUpdated(table, { oldTable })
|
await checkIfFormulaUpdated(table, { oldTable })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function runAIColumnChecks(
|
||||||
|
table: Table,
|
||||||
|
{ oldTable }: { oldTable?: Table }
|
||||||
|
) {
|
||||||
|
// look to see if any formula values have changed
|
||||||
|
const shouldUpdate = Object.values(table.schema).find(
|
||||||
|
column =>
|
||||||
|
column.type === FieldType.AI &&
|
||||||
|
(!oldTable ||
|
||||||
|
!oldTable.schema[column.name] ||
|
||||||
|
!isEqual(oldTable.schema[column.name], column))
|
||||||
|
)
|
||||||
|
// if a static formula column has updated, then need to run the update
|
||||||
|
if (shouldUpdate != null) {
|
||||||
|
await updateAllAIColumnsInTable(table)
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
import { runStaticFormulaChecks } from "./bulkFormula"
|
import { runAIColumnChecks, runStaticFormulaChecks } from "./bulkFormula"
|
||||||
|
|
||||||
export async function clearColumns(table: Table, columnNames: string[]) {
|
export async function clearColumns(table: Table, columnNames: string[]) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
|
@ -296,7 +296,7 @@ export async function squashLinks<T = Row[] | Row>(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
[FieldType.LINK, FieldType.FORMULA].includes(tableColumn.type)
|
[FieldType.LINK, FieldType.FORMULA, FieldType.AI].includes(tableColumn.type)
|
||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ interface AuthTokenResponse {
|
||||||
const isTypeAllowed: Record<FieldType, boolean> = {
|
const isTypeAllowed: Record<FieldType, boolean> = {
|
||||||
[FieldType.STRING]: true,
|
[FieldType.STRING]: true,
|
||||||
[FieldType.FORMULA]: true,
|
[FieldType.FORMULA]: true,
|
||||||
|
[FieldType.AI]: true,
|
||||||
[FieldType.NUMBER]: true,
|
[FieldType.NUMBER]: true,
|
||||||
[FieldType.LONGFORM]: true,
|
[FieldType.LONGFORM]: true,
|
||||||
[FieldType.DATETIME]: true,
|
[FieldType.DATETIME]: true,
|
||||||
|
@ -490,7 +491,8 @@ export class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!sheet.headerValues.includes(key) &&
|
!sheet.headerValues.includes(key) &&
|
||||||
column.type !== FieldType.FORMULA
|
column.type !== FieldType.FORMULA &&
|
||||||
|
column.type !== FieldType.AI
|
||||||
) {
|
) {
|
||||||
updatedHeaderValues.push(key)
|
updatedHeaderValues.push(key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
import { EventType, updateLinks } from "../../../../db/linkedRows"
|
import { EventType, updateLinks } from "../../../../db/linkedRows"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import isEqual from "lodash/isEqual"
|
import isEqual from "lodash/isEqual"
|
||||||
import { runStaticFormulaChecks } from "../../../../api/controllers/table/bulkFormula"
|
import { runAIColumnChecks, runStaticFormulaChecks } from "../../../../api/controllers/table/bulkFormula"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { findDuplicateInternalColumns } from "@budibase/shared-core"
|
import { findDuplicateInternalColumns } from "@budibase/shared-core"
|
||||||
import { getTable } from "../getters"
|
import { getTable } from "../getters"
|
||||||
|
@ -133,6 +133,7 @@ export async function save(
|
||||||
}
|
}
|
||||||
// has to run after, make sure it has _id
|
// has to run after, make sure it has _id
|
||||||
await runStaticFormulaChecks(table, { oldTable, deletion: false })
|
await runStaticFormulaChecks(table, { oldTable, deletion: false })
|
||||||
|
await runAIColumnChecks(table, { oldTable, deletion: false })
|
||||||
return { table }
|
return { table }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ const FieldTypeMap: Record<FieldType, SQLiteType> = {
|
||||||
[FieldType.BOOLEAN]: SQLiteType.NUMERIC,
|
[FieldType.BOOLEAN]: SQLiteType.NUMERIC,
|
||||||
[FieldType.DATETIME]: SQLiteType.TEXT,
|
[FieldType.DATETIME]: SQLiteType.TEXT,
|
||||||
[FieldType.FORMULA]: SQLiteType.TEXT,
|
[FieldType.FORMULA]: SQLiteType.TEXT,
|
||||||
|
[FieldType.AI]: SQLiteType.TEXT,
|
||||||
[FieldType.LONGFORM]: SQLiteType.TEXT,
|
[FieldType.LONGFORM]: SQLiteType.TEXT,
|
||||||
[FieldType.NUMBER]: SQLiteType.REAL,
|
[FieldType.NUMBER]: SQLiteType.REAL,
|
||||||
[FieldType.STRING]: SQLiteType.TEXT,
|
[FieldType.STRING]: SQLiteType.TEXT,
|
||||||
|
|
|
@ -172,7 +172,7 @@ export async function enrichSchema(
|
||||||
|
|
||||||
for (const relTableFieldName of Object.keys(relTable.schema)) {
|
for (const relTableFieldName of Object.keys(relTable.schema)) {
|
||||||
const relTableField = relTable.schema[relTableFieldName]
|
const relTableField = relTable.schema[relTableFieldName]
|
||||||
if ([FieldType.LINK, FieldType.FORMULA].includes(relTableField.type)) {
|
if ([FieldType.LINK, FieldType.FORMULA, FieldType.AI].includes(relTableField.type)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -189,6 +189,10 @@ export async function inputProcessing(
|
||||||
if (field.type === FieldType.FORMULA) {
|
if (field.type === FieldType.FORMULA) {
|
||||||
delete clonedRow[key]
|
delete clonedRow[key]
|
||||||
}
|
}
|
||||||
|
// remove any AI values, they are to be generated
|
||||||
|
if (field.type === FieldType.AI) {
|
||||||
|
delete clonedRow[key]
|
||||||
|
}
|
||||||
// otherwise coerce what is there to correct types
|
// otherwise coerce what is there to correct types
|
||||||
else {
|
else {
|
||||||
clonedRow[key] = coerce(value, field.type)
|
clonedRow[key] = coerce(value, field.type)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import tracer from "dd-trace"
|
import tracer from "dd-trace"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
|
import * as pro from "@budibase/pro"
|
||||||
|
|
||||||
interface FormulaOpts {
|
interface FormulaOpts {
|
||||||
dynamic?: boolean
|
dynamic?: boolean
|
||||||
|
@ -91,6 +92,56 @@ export async function processFormulas<T extends Row | Row[]>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks through the rows provided and finds AI columns - which it then processes.
|
||||||
|
*/
|
||||||
|
export async function processAIColumns<T extends Row | Row[]>(
|
||||||
|
table: Table,
|
||||||
|
inputRows: T,
|
||||||
|
{ contextRows }: FormulaOpts
|
||||||
|
): Promise<T> {
|
||||||
|
return tracer.trace("processAIColumns", {}, async span => {
|
||||||
|
const numRows = Array.isArray(inputRows) ? inputRows.length : 1
|
||||||
|
span?.addTags({ table_id: table._id })
|
||||||
|
const rows = Array.isArray(inputRows) ? inputRows : [inputRows]
|
||||||
|
if (rows) {
|
||||||
|
// Ensure we have snippet context
|
||||||
|
await context.ensureSnippetContext()
|
||||||
|
|
||||||
|
for (let [column, schema] of Object.entries(table.schema)) {
|
||||||
|
if (schema.type !== FieldType.AI) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// const llm = pro.ai.LargeLanguageModel()
|
||||||
|
// if (
|
||||||
|
// schema.formula == null ||
|
||||||
|
// (dynamic && isStatic) ||
|
||||||
|
// (!dynamic && !isStatic)
|
||||||
|
// ) {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// iterate through rows and process formula
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
let row = rows[i]
|
||||||
|
// let context = contextRows ? contextRows[i] : row
|
||||||
|
// let formula = schema.prompt
|
||||||
|
rows[i] = {
|
||||||
|
...row,
|
||||||
|
[column]: tracer.trace("processAIColumn", {}, span => {
|
||||||
|
span?.addTags({ table_id: table._id, column })
|
||||||
|
// return processStringSync(formula, context)
|
||||||
|
// TODO: Add the AI stuff in to this
|
||||||
|
return "YEET AI"
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.isArray(inputRows) ? rows : rows[0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes any date columns and ensures that those without the ignoreTimezones
|
* Processes any date columns and ensures that those without the ignoreTimezones
|
||||||
* flag set are parsed as UTC rather than local time.
|
* flag set are parsed as UTC rather than local time.
|
||||||
|
|
|
@ -87,6 +87,8 @@ export const getValidOperatorsForType = (
|
||||||
ops = numOps
|
ops = numOps
|
||||||
} else if (type === FieldType.FORMULA && formulaType === FormulaType.STATIC) {
|
} else if (type === FieldType.FORMULA && formulaType === FormulaType.STATIC) {
|
||||||
ops = stringOps.concat([Op.MoreThan, Op.LessThan])
|
ops = stringOps.concat([Op.MoreThan, Op.LessThan])
|
||||||
|
} else if (type === FieldType.AI) {
|
||||||
|
ops = stringOps.concat([Op.MoreThan, Op.LessThan])
|
||||||
} else if (
|
} else if (
|
||||||
type === FieldType.BB_REFERENCE_SINGLE ||
|
type === FieldType.BB_REFERENCE_SINGLE ||
|
||||||
schema.isDeprecatedSingleUserColumn(fieldType)
|
schema.isDeprecatedSingleUserColumn(fieldType)
|
||||||
|
|
|
@ -8,6 +8,7 @@ const allowDisplayColumnByType: Record<FieldType, boolean> = {
|
||||||
[FieldType.NUMBER]: true,
|
[FieldType.NUMBER]: true,
|
||||||
[FieldType.DATETIME]: true,
|
[FieldType.DATETIME]: true,
|
||||||
[FieldType.FORMULA]: true,
|
[FieldType.FORMULA]: true,
|
||||||
|
[FieldType.AI]: true,
|
||||||
[FieldType.AUTO]: true,
|
[FieldType.AUTO]: true,
|
||||||
[FieldType.INTERNAL]: true,
|
[FieldType.INTERNAL]: true,
|
||||||
[FieldType.BARCODEQR]: true,
|
[FieldType.BARCODEQR]: true,
|
||||||
|
@ -38,6 +39,7 @@ const allowSortColumnByType: Record<FieldType, boolean> = {
|
||||||
[FieldType.JSON]: true,
|
[FieldType.JSON]: true,
|
||||||
|
|
||||||
[FieldType.FORMULA]: false,
|
[FieldType.FORMULA]: false,
|
||||||
|
[FieldType.AI]: false,
|
||||||
[FieldType.ATTACHMENTS]: false,
|
[FieldType.ATTACHMENTS]: false,
|
||||||
[FieldType.ATTACHMENT_SINGLE]: false,
|
[FieldType.ATTACHMENT_SINGLE]: false,
|
||||||
[FieldType.SIGNATURE_SINGLE]: false,
|
[FieldType.SIGNATURE_SINGLE]: false,
|
||||||
|
@ -61,6 +63,7 @@ const allowDefaultColumnByType: Record<FieldType, boolean> = {
|
||||||
[FieldType.BIGINT]: false,
|
[FieldType.BIGINT]: false,
|
||||||
[FieldType.BOOLEAN]: false,
|
[FieldType.BOOLEAN]: false,
|
||||||
[FieldType.FORMULA]: false,
|
[FieldType.FORMULA]: false,
|
||||||
|
[FieldType.AI]: false,
|
||||||
[FieldType.ATTACHMENTS]: false,
|
[FieldType.ATTACHMENTS]: false,
|
||||||
[FieldType.ATTACHMENT_SINGLE]: false,
|
[FieldType.ATTACHMENT_SINGLE]: false,
|
||||||
[FieldType.SIGNATURE_SINGLE]: false,
|
[FieldType.SIGNATURE_SINGLE]: false,
|
||||||
|
|
|
@ -76,6 +76,10 @@ export enum FieldType {
|
||||||
* that is part of the initial formula definition, the formula will be live evaluated in the browser.
|
* that is part of the initial formula definition, the formula will be live evaluated in the browser.
|
||||||
*/
|
*/
|
||||||
AUTO = "auto",
|
AUTO = "auto",
|
||||||
|
/**
|
||||||
|
* A complex type, called an AI column within Budibase. This type has a... TODO: fill out
|
||||||
|
*/
|
||||||
|
AI = "ai",
|
||||||
/**
|
/**
|
||||||
* a JSON type, called JSON within Budibase. This type allows any arbitrary JSON to be input to this column
|
* a JSON type, called JSON within Budibase. This type allows any arbitrary JSON to be input to this column
|
||||||
* type, which will be represented as a JSON object in the row. This type depends on a schema being
|
* type, which will be represented as a JSON object in the row. This type depends on a schema being
|
||||||
|
|
|
@ -30,6 +30,7 @@ export enum JsonFieldSubType {
|
||||||
export enum FormulaType {
|
export enum FormulaType {
|
||||||
STATIC = "static",
|
STATIC = "static",
|
||||||
DYNAMIC = "dynamic",
|
DYNAMIC = "dynamic",
|
||||||
|
AI = "ai"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BBReferenceFieldSubType {
|
export enum BBReferenceFieldSubType {
|
||||||
|
|
|
@ -116,6 +116,17 @@ export interface FormulaFieldMetadata extends BaseFieldSchema {
|
||||||
formulaType?: FormulaType
|
formulaType?: FormulaType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AIFieldMetadata extends BaseFieldSchema {
|
||||||
|
type: FieldType.AI
|
||||||
|
formula: string
|
||||||
|
// TODO: needs better types
|
||||||
|
operation: string
|
||||||
|
columns?: string[]
|
||||||
|
column?: string
|
||||||
|
prompt?: string
|
||||||
|
language?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface BBReferenceFieldMetadata
|
export interface BBReferenceFieldMetadata
|
||||||
extends Omit<BaseFieldSchema, "subtype"> {
|
extends Omit<BaseFieldSchema, "subtype"> {
|
||||||
type: FieldType.BB_REFERENCE
|
type: FieldType.BB_REFERENCE
|
||||||
|
@ -190,6 +201,7 @@ interface OtherFieldMetadata extends BaseFieldSchema {
|
||||||
| FieldType.LINK
|
| FieldType.LINK
|
||||||
| FieldType.AUTO
|
| FieldType.AUTO
|
||||||
| FieldType.FORMULA
|
| FieldType.FORMULA
|
||||||
|
| FieldType.AI
|
||||||
| FieldType.NUMBER
|
| FieldType.NUMBER
|
||||||
| FieldType.LONGFORM
|
| FieldType.LONGFORM
|
||||||
| FieldType.BB_REFERENCE
|
| FieldType.BB_REFERENCE
|
||||||
|
@ -207,6 +219,7 @@ export type FieldSchema =
|
||||||
| RelationshipFieldMetadata
|
| RelationshipFieldMetadata
|
||||||
| AutoColumnFieldMetadata
|
| AutoColumnFieldMetadata
|
||||||
| FormulaFieldMetadata
|
| FormulaFieldMetadata
|
||||||
|
| AIFieldMetadata
|
||||||
| NumberFieldMetadata
|
| NumberFieldMetadata
|
||||||
| LongFormFieldMetadata
|
| LongFormFieldMetadata
|
||||||
| StringFieldMetadata
|
| StringFieldMetadata
|
||||||
|
|
Loading…
Reference in New Issue