✨ Introducing The Forge (#1072)
The Forge allows anyone to easily create their own Typebot Block. Closes #380
This commit is contained in:
110
packages/forge/blocks/zemanticAi/actions/searchDocuments.ts
Normal file
110
packages/forge/blocks/zemanticAi/actions/searchDocuments.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { createAction, option } from '@typebot.io/forge'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { ZemanticAiResponse } from '../types'
|
||||
import { got } from 'got'
|
||||
import { apiBaseUrl } from '../constants'
|
||||
import { auth } from '../auth'
|
||||
import { baseOptions } from '../baseOptions'
|
||||
|
||||
export const searchDocuments = createAction({
|
||||
baseOptions,
|
||||
auth,
|
||||
name: 'Search documents',
|
||||
options: option.object({
|
||||
query: option.string.layout({
|
||||
label: 'Query',
|
||||
placeholder: 'Content',
|
||||
moreInfoTooltip:
|
||||
'The question you want to ask or search against the documents in the project.',
|
||||
}),
|
||||
maxResults: option.number.layout({
|
||||
label: 'Max results',
|
||||
placeholder: 'i.e. 3',
|
||||
defaultValue: 3,
|
||||
moreInfoTooltip:
|
||||
'The maximum number of document chunk results to return from your search.',
|
||||
}),
|
||||
systemPrompt: option.string.layout({
|
||||
accordion: 'Advanced settings',
|
||||
label: 'System prompt',
|
||||
moreInfoTooltip:
|
||||
'System prompt to send to the summarization LLM. This is prepended to the prompt and helps guide system behavior.',
|
||||
input: 'textarea',
|
||||
}),
|
||||
prompt: option.string.layout({
|
||||
accordion: 'Advanced settings',
|
||||
label: 'Prompt',
|
||||
moreInfoTooltip: 'Prompt to send to the summarization LLM.',
|
||||
input: 'textarea',
|
||||
}),
|
||||
responseMapping: option
|
||||
.saveResponseArray([
|
||||
'Summary',
|
||||
'Document IDs',
|
||||
'Texts',
|
||||
'Scores',
|
||||
] as const)
|
||||
.layout({
|
||||
accordion: 'Save response',
|
||||
}),
|
||||
}),
|
||||
getSetVariableIds: ({ responseMapping }) =>
|
||||
responseMapping?.map((r) => r.variableId).filter(isDefined) ?? [],
|
||||
run: {
|
||||
server: async ({
|
||||
credentials: { apiKey },
|
||||
options: {
|
||||
maxResults,
|
||||
projectId,
|
||||
prompt,
|
||||
query,
|
||||
responseMapping,
|
||||
systemPrompt,
|
||||
},
|
||||
variables,
|
||||
}) => {
|
||||
const res: ZemanticAiResponse = await got
|
||||
.post(apiBaseUrl, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
json: {
|
||||
projectId,
|
||||
query,
|
||||
maxResults,
|
||||
summarize: true,
|
||||
summaryOptions: {
|
||||
system_prompt: systemPrompt,
|
||||
prompt: prompt,
|
||||
},
|
||||
},
|
||||
})
|
||||
.json()
|
||||
|
||||
responseMapping?.forEach((mapping) => {
|
||||
if (!mapping.variableId || !mapping.item) return
|
||||
|
||||
if (mapping.item === 'Document IDs')
|
||||
variables.set(
|
||||
mapping.variableId,
|
||||
res.results.map((r) => r.documentId)
|
||||
)
|
||||
|
||||
if (mapping.item === 'Texts')
|
||||
variables.set(
|
||||
mapping.variableId,
|
||||
res.results.map((r) => r.text)
|
||||
)
|
||||
|
||||
if (mapping.item === 'Scores')
|
||||
variables.set(
|
||||
mapping.variableId,
|
||||
res.results.map((r) => r.score)
|
||||
)
|
||||
|
||||
if (mapping.item === 'Summary')
|
||||
variables.set(mapping.variableId, res.summary)
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
15
packages/forge/blocks/zemanticAi/auth.ts
Normal file
15
packages/forge/blocks/zemanticAi/auth.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { AuthDefinition, option } from '@typebot.io/forge'
|
||||
|
||||
export const auth = {
|
||||
type: 'encryptedCredentials',
|
||||
name: 'Zemantic AI account',
|
||||
schema: option.object({
|
||||
apiKey: option.string.layout({
|
||||
label: 'API key',
|
||||
isRequired: true,
|
||||
placeholder: 'ze...',
|
||||
helperText:
|
||||
'You can generate an API key [here](https://zemantic.ai/dashboard/settings).',
|
||||
}),
|
||||
}),
|
||||
} satisfies AuthDefinition
|
8
packages/forge/blocks/zemanticAi/baseOptions.ts
Normal file
8
packages/forge/blocks/zemanticAi/baseOptions.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { option } from '@typebot.io/forge'
|
||||
|
||||
export const baseOptions = option.object({
|
||||
projectId: option.string.layout({
|
||||
placeholder: 'Select a project',
|
||||
fetcher: 'fetchProjects',
|
||||
}),
|
||||
})
|
1
packages/forge/blocks/zemanticAi/constants.ts
Normal file
1
packages/forge/blocks/zemanticAi/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const apiBaseUrl = 'https://api.zemantic.ai/v1/search-documents'
|
43
packages/forge/blocks/zemanticAi/index.ts
Normal file
43
packages/forge/blocks/zemanticAi/index.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { createBlock } from '@typebot.io/forge'
|
||||
import { ZemanticAiLogo } from './logo'
|
||||
import { got } from 'got'
|
||||
import { searchDocuments } from './actions/searchDocuments'
|
||||
import { auth } from './auth'
|
||||
import { baseOptions } from './baseOptions'
|
||||
|
||||
export const zemanticAi = createBlock({
|
||||
id: 'zemantic-ai',
|
||||
name: 'Zemantic AI',
|
||||
tags: [],
|
||||
LightLogo: ZemanticAiLogo,
|
||||
auth,
|
||||
options: baseOptions,
|
||||
fetchers: [
|
||||
{
|
||||
id: 'fetchProjects',
|
||||
dependencies: [],
|
||||
fetch: async ({ credentials: { apiKey } }) => {
|
||||
const url = 'https://api.zemantic.ai/v1/projects'
|
||||
|
||||
const response = await got
|
||||
.get(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
})
|
||||
.json()
|
||||
|
||||
const projectsData = response as {
|
||||
id: string
|
||||
name: string
|
||||
}[]
|
||||
|
||||
return projectsData.map((project) => ({
|
||||
label: project.name,
|
||||
value: project.id,
|
||||
}))
|
||||
},
|
||||
},
|
||||
],
|
||||
actions: [searchDocuments],
|
||||
})
|
21
packages/forge/blocks/zemanticAi/logo.tsx
Normal file
21
packages/forge/blocks/zemanticAi/logo.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
|
||||
export const ZemanticAiLogo = (props: React.SVGProps<SVGSVGElement>) => (
|
||||
<svg viewBox="0 0 24 24" {...props}>
|
||||
<g transform="matrix(.049281 0 0 .064343 -.27105 -3.4424)">
|
||||
<path
|
||||
d="m99.5 205.5v221h-94v-373h94v152z"
|
||||
fill="#8771b1"
|
||||
opacity=".991"
|
||||
/>
|
||||
<path
|
||||
d="m284.5 426.5v-221-152h94v373h-94z"
|
||||
fill="#f05b4e"
|
||||
opacity=".99"
|
||||
/>
|
||||
<path d="m99.5 205.5h93v221h-93v-221z" fill="#ec9896" />
|
||||
<path d="m192.5 205.5h92v221h-92v-221z" fill="#efe894" />
|
||||
<path d="m398.5 298.5h94v128h-94v-128z" fill="#46bb91" opacity=".989" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
16
packages/forge/blocks/zemanticAi/package.json
Normal file
16
packages/forge/blocks/zemanticAi/package.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "@typebot.io/zemantic-ai-block",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"keywords": [],
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"got": "12.6.0"
|
||||
}
|
||||
}
|
10
packages/forge/blocks/zemanticAi/tsconfig.json
Normal file
10
packages/forge/blocks/zemanticAi/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@typebot.io/tsconfig/base.json",
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
4
packages/forge/blocks/zemanticAi/types.ts
Normal file
4
packages/forge/blocks/zemanticAi/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type ZemanticAiResponse = {
|
||||
results: { documentId: string; text: string; score: number }[]
|
||||
summary: string
|
||||
}
|
Reference in New Issue
Block a user