✨ Add NocoDB block (#1365)
#970 #997 Fully integrate NocoDB. Added all API Functions: - List Table Records - Create Table Records - Update Table Records - Delete Table Records - Read Table Record - Count Table Records - List Linked Records - Link Records - Unlink Records Optional Todo: - Save responses of non-get requests in a variable (error validation try-catch is added and logged so i do not think so it is much needed) You are free to implement any extra validation/function :D --------- Co-authored-by: Baptiste Arnaud <baptiste.arnaud95@gmail.com>
This commit is contained in:
committed by
GitHub
parent
3e4e7531f6
commit
a17781dfa6
@ -6,12 +6,14 @@ import { ZodObjectLayout } from './zodLayouts/ZodObjectLayout'
|
||||
import { ZodActionDiscriminatedUnion } from './zodLayouts/ZodActionDiscriminatedUnion'
|
||||
import { useForgedBlock } from '../hooks/useForgedBlock'
|
||||
import { ForgedBlock } from '@typebot.io/forge-repository/types'
|
||||
import { useState } from 'react'
|
||||
|
||||
type Props = {
|
||||
block: ForgedBlock
|
||||
onOptionsChange: (options: BlockOptions) => void
|
||||
}
|
||||
export const ForgedBlockSettings = ({ block, onOptionsChange }: Props) => {
|
||||
const [keySuffix, setKeySuffix] = useState<number>(0)
|
||||
const { blockDef, blockSchema, actionDef } = useForgedBlock(
|
||||
block.type,
|
||||
block.options?.action
|
||||
@ -32,7 +34,10 @@ export const ForgedBlockSettings = ({ block, onOptionsChange }: Props) => {
|
||||
const actionOptions = actionOptionsKeys.reduce(
|
||||
(acc, key) => ({
|
||||
...acc,
|
||||
[key]: undefined,
|
||||
[key]:
|
||||
block.options[key] && typeof block.options[key] !== 'object'
|
||||
? block.options[key]
|
||||
: undefined,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
@ -40,6 +45,7 @@ export const ForgedBlockSettings = ({ block, onOptionsChange }: Props) => {
|
||||
...updates,
|
||||
...actionOptions,
|
||||
})
|
||||
setKeySuffix((prev) => prev + 1)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -85,6 +91,7 @@ export const ForgedBlockSettings = ({ block, onOptionsChange }: Props) => {
|
||||
/>
|
||||
)}
|
||||
<ZodActionDiscriminatedUnion
|
||||
key={block.id + keySuffix}
|
||||
schema={blockSchema.shape.options}
|
||||
blockDef={blockDef}
|
||||
blockOptions={block.options}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { NumberInput, TextInput, Textarea } from '@/components/inputs'
|
||||
import { z } from '@typebot.io/forge/zod'
|
||||
import { ZodLayoutMetadata } from '@typebot.io/forge/zod'
|
||||
import { evaluateIsHidden } from '@typebot.io/forge/zod/helpers/evaluateIsHidden'
|
||||
import Markdown, { Components } from 'react-markdown'
|
||||
import { ZodTypeAny } from 'zod'
|
||||
import { ForgeSelectInput } from '../ForgeSelectInput'
|
||||
@ -64,7 +65,7 @@ export const ZodFieldLayout = ({
|
||||
const innerSchema = getZodInnerSchema(schema)
|
||||
const layout = innerSchema._def.layout
|
||||
|
||||
if (layout?.isHidden) return null
|
||||
if (evaluateIsHidden(layout?.isHidden, blockOptions)) return null
|
||||
|
||||
switch (innerSchema._def.typeName) {
|
||||
case 'ZodObject':
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
ForgedBlock,
|
||||
} from '@typebot.io/forge-repository/types'
|
||||
import { getZodInnerSchema } from '../../helpers/getZodInnerSchema'
|
||||
import { evaluateIsHidden } from '@typebot.io/forge/zod/helpers/evaluateIsHidden'
|
||||
|
||||
export const ZodObjectLayout = ({
|
||||
schema,
|
||||
@ -37,20 +38,23 @@ export const ZodObjectLayout = ({
|
||||
blockOptions?: ForgedBlock['options']
|
||||
onDataChange: (value: any) => void
|
||||
}): ReactNode[] => {
|
||||
const layout = getZodInnerSchema(schema)._def.layout
|
||||
if (layout?.isHidden) return []
|
||||
return Object.keys(schema.shape).reduce<{
|
||||
const innerSchema = getZodInnerSchema(schema)
|
||||
const shape =
|
||||
'shape' in innerSchema ? innerSchema.shape : innerSchema._def.shape()
|
||||
const layout = innerSchema._def.layout
|
||||
if (evaluateIsHidden(layout?.isHidden, blockOptions)) return []
|
||||
return Object.keys(shape).reduce<{
|
||||
nodes: ReactNode[]
|
||||
accordionsCreated: string[]
|
||||
}>(
|
||||
(nodes, key, index) => {
|
||||
if (ignoreKeys?.includes(key)) return nodes
|
||||
const keySchema = getZodInnerSchema(schema.shape[key])
|
||||
const keySchema = getZodInnerSchema(shape[key])
|
||||
const layout = keySchema._def.layout as
|
||||
| ZodLayoutMetadata<ZodTypeAny>
|
||||
| undefined
|
||||
|
||||
if (layout?.isHidden) return nodes
|
||||
if (evaluateIsHidden(layout?.isHidden, blockOptions)) return nodes
|
||||
if (
|
||||
layout &&
|
||||
layout.accordion &&
|
||||
@ -60,7 +64,7 @@ export const ZodObjectLayout = ({
|
||||
if (nodes.accordionsCreated.includes(layout.accordion)) return nodes
|
||||
const accordionKeys = getObjectKeysWithSameAccordionAttr(
|
||||
layout.accordion,
|
||||
schema
|
||||
shape
|
||||
)
|
||||
return {
|
||||
nodes: [
|
||||
@ -77,7 +81,7 @@ export const ZodObjectLayout = ({
|
||||
{accordionKeys.map((accordionKey, idx) => (
|
||||
<ZodFieldLayout
|
||||
key={accordionKey + idx}
|
||||
schema={schema.shape[accordionKey]}
|
||||
schema={shape[accordionKey]}
|
||||
data={data?.[accordionKey]}
|
||||
onDataChange={(val) =>
|
||||
onDataChange({ ...data, [accordionKey]: val })
|
||||
@ -118,12 +122,9 @@ export const ZodObjectLayout = ({
|
||||
).nodes
|
||||
}
|
||||
|
||||
const getObjectKeysWithSameAccordionAttr = (
|
||||
accordion: string,
|
||||
schema: z.ZodObject<any>
|
||||
) =>
|
||||
Object.keys(schema.shape).reduce<string[]>((keys, currentKey) => {
|
||||
const l = schema.shape[currentKey]._def.layout as
|
||||
const getObjectKeysWithSameAccordionAttr = (accordion: string, shape: any) =>
|
||||
Object.keys(shape).reduce<string[]>((keys, currentKey) => {
|
||||
const l = shape[currentKey]._def.layout as
|
||||
| ZodLayoutMetadata<ZodTypeAny>
|
||||
| undefined
|
||||
return !l?.accordion || l.accordion !== accordion
|
||||
|
@ -2,11 +2,6 @@
|
||||
title: Anthropic
|
||||
---
|
||||
|
||||
<Warning>
|
||||
There is an ongoing issue with Anthropic block streaming capabilities. We are
|
||||
working on a fix and will update this page once the issue is resolved.
|
||||
</Warning>
|
||||
|
||||
## Create Message
|
||||
|
||||
With the Anthropic block, you can create chat messages based on your user queries and display the answer back to your typebot using Claude AI.
|
||||
|
31
apps/docs/editor/blocks/integrations/nocodb.mdx
Normal file
31
apps/docs/editor/blocks/integrations/nocodb.mdx
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
title: NocoDB
|
||||
---
|
||||
|
||||
With the NocoDB block, you can create, update or get data from your NocoDB tables.
|
||||
|
||||
## How to find my `Table ID`?
|
||||
|
||||
To find your `Table ID`, you need to go to your NocoDB dashboard and click on the 3 dots button next to your table name.
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="/images/blocks/integrations/nocodb-table-id.jpg"
|
||||
alt="NocoDB table ID"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Search Records
|
||||
|
||||
This action allows you to search for existing records in a table. It requires your `Table ID` and can optionally take a `View ID` to search in a specific view.
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="/images/blocks/integrations/nocodb.jpg"
|
||||
alt="NocoDB block example"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
You can configure the filter to return `All`, `First`, `Last` or `Random` found records.
|
||||
|
||||
Then all you need to do is to map the found fields to variables that you can re-use on your bot.
|
BIN
apps/docs/images/blocks/integrations/nocodb-table-id.jpg
Normal file
BIN
apps/docs/images/blocks/integrations/nocodb-table-id.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
BIN
apps/docs/images/blocks/integrations/nocodb.jpg
Normal file
BIN
apps/docs/images/blocks/integrations/nocodb.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
@ -126,7 +126,8 @@
|
||||
"editor/blocks/integrations/mistral",
|
||||
"editor/blocks/integrations/elevenlabs",
|
||||
"editor/blocks/integrations/anthropic",
|
||||
"editor/blocks/integrations/dify-ai"
|
||||
"editor/blocks/integrations/dify-ai",
|
||||
"editor/blocks/integrations/nocodb"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -20532,6 +20532,242 @@
|
||||
"id",
|
||||
"type"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"outgoingEdgeId": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"nocodb"
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentialsId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentialsId": {
|
||||
"type": "string"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Search Records"
|
||||
]
|
||||
},
|
||||
"tableId": {
|
||||
"type": "string"
|
||||
},
|
||||
"viewId": {
|
||||
"type": "string"
|
||||
},
|
||||
"returnType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"All",
|
||||
"First",
|
||||
"Last",
|
||||
"Random"
|
||||
]
|
||||
},
|
||||
"filter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"comparisons": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Equal to",
|
||||
"Not equal",
|
||||
"Contains",
|
||||
"Greater than",
|
||||
"Less than",
|
||||
"Is set",
|
||||
"Is empty",
|
||||
"Starts with",
|
||||
"Ends with"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"joiner": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"AND",
|
||||
"OR"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"comparisons"
|
||||
]
|
||||
},
|
||||
"responseMapping": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fieldName": {
|
||||
"type": "string"
|
||||
},
|
||||
"variableId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentialsId": {
|
||||
"type": "string"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Create Record"
|
||||
]
|
||||
},
|
||||
"tableId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentialsId": {
|
||||
"type": "string"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Update Existing Record"
|
||||
]
|
||||
},
|
||||
"tableId": {
|
||||
"type": "string"
|
||||
},
|
||||
"viewId": {
|
||||
"type": "string"
|
||||
},
|
||||
"filter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"comparisons": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Equal to",
|
||||
"Not equal",
|
||||
"Contains",
|
||||
"Greater than",
|
||||
"Less than",
|
||||
"Is set",
|
||||
"Is empty",
|
||||
"Starts with",
|
||||
"Ends with"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"joiner": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"AND",
|
||||
"OR"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"comparisons"
|
||||
]
|
||||
},
|
||||
"updates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fieldName": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"type"
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "Block"
|
||||
|
@ -11513,6 +11513,242 @@
|
||||
"id",
|
||||
"type"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"outgoingEdgeId": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"nocodb"
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentialsId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentialsId": {
|
||||
"type": "string"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Search Records"
|
||||
]
|
||||
},
|
||||
"tableId": {
|
||||
"type": "string"
|
||||
},
|
||||
"viewId": {
|
||||
"type": "string"
|
||||
},
|
||||
"returnType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"All",
|
||||
"First",
|
||||
"Last",
|
||||
"Random"
|
||||
]
|
||||
},
|
||||
"filter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"comparisons": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Equal to",
|
||||
"Not equal",
|
||||
"Contains",
|
||||
"Greater than",
|
||||
"Less than",
|
||||
"Is set",
|
||||
"Is empty",
|
||||
"Starts with",
|
||||
"Ends with"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"joiner": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"AND",
|
||||
"OR"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"comparisons"
|
||||
]
|
||||
},
|
||||
"responseMapping": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fieldName": {
|
||||
"type": "string"
|
||||
},
|
||||
"variableId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentialsId": {
|
||||
"type": "string"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Create Record"
|
||||
]
|
||||
},
|
||||
"tableId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentialsId": {
|
||||
"type": "string"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Update Existing Record"
|
||||
]
|
||||
},
|
||||
"tableId": {
|
||||
"type": "string"
|
||||
},
|
||||
"viewId": {
|
||||
"type": "string"
|
||||
},
|
||||
"filter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"comparisons": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Equal to",
|
||||
"Not equal",
|
||||
"Contains",
|
||||
"Greater than",
|
||||
"Less than",
|
||||
"Is set",
|
||||
"Is empty",
|
||||
"Starts with",
|
||||
"Ends with"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"joiner": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"AND",
|
||||
"OR"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"comparisons"
|
||||
]
|
||||
},
|
||||
"updates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fieldName": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"type"
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "Block"
|
||||
|
@ -29,9 +29,20 @@ export const createChatMessage = createAction({
|
||||
moreInfoTooltip:
|
||||
'The user identifier, defined by the developer, must ensure uniqueness within the app.',
|
||||
}),
|
||||
inputs: option.keyValueList.layout({
|
||||
accordion: 'Inputs',
|
||||
}),
|
||||
inputs: option
|
||||
.array(
|
||||
option.object({
|
||||
key: option.string.layout({
|
||||
label: 'Key',
|
||||
}),
|
||||
value: option.string.layout({
|
||||
label: 'Value',
|
||||
}),
|
||||
})
|
||||
)
|
||||
.layout({
|
||||
accordion: 'Inputs',
|
||||
}),
|
||||
responseMapping: option
|
||||
.saveResponseArray(
|
||||
['Answer', 'Conversation ID', 'Total Tokens'] as const,
|
||||
|
61
packages/forge/blocks/nocodb/actions/createRecord.ts
Normal file
61
packages/forge/blocks/nocodb/actions/createRecord.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { createAction, option } from '@typebot.io/forge'
|
||||
import { auth } from '../auth'
|
||||
import ky, { HTTPError } from 'ky'
|
||||
import { defaultBaseUrl } from '../constants'
|
||||
import { parseRecordsCreateBody } from '../helpers/parseRecordCreateBody'
|
||||
|
||||
export const createRecord = createAction({
|
||||
auth,
|
||||
name: 'Create Record',
|
||||
options: option.object({
|
||||
tableId: option.string.layout({
|
||||
label: 'Table ID',
|
||||
isRequired: true,
|
||||
helperText: 'Identifier of the table to create records in.',
|
||||
}),
|
||||
fields: option
|
||||
.array(
|
||||
option.object({
|
||||
key: option.string.layout({
|
||||
label: 'Field',
|
||||
isRequired: true,
|
||||
}),
|
||||
value: option.string.layout({
|
||||
label: 'Value',
|
||||
isRequired: true,
|
||||
}),
|
||||
})
|
||||
)
|
||||
.layout({
|
||||
itemLabel: 'field',
|
||||
}),
|
||||
}),
|
||||
run: {
|
||||
server: async ({
|
||||
credentials: { baseUrl, apiKey },
|
||||
options: { tableId, fields },
|
||||
logs,
|
||||
}) => {
|
||||
try {
|
||||
if (!fields || fields.length === 0) return
|
||||
await ky.post(
|
||||
`${baseUrl ?? defaultBaseUrl}/api/v2/tables/${tableId}/records`,
|
||||
{
|
||||
headers: {
|
||||
'xc-token': apiKey,
|
||||
},
|
||||
json: parseRecordsCreateBody(fields),
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
if (error instanceof HTTPError)
|
||||
return logs.add({
|
||||
status: 'error',
|
||||
description: error.message,
|
||||
details: await error.response.text(),
|
||||
})
|
||||
console.error(error)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
136
packages/forge/blocks/nocodb/actions/searchRecords.ts
Normal file
136
packages/forge/blocks/nocodb/actions/searchRecords.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import { createAction, option } from '@typebot.io/forge'
|
||||
import { auth } from '../auth'
|
||||
import ky, { HTTPError } from 'ky'
|
||||
import { ListTableRecordsResponse } from '../types'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { parseSearchParams } from '../helpers/parseSearchParams'
|
||||
import { convertFilterToWhereClause } from '../helpers/convertFilterToWhereClause'
|
||||
import {
|
||||
defaultBaseUrl,
|
||||
defaultLimitForSearch,
|
||||
filterOperators,
|
||||
} from '../constants'
|
||||
|
||||
export const searchRecords = createAction({
|
||||
auth,
|
||||
name: 'Search Records',
|
||||
options: option.object({
|
||||
tableId: option.string.layout({
|
||||
label: 'Table ID',
|
||||
moreInfoTooltip:
|
||||
'Can be found by clicking on the 3 dots next to the table name.',
|
||||
isRequired: true,
|
||||
}),
|
||||
viewId: option.string.layout({
|
||||
label: 'View ID',
|
||||
moreInfoTooltip:
|
||||
'Can be found by clicking on the 3 dots next to the view name.',
|
||||
}),
|
||||
returnType: option.enum(['All', 'First', 'Last', 'Random']).layout({
|
||||
accordion: 'Filter',
|
||||
defaultValue: 'All',
|
||||
}),
|
||||
filter: option
|
||||
.filter({
|
||||
operators: filterOperators,
|
||||
isJoinerHidden: ({ filter }) =>
|
||||
!filter?.comparisons || filter.comparisons.length < 2,
|
||||
})
|
||||
.layout({
|
||||
accordion: 'Filter',
|
||||
}),
|
||||
responseMapping: option
|
||||
.array(
|
||||
option.object({
|
||||
fieldName: option.string.layout({
|
||||
label: 'Enter a field name',
|
||||
}),
|
||||
variableId: option.string.layout({
|
||||
inputType: 'variableDropdown',
|
||||
}),
|
||||
})
|
||||
)
|
||||
.layout({
|
||||
accordion: 'Response Mapping',
|
||||
}),
|
||||
}),
|
||||
getSetVariableIds: ({ responseMapping }) =>
|
||||
responseMapping?.map((r) => r.variableId).filter(isDefined) ?? [],
|
||||
run: {
|
||||
server: async ({
|
||||
credentials: { baseUrl, apiKey },
|
||||
options: { tableId, responseMapping, filter, returnType, viewId },
|
||||
variables,
|
||||
logs,
|
||||
}) => {
|
||||
if (!apiKey) return logs.add('API key is required')
|
||||
try {
|
||||
const data = await ky
|
||||
.get(
|
||||
`${baseUrl ?? defaultBaseUrl}/api/v2/tables/${tableId}/records`,
|
||||
{
|
||||
headers: {
|
||||
'xc-token': apiKey,
|
||||
},
|
||||
searchParams: parseSearchParams({
|
||||
where: convertFilterToWhereClause(filter),
|
||||
viewId,
|
||||
limit: defaultLimitForSearch,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.json<ListTableRecordsResponse>()
|
||||
|
||||
let filterIndex: number | undefined = undefined
|
||||
|
||||
if (returnType && returnType !== 'All') {
|
||||
const total = data.pageInfo.totalRows
|
||||
if (returnType === 'First') {
|
||||
filterIndex = 0
|
||||
} else if (returnType === 'Last') {
|
||||
filterIndex = total - 1
|
||||
} else if (returnType === 'Random') {
|
||||
filterIndex = Math.floor(Math.random() * total)
|
||||
}
|
||||
}
|
||||
|
||||
const filteredList =
|
||||
isDefined(filterIndex) && data.list[filterIndex]
|
||||
? [data.list[filterIndex]]
|
||||
: data.list
|
||||
|
||||
if (filteredList.length === 0)
|
||||
return logs.add({
|
||||
status: 'info',
|
||||
description: `Couldn't find any rows matching the filter`,
|
||||
details: JSON.stringify(filter, null, 2),
|
||||
})
|
||||
|
||||
responseMapping?.forEach((mapping) => {
|
||||
if (!mapping.fieldName || !mapping.variableId) return
|
||||
if (!filteredList[0][mapping.fieldName]) {
|
||||
logs.add(`Field ${mapping.fieldName} does not exist in the table`)
|
||||
return
|
||||
}
|
||||
|
||||
const items = filteredList.map(
|
||||
(item) => item[mapping.fieldName as string]
|
||||
)
|
||||
|
||||
variables.set(
|
||||
mapping.variableId,
|
||||
items.length === 1 ? items[0] : items
|
||||
)
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof HTTPError)
|
||||
return logs.add({
|
||||
status: 'error',
|
||||
description: error.message,
|
||||
details: await error.response.text(),
|
||||
})
|
||||
console.error(error)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
103
packages/forge/blocks/nocodb/actions/updateExistingRecord.ts
Normal file
103
packages/forge/blocks/nocodb/actions/updateExistingRecord.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { createAction, option } from '@typebot.io/forge'
|
||||
import { auth } from '../auth'
|
||||
import ky, { HTTPError } from 'ky'
|
||||
import { parseRecordsUpdateBody } from '../helpers/parseRecordsUpdateBody'
|
||||
import {
|
||||
defaultBaseUrl,
|
||||
defaultLimitForSearch,
|
||||
filterOperators,
|
||||
} from '../constants'
|
||||
import { parseSearchParams } from '../helpers/parseSearchParams'
|
||||
import { convertFilterToWhereClause } from '../helpers/convertFilterToWhereClause'
|
||||
import { ListTableRecordsResponse } from '../types'
|
||||
|
||||
export const updateExistingRecord = createAction({
|
||||
auth,
|
||||
name: 'Update Existing Record',
|
||||
options: option.object({
|
||||
tableId: option.string.layout({
|
||||
label: 'Table ID',
|
||||
isRequired: true,
|
||||
moreInfoTooltip:
|
||||
'Can be found by clicking on the 3 dots next to the table name.',
|
||||
}),
|
||||
viewId: option.string.layout({
|
||||
label: 'View ID',
|
||||
moreInfoTooltip:
|
||||
'Can be found by clicking on the 3 dots next to the view name.',
|
||||
}),
|
||||
filter: option
|
||||
.filter({
|
||||
operators: filterOperators,
|
||||
isJoinerHidden: ({ filter }) =>
|
||||
!filter?.comparisons || filter.comparisons.length < 2,
|
||||
})
|
||||
.layout({
|
||||
accordion: 'Select Records',
|
||||
}),
|
||||
updates: option
|
||||
.array(
|
||||
option.object({
|
||||
fieldName: option.string.layout({
|
||||
label: 'Enter a field name',
|
||||
}),
|
||||
value: option.string.layout({
|
||||
placeholder: 'Enter a value',
|
||||
}),
|
||||
})
|
||||
)
|
||||
.layout({
|
||||
accordion: 'Updates',
|
||||
}),
|
||||
}),
|
||||
run: {
|
||||
server: async ({
|
||||
credentials: { baseUrl, apiKey },
|
||||
options: { tableId, filter, viewId, updates },
|
||||
logs,
|
||||
}) => {
|
||||
if (!apiKey) return logs.add('API key is required')
|
||||
if (!updates) return logs.add('At least one update is required')
|
||||
if (!filter?.comparisons || filter.comparisons.length === 0)
|
||||
return logs.add('At least one filter is required')
|
||||
try {
|
||||
const listData = await ky
|
||||
.get(
|
||||
`${baseUrl ?? defaultBaseUrl}/api/v2/tables/${tableId}/records`,
|
||||
{
|
||||
headers: {
|
||||
'xc-token': apiKey,
|
||||
},
|
||||
searchParams: parseSearchParams({
|
||||
where: convertFilterToWhereClause(filter),
|
||||
viewId,
|
||||
limit: defaultLimitForSearch,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.json<ListTableRecordsResponse>()
|
||||
|
||||
await ky.patch(
|
||||
`${baseUrl ?? defaultBaseUrl}/api/v2/tables/${tableId}/records`,
|
||||
{
|
||||
headers: {
|
||||
'xc-token': apiKey,
|
||||
},
|
||||
json: parseRecordsUpdateBody(
|
||||
listData.list.map((item) => item.Id),
|
||||
updates
|
||||
),
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
if (error instanceof HTTPError)
|
||||
return logs.add({
|
||||
status: 'error',
|
||||
description: error.message,
|
||||
details: await error.response.text(),
|
||||
})
|
||||
console.error(error)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
24
packages/forge/blocks/nocodb/auth.ts
Normal file
24
packages/forge/blocks/nocodb/auth.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { option, AuthDefinition } from '@typebot.io/forge'
|
||||
import { defaultBaseUrl } from './constants'
|
||||
|
||||
export const auth = {
|
||||
type: 'encryptedCredentials',
|
||||
name: 'NocoDB account',
|
||||
schema: option.object({
|
||||
baseUrl: option.string.layout({
|
||||
label: 'Base URL',
|
||||
isRequired: true,
|
||||
helperText: 'Change it only if you are self-hosting NocoDB.',
|
||||
withVariableButton: false,
|
||||
defaultValue: defaultBaseUrl,
|
||||
}),
|
||||
apiKey: option.string.layout({
|
||||
label: 'API Token',
|
||||
isRequired: true,
|
||||
helperText:
|
||||
'You can generate an API token [here](https://app.nocodb.com/#/account/tokens)',
|
||||
inputType: 'password',
|
||||
withVariableButton: false,
|
||||
}),
|
||||
}),
|
||||
} satisfies AuthDefinition
|
15
packages/forge/blocks/nocodb/constants.ts
Normal file
15
packages/forge/blocks/nocodb/constants.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export const defaultBaseUrl = 'https://app.nocodb.com'
|
||||
|
||||
export const filterOperators = [
|
||||
'Equal to',
|
||||
'Not equal',
|
||||
'Contains',
|
||||
'Greater than',
|
||||
'Less than',
|
||||
'Is set',
|
||||
'Is empty',
|
||||
'Starts with',
|
||||
'Ends with',
|
||||
] as const
|
||||
|
||||
export const defaultLimitForSearch = 1000
|
@ -0,0 +1,50 @@
|
||||
// See `where`: https://docs.nocodb.com/0.109.7/developer-resources/rest-apis/#query-params
|
||||
// Example: (colName,eq,colValue)~or(colName2,gt,colValue2)
|
||||
|
||||
import { isEmpty } from '@typebot.io/lib'
|
||||
|
||||
export const convertFilterToWhereClause = (
|
||||
filter:
|
||||
| {
|
||||
comparisons: {
|
||||
input?: string
|
||||
operator?: string
|
||||
value?: string
|
||||
}[]
|
||||
joiner?: 'AND' | 'OR'
|
||||
}
|
||||
| undefined
|
||||
): string | undefined => {
|
||||
if (!filter || !filter.comparisons || filter.comparisons.length === 0) return
|
||||
|
||||
const where = filter.comparisons
|
||||
.map((comparison) => {
|
||||
if (!comparison.value) return ''
|
||||
|
||||
switch (comparison.operator) {
|
||||
case 'Not equal':
|
||||
return `(${comparison.input},ne,${comparison.value})`
|
||||
case 'Contains':
|
||||
return `(${comparison.input},like,%${comparison.value}%)`
|
||||
case 'Greater than':
|
||||
return `(${comparison.input},gt,${comparison.value})`
|
||||
case 'Less than':
|
||||
return `(${comparison.input},lt,${comparison.value})`
|
||||
case 'Is set':
|
||||
return `(${comparison.input},isnot,null)`
|
||||
case 'Is empty':
|
||||
return `(${comparison.input},is,null)`
|
||||
case 'Starts with':
|
||||
return `(${comparison.input},like,${comparison.value}%)`
|
||||
case 'Ends with':
|
||||
return `(${comparison.input},like,%${comparison.value})`
|
||||
default:
|
||||
return `(${comparison.input},eq,${comparison.value})`
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('~' + (filter.joiner === 'OR' ? 'or' : 'and'))
|
||||
|
||||
if (isEmpty(where)) return
|
||||
return where
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
export const parseRecordsCreateBody = (
|
||||
fields: { key?: string; value?: string }[]
|
||||
): Record<string, any> => {
|
||||
const record: Record<string, any> = {}
|
||||
|
||||
fields.forEach(({ key, value }) => {
|
||||
if (!key || !value) return
|
||||
record[key] = value
|
||||
})
|
||||
|
||||
return record
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
export const parseRecordsUpdateBody = (
|
||||
ids: string[],
|
||||
updates: { fieldName?: string; value?: string }[]
|
||||
): Record<string, any>[] =>
|
||||
ids.map((id) => {
|
||||
const record: Record<string, any> = {
|
||||
Id: id,
|
||||
}
|
||||
|
||||
updates.forEach(({ fieldName, value }) => {
|
||||
if (!fieldName) return
|
||||
record[fieldName] = value ?? null
|
||||
})
|
||||
|
||||
return record
|
||||
})
|
@ -0,0 +1,8 @@
|
||||
export const parseSearchParams = (
|
||||
records: Record<string, any>
|
||||
): Record<string, string> => {
|
||||
return Object.entries(records).reduce((acc, [key, value]) => {
|
||||
if (value === null || value === undefined) return acc
|
||||
return { ...acc, [key]: value.toString() }
|
||||
}, {})
|
||||
}
|
16
packages/forge/blocks/nocodb/index.ts
Normal file
16
packages/forge/blocks/nocodb/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { createBlock } from '@typebot.io/forge'
|
||||
import { NocodbLogo } from './logo'
|
||||
import { auth } from './auth'
|
||||
import { searchRecords } from './actions/searchRecords'
|
||||
import { createRecord } from './actions/createRecord'
|
||||
import { updateExistingRecord } from './actions/updateExistingRecord'
|
||||
|
||||
export const nocodbBlock = createBlock({
|
||||
id: 'nocodb',
|
||||
name: 'NocoDB',
|
||||
docsUrl: 'https://docs.typebot.io/forge/blocks/nocodb',
|
||||
tags: ['database'],
|
||||
LightLogo: NocodbLogo,
|
||||
auth,
|
||||
actions: [searchRecords, createRecord, updateExistingRecord],
|
||||
})
|
34
packages/forge/blocks/nocodb/logo.tsx
Normal file
34
packages/forge/blocks/nocodb/logo.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
/** @jsxImportSource react */
|
||||
|
||||
export const NocodbLogo = (props: React.SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
width="32"
|
||||
height="32"
|
||||
rx="4"
|
||||
fill="url(#paint0_linear_1871_109536)"
|
||||
></rect>
|
||||
<path
|
||||
d="M8.3335 15.1562L12.0047 18.8297V24.6454H8.3335V15.1562ZM23.7533 7.34649V24.0464C23.7533 24.3894 23.4738 24.6665 23.1309 24.6665C22.9665 24.6665 22.8092 24.6031 22.6917 24.4857L8.3335 11.5367V7.8726C8.3335 7.52968 8.61066 7.25253 8.95359 7.25253H8.98648C9.1509 7.25253 9.3106 7.31831 9.42569 7.4334L20.0798 16.6783V7.34649H23.7533Z"
|
||||
fill="white"
|
||||
></path>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_1871_109536"
|
||||
x1="15.9976"
|
||||
y1="42.7731"
|
||||
x2="15.9976"
|
||||
y2="-8.9707"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#4351E8"></stop>
|
||||
<stop offset="1" stopColor="#2A1EA5"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
18
packages/forge/blocks/nocodb/package.json
Normal file
18
packages/forge/blocks/nocodb/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@typebot.io/nocodb-block",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"keywords": [],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ky": "1.2.3"
|
||||
}
|
||||
}
|
6
packages/forge/blocks/nocodb/schemas.ts
Normal file
6
packages/forge/blocks/nocodb/schemas.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// Do not edit this file manually
|
||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||
import { nocodbBlock } from '.'
|
||||
|
||||
export const nocodbBlockSchema = parseBlockSchema(nocodbBlock)
|
||||
export const nocodbCredentialsSchema = parseBlockCredentials(nocodbBlock)
|
11
packages/forge/blocks/nocodb/tsconfig.json
Normal file
11
packages/forge/blocks/nocodb/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "@typebot.io/tsconfig/base.json",
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "react"
|
||||
}
|
||||
}
|
10
packages/forge/blocks/nocodb/types.ts
Normal file
10
packages/forge/blocks/nocodb/types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export type ListTableRecordsResponse = {
|
||||
list: Array<Record<string, any>>
|
||||
pageInfo: {
|
||||
totalRows: number
|
||||
page: number
|
||||
pageSize: number
|
||||
isFirstPage: boolean
|
||||
isLastPage: boolean
|
||||
}
|
||||
}
|
@ -110,18 +110,6 @@ export const option = {
|
||||
z.enum(values).optional(),
|
||||
number: z.number().or(variableStringSchema).optional(),
|
||||
array: <T extends z.ZodTypeAny>(schema: T) => z.array(schema).optional(),
|
||||
keyValueList: z
|
||||
.array(
|
||||
z.object({
|
||||
key: z.string().optional().layout({
|
||||
label: 'Key',
|
||||
}),
|
||||
value: z.string().optional().layout({
|
||||
label: 'Value',
|
||||
}),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
discriminatedUnion: <
|
||||
T extends string,
|
||||
J extends [
|
||||
@ -165,6 +153,49 @@ export const option = {
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
filter: ({
|
||||
operators = defaultFilterOperators,
|
||||
isJoinerHidden,
|
||||
}: {
|
||||
operators?: readonly [string, ...string[]]
|
||||
isJoinerHidden: (currentObj: Record<string, any>) => boolean
|
||||
}) =>
|
||||
z
|
||||
.object({
|
||||
comparisons: z.array(
|
||||
z.object({
|
||||
input: z.string().optional().layout({ label: 'Enter a field ' }),
|
||||
operator: z
|
||||
.enum(operators)
|
||||
.optional()
|
||||
.layout({ defaultValue: 'Equal to' }),
|
||||
value: z
|
||||
.string()
|
||||
.optional()
|
||||
.layout({ placeholder: 'Enter a value' }),
|
||||
})
|
||||
),
|
||||
joiner: z.enum(['AND', 'OR']).optional().layout({
|
||||
placeholder: 'Select joiner',
|
||||
isHidden: isJoinerHidden,
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
}
|
||||
|
||||
const defaultFilterOperators = [
|
||||
'Equal to',
|
||||
'Not equal',
|
||||
'Contains',
|
||||
'Does not contain',
|
||||
'Greater than',
|
||||
'Less than',
|
||||
'Is set',
|
||||
'Is empty',
|
||||
'Starts with',
|
||||
'Ends with',
|
||||
'Matches regex',
|
||||
'Does not match regex',
|
||||
] as const
|
||||
|
||||
export type * from './types'
|
||||
|
@ -20,7 +20,7 @@ export interface ZodLayoutMetadata<
|
||||
itemLabel?: T extends OptionableZodType<ZodArray<any>> ? string : never
|
||||
isOrdered?: T extends OptionableZodType<ZodArray<any>> ? boolean : never
|
||||
moreInfoTooltip?: string
|
||||
isHidden?: boolean
|
||||
isHidden?: boolean | ((currentObj: Record<string, any>) => boolean)
|
||||
isDebounceDisabled?: boolean
|
||||
hiddenItems?: string[]
|
||||
}
|
||||
|
9
packages/forge/core/zod/helpers/evaluateIsHidden.ts
Normal file
9
packages/forge/core/zod/helpers/evaluateIsHidden.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const evaluateIsHidden = (
|
||||
isHidden: boolean | ((obj: any) => boolean) | undefined,
|
||||
obj: any
|
||||
): boolean => {
|
||||
if (typeof isHidden === 'function') {
|
||||
return isHidden(obj)
|
||||
}
|
||||
return isHidden ?? false
|
||||
}
|
@ -13,4 +13,5 @@ export const forgedBlockIds = [
|
||||
'anthropic',
|
||||
'together-ai',
|
||||
'open-router',
|
||||
'nocodb',
|
||||
] as const satisfies ForgedBlock['type'][]
|
||||
|
@ -20,6 +20,8 @@ import { togetherAiBlock } from '@typebot.io/together-ai-block'
|
||||
import { togetherAiCredentialsSchema } from '@typebot.io/together-ai-block/schemas'
|
||||
import { zemanticAiBlock } from '@typebot.io/zemantic-ai-block'
|
||||
import { zemanticAiCredentialsSchema } from '@typebot.io/zemantic-ai-block/schemas'
|
||||
import { nocodbBlock } from '@typebot.io/nocodb-block'
|
||||
import { nocodbCredentialsSchema } from '@typebot.io/nocodb-block/schemas'
|
||||
|
||||
export const forgedCredentialsSchemas = {
|
||||
[openAIBlock.id]: openAICredentialsSchema,
|
||||
@ -33,4 +35,5 @@ export const forgedCredentialsSchemas = {
|
||||
[anthropicBlock.id]: anthropicCredentialsSchema,
|
||||
[togetherAiBlock.id]: togetherAiCredentialsSchema,
|
||||
[openRouterBlock.id]: openRouterCredentialsSchema,
|
||||
[nocodbBlock.id]: nocodbCredentialsSchema,
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import { chatNodeBlock } from '@typebot.io/chat-node-block'
|
||||
import { calComBlock } from '@typebot.io/cal-com-block'
|
||||
import { zemanticAiBlock } from '@typebot.io/zemantic-ai-block'
|
||||
import { openAIBlock } from '@typebot.io/openai-block'
|
||||
import { nocodbBlock } from '@typebot.io/nocodb-block'
|
||||
|
||||
export const forgedBlocks = {
|
||||
[openAIBlock.id]: openAIBlock,
|
||||
@ -23,4 +24,5 @@ export const forgedBlocks = {
|
||||
[anthropicBlock.id]: anthropicBlock,
|
||||
[togetherAiBlock.id]: togetherAiBlock,
|
||||
[openRouterBlock.id]: openRouterBlock,
|
||||
[nocodbBlock.id]: nocodbBlock,
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
"@typebot.io/elevenlabs-block": "workspace:*",
|
||||
"@typebot.io/anthropic-block": "workspace:*",
|
||||
"@typebot.io/together-ai-block": "workspace:*",
|
||||
"@typebot.io/open-router-block": "workspace:*"
|
||||
"@typebot.io/open-router-block": "workspace:*",
|
||||
"@typebot.io/nocodb-block": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import { togetherAiBlock } from '@typebot.io/together-ai-block'
|
||||
import { togetherAiBlockSchema } from '@typebot.io/together-ai-block/schemas'
|
||||
import { zemanticAiBlock } from '@typebot.io/zemantic-ai-block'
|
||||
import { zemanticAiBlockSchema } from '@typebot.io/zemantic-ai-block/schemas'
|
||||
import { nocodbBlock } from '@typebot.io/nocodb-block'
|
||||
import { nocodbBlockSchema } from '@typebot.io/nocodb-block/schemas'
|
||||
|
||||
export const forgedBlockSchemas = {
|
||||
[openAIBlock.id]: openAIBlockSchema,
|
||||
@ -34,4 +36,5 @@ export const forgedBlockSchemas = {
|
||||
[anthropicBlock.id]: anthropicBlockSchema,
|
||||
[togetherAiBlock.id]: togetherAiBlockSchema,
|
||||
[openRouterBlock.id]: openRouterBlockSchema,
|
||||
[nocodbBlock.id]: nocodbBlockSchema,
|
||||
}
|
||||
|
31
pnpm-lock.yaml
generated
31
pnpm-lock.yaml
generated
@ -1420,6 +1420,28 @@ importers:
|
||||
specifier: 5.4.5
|
||||
version: 5.4.5
|
||||
|
||||
packages/forge/blocks/nocodb:
|
||||
dependencies:
|
||||
ky:
|
||||
specifier: 1.2.3
|
||||
version: 1.2.3
|
||||
devDependencies:
|
||||
'@typebot.io/forge':
|
||||
specifier: workspace:*
|
||||
version: link:../../core
|
||||
'@typebot.io/lib':
|
||||
specifier: workspace:*
|
||||
version: link:../../../lib
|
||||
'@typebot.io/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tsconfig
|
||||
'@types/react':
|
||||
specifier: 18.2.15
|
||||
version: 18.2.15
|
||||
typescript:
|
||||
specifier: 5.3.2
|
||||
version: 5.3.2
|
||||
|
||||
packages/forge/blocks/openRouter:
|
||||
devDependencies:
|
||||
'@typebot.io/forge':
|
||||
@ -1596,6 +1618,9 @@ importers:
|
||||
'@typebot.io/mistral-block':
|
||||
specifier: workspace:*
|
||||
version: link:../blocks/mistral
|
||||
'@typebot.io/nocodb-block':
|
||||
specifier: workspace:*
|
||||
version: link:../blocks/nocodb
|
||||
'@typebot.io/open-router-block':
|
||||
specifier: workspace:*
|
||||
version: link:../blocks/openRouter
|
||||
@ -22392,6 +22417,12 @@ packages:
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/typescript@5.3.2:
|
||||
resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/typescript@5.4.5:
|
||||
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
|
Reference in New Issue
Block a user