mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-11 07:58:06 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b7437af14 | ||
|
|
4d7ebd8bcb | ||
|
|
ca1156a6c2 | ||
|
|
89eb1849d0 | ||
|
|
1d4833f485 | ||
|
|
d5902e91da | ||
|
|
9751c9f5c4 | ||
|
|
e6ba323de4 | ||
|
|
859711991f | ||
|
|
c178a90f02 | ||
|
|
eb8995ee7c | ||
|
|
b269447539 |
@@ -97,7 +97,7 @@ export function SidebarFolder({
|
||||
<div
|
||||
className={cn(
|
||||
'overflow-hidden transition-all duration-200 ease-in-out',
|
||||
open ? 'max-h-[2000px] opacity-100' : 'max-h-0 opacity-0'
|
||||
open ? 'max-h-[10000px] opacity-100' : 'max-h-0 opacity-0'
|
||||
)}
|
||||
>
|
||||
<ul className='mt-0.5 ml-2 space-y-[0.0625rem] border-gray-200/60 border-l pl-2.5 dark:border-gray-700/60'>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function Navbar() {
|
||||
</div>
|
||||
|
||||
{/* Center cluster: search */}
|
||||
<div className='flex flex-1 items-center justify-center'>
|
||||
<div className='flex flex-1 items-center justify-center pl-32'>
|
||||
<SearchTrigger />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export function SearchTrigger() {
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
className='flex h-10 w-[500px] items-center gap-2 rounded-xl border border-border/50 px-3 py-2 text-sm backdrop-blur-xl transition-colors hover:border-border'
|
||||
className='flex h-10 w-[460px] items-center gap-2 rounded-xl border border-border/50 px-3 py-2 text-sm backdrop-blur-xl transition-colors hover:border-border'
|
||||
style={{
|
||||
backgroundColor: 'hsla(0, 0%, 5%, 0.85)',
|
||||
backdropFilter: 'blur(33px) saturate(180%)',
|
||||
|
||||
@@ -75,6 +75,7 @@ Send a message to a Discord channel
|
||||
| `channelId` | string | Yes | The Discord channel ID to send the message to |
|
||||
| `content` | string | No | The text content of the message |
|
||||
| `serverId` | string | Yes | The Discord server ID \(guild ID\) |
|
||||
| `files` | file[] | No | Files to attach to the message |
|
||||
|
||||
#### Output
|
||||
|
||||
|
||||
@@ -230,4 +230,4 @@ curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \
|
||||
- Category: `triggers`
|
||||
- Type: `generic_webhook`
|
||||
- **File Support**: Available via input format configuration
|
||||
- **Max File Size**: 20MB per file
|
||||
- **Max File Size**: 20MB per file
|
||||
@@ -88,8 +88,9 @@ Upload a file to Google Drive
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `fileName` | string | Yes | The name of the file to upload |
|
||||
| `content` | string | Yes | The content of the file to upload |
|
||||
| `mimeType` | string | No | The MIME type of the file to upload |
|
||||
| `file` | file | No | Binary file to upload \(UserFile object\) |
|
||||
| `content` | string | No | Text content to upload \(use this OR file, not both\) |
|
||||
| `mimeType` | string | No | The MIME type of the file to upload \(auto-detected from file if not provided\) |
|
||||
| `folderSelector` | string | No | Select the folder to upload the file to |
|
||||
| `folderId` | string | No | The ID of the folder to upload the file to \(internal use\) |
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ Write or update content in a Microsoft Teams chat
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `chatId` | string | Yes | The ID of the chat to write to |
|
||||
| `content` | string | Yes | The content to write to the message |
|
||||
| `files` | file[] | No | Files to attach to the message |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -187,6 +188,7 @@ Write or send a message to a Microsoft Teams channel
|
||||
| `teamId` | string | Yes | The ID of the team to write to |
|
||||
| `channelId` | string | Yes | The ID of the channel to write to |
|
||||
| `content` | string | Yes | The content to write to the channel |
|
||||
| `files` | file[] | No | Files to attach to the message |
|
||||
|
||||
#### Output
|
||||
|
||||
|
||||
@@ -66,7 +66,8 @@ Upload a file to OneDrive
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `fileName` | string | Yes | The name of the file to upload |
|
||||
| `content` | string | Yes | The content of the file to upload |
|
||||
| `file` | file | No | The file to upload \(binary\) |
|
||||
| `content` | string | No | The text content to upload \(if no file is provided\) |
|
||||
| `folderSelector` | string | No | Select the folder to upload the file to |
|
||||
| `manualFolderId` | string | No | Manually entered folder ID \(advanced mode\) |
|
||||
|
||||
|
||||
@@ -161,6 +161,7 @@ Send emails using Outlook
|
||||
| `conversationId` | string | No | Conversation ID for threading |
|
||||
| `cc` | string | No | CC recipients \(comma-separated\) |
|
||||
| `bcc` | string | No | BCC recipients \(comma-separated\) |
|
||||
| `attachments` | file[] | No | Files to attach to the email |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -184,6 +185,7 @@ Draft emails using Outlook
|
||||
| `body` | string | Yes | Email body content |
|
||||
| `cc` | string | No | CC recipients \(comma-separated\) |
|
||||
| `bcc` | string | No | BCC recipients \(comma-separated\) |
|
||||
| `attachments` | file[] | No | Files to attach to the email draft |
|
||||
|
||||
#### Output
|
||||
|
||||
|
||||
@@ -202,6 +202,26 @@ Add a new item to a SharePoint list
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | object | Created SharePoint list item |
|
||||
|
||||
### `sharepoint_upload_file`
|
||||
|
||||
Upload files to a SharePoint document library
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `siteId` | string | No | The ID of the SharePoint site |
|
||||
| `driveId` | string | No | The ID of the document library \(drive\). If not provided, uses default drive. |
|
||||
| `folderPath` | string | No | Optional folder path within the document library \(e.g., /Documents/Subfolder\) |
|
||||
| `fileName` | string | No | Optional: override the uploaded file name |
|
||||
| `files` | file[] | No | Files to upload to SharePoint |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uploadedFiles` | array | Array of uploaded file objects |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -82,6 +82,7 @@ Send messages to Slack channels or users through the Slack API. Supports Slack m
|
||||
| `botToken` | string | No | Bot token for Custom Bot |
|
||||
| `channel` | string | Yes | Target Slack channel \(e.g., #general\) |
|
||||
| `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) |
|
||||
| `files` | file[] | No | Files to attach to the message |
|
||||
|
||||
#### Output
|
||||
|
||||
|
||||
@@ -205,6 +205,28 @@ Insert or update data in a Supabase table (upsert operation)
|
||||
| `message` | string | Operation status message |
|
||||
| `results` | array | Array of upserted records |
|
||||
|
||||
### `supabase_vector_search`
|
||||
|
||||
Perform similarity search using pgvector in a Supabase table
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
|
||||
| `functionName` | string | Yes | The name of the PostgreSQL function that performs vector search \(e.g., match_documents\) |
|
||||
| `queryEmbedding` | array | Yes | The query vector/embedding to search for similar items |
|
||||
| `matchThreshold` | number | No | Minimum similarity threshold \(0-1\), typically 0.7-0.9 |
|
||||
| `matchCount` | number | No | Maximum number of results to return \(default: 10\) |
|
||||
| `apiKey` | string | Yes | Your Supabase service role secret key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `results` | array | Array of records with similarity scores from the vector search. Each record includes a similarity field \(0-1\) indicating how similar it is to the query vector. |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -191,6 +191,26 @@ Send animations (GIFs) to Telegram channels or users through the Telegram Bot AP
|
||||
| `message` | string | Success or error message |
|
||||
| `data` | object | Telegram message data including optional media |
|
||||
|
||||
### `telegram_send_document`
|
||||
|
||||
Send documents (PDF, ZIP, DOC, etc.) to Telegram channels or users through the Telegram Bot API.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `botToken` | string | Yes | Your Telegram Bot API Token |
|
||||
| `chatId` | string | Yes | Target Telegram chat ID |
|
||||
| `files` | file[] | No | Document file to send \(PDF, ZIP, DOC, etc.\). Max size: 50MB |
|
||||
| `caption` | string | No | Document caption \(optional\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Success or error message |
|
||||
| `data` | object | Telegram message data including document |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -62,7 +62,8 @@ Process and analyze images using advanced vision models. Capable of understandin
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key for the selected model provider |
|
||||
| `imageUrl` | string | Yes | Publicly accessible image URL |
|
||||
| `imageUrl` | string | No | Publicly accessible image URL |
|
||||
| `imageFile` | file | No | Image file to analyze |
|
||||
| `model` | string | No | Vision model to use \(gpt-4o, claude-3-opus-20240229, etc\) |
|
||||
| `prompt` | string | No | Custom prompt for image analysis |
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ describe('Copilot Checkpoints Revert API Route', () => {
|
||||
setupCommonApiMocks()
|
||||
mockCryptoUuid()
|
||||
|
||||
// Mock getBaseUrl to return localhost for tests
|
||||
vi.doMock('@/lib/urls/utils', () => ({
|
||||
getBaseUrl: vi.fn(() => 'http://localhost:3000'),
|
||||
getBaseDomain: vi.fn(() => 'localhost:3000'),
|
||||
getEmailDomain: vi.fn(() => 'localhost:3000'),
|
||||
}))
|
||||
|
||||
mockSelect.mockReturnValue({ from: mockFrom })
|
||||
mockFrom.mockReturnValue({ where: mockWhere })
|
||||
mockWhere.mockReturnValue({ then: mockThen })
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@/lib/copilot/auth'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { validateUUID } from '@/lib/security/input-validation'
|
||||
import { getBaseUrl } from '@/lib/urls/utils'
|
||||
|
||||
const logger = createLogger('CheckpointRevertAPI')
|
||||
|
||||
@@ -93,7 +94,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const stateResponse = await fetch(
|
||||
`${request.nextUrl.origin}/api/workflows/${checkpoint.workflowId}/state`,
|
||||
`${getBaseUrl()}/api/workflows/${checkpoint.workflowId}/state`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
|
||||
@@ -29,6 +29,7 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
|
||||
{ label: 'Update a Row', id: 'update' },
|
||||
{ label: 'Delete a Row', id: 'delete' },
|
||||
{ label: 'Upsert a Row', id: 'upsert' },
|
||||
{ label: 'Vector Search', id: 'vector_search' },
|
||||
],
|
||||
value: () => 'query',
|
||||
},
|
||||
@@ -381,6 +382,41 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
|
||||
placeholder: '100',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
},
|
||||
// Vector search operation fields
|
||||
{
|
||||
id: 'functionName',
|
||||
title: 'Function Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'match_documents',
|
||||
condition: { field: 'operation', value: 'vector_search' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'queryEmbedding',
|
||||
title: 'Query Embedding',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
placeholder: '[0.1, 0.2, 0.3, ...]',
|
||||
condition: { field: 'operation', value: 'vector_search' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'matchThreshold',
|
||||
title: 'Match Threshold (optional)',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: '0.78',
|
||||
condition: { field: 'operation', value: 'vector_search' },
|
||||
},
|
||||
{
|
||||
id: 'matchCount',
|
||||
title: 'Match Count (optional)',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: '10',
|
||||
condition: { field: 'operation', value: 'vector_search' },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
@@ -390,6 +426,7 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
|
||||
'supabase_update',
|
||||
'supabase_delete',
|
||||
'supabase_upsert',
|
||||
'supabase_vector_search',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
@@ -406,12 +443,14 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
|
||||
return 'supabase_delete'
|
||||
case 'upsert':
|
||||
return 'supabase_upsert'
|
||||
case 'vector_search':
|
||||
return 'supabase_vector_search'
|
||||
default:
|
||||
throw new Error(`Invalid Supabase operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { operation, data, filter, ...rest } = params
|
||||
const { operation, data, filter, queryEmbedding, ...rest } = params
|
||||
|
||||
// Parse JSON data if it's a string
|
||||
let parsedData
|
||||
@@ -435,6 +474,21 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
|
||||
parsedFilter = filter.trim()
|
||||
}
|
||||
|
||||
// Handle query embedding for vector search
|
||||
let parsedQueryEmbedding
|
||||
if (queryEmbedding && typeof queryEmbedding === 'string' && queryEmbedding.trim()) {
|
||||
try {
|
||||
parsedQueryEmbedding = JSON.parse(queryEmbedding)
|
||||
} catch (parseError) {
|
||||
const errorMsg = parseError instanceof Error ? parseError.message : 'Unknown JSON error'
|
||||
throw new Error(
|
||||
`Invalid query embedding format: ${errorMsg}. Please provide a valid array of numbers like [0.1, 0.2, 0.3].`
|
||||
)
|
||||
}
|
||||
} else if (queryEmbedding && Array.isArray(queryEmbedding)) {
|
||||
parsedQueryEmbedding = queryEmbedding
|
||||
}
|
||||
|
||||
// Build params object, only including defined values
|
||||
const result = { ...rest }
|
||||
|
||||
@@ -446,6 +500,10 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
|
||||
result.filter = parsedFilter
|
||||
}
|
||||
|
||||
if (parsedQueryEmbedding !== undefined) {
|
||||
result.queryEmbedding = parsedQueryEmbedding
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
},
|
||||
@@ -462,6 +520,11 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
|
||||
// Query operation inputs
|
||||
orderBy: { type: 'string', description: 'Sort column' },
|
||||
limit: { type: 'number', description: 'Result limit' },
|
||||
// Vector search operation inputs
|
||||
functionName: { type: 'string', description: 'PostgreSQL function name for vector search' },
|
||||
queryEmbedding: { type: 'array', description: 'Query vector/embedding for similarity search' },
|
||||
matchThreshold: { type: 'number', description: 'Minimum similarity threshold (0-1)' },
|
||||
matchCount: { type: 'number', description: 'Maximum number of similar results to return' },
|
||||
},
|
||||
outputs: {
|
||||
message: {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { JSX, SVGProps } from 'react'
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export type BlockIcon = (props: SVGProps<SVGSVGElement>) => JSX.Element
|
||||
export type ParamType = 'string' | 'number' | 'boolean' | 'json'
|
||||
export type ParamType = 'string' | 'number' | 'boolean' | 'json' | 'array'
|
||||
export type PrimitiveValueType =
|
||||
| 'string'
|
||||
| 'number'
|
||||
|
||||
@@ -407,16 +407,30 @@ async function parseWithMistralOCR(fileUrl: string, filename: string, mimeType:
|
||||
try {
|
||||
const response = await retryWithExponentialBackoff(
|
||||
async () => {
|
||||
const url =
|
||||
let url =
|
||||
typeof mistralParserTool.request!.url === 'function'
|
||||
? mistralParserTool.request!.url(params)
|
||||
: mistralParserTool.request!.url
|
||||
|
||||
const headers =
|
||||
if (url.startsWith('/')) {
|
||||
const { getBaseUrl } = await import('@/lib/urls/utils')
|
||||
url = `${getBaseUrl()}${url}`
|
||||
}
|
||||
|
||||
let headers =
|
||||
typeof mistralParserTool.request!.headers === 'function'
|
||||
? mistralParserTool.request!.headers(params)
|
||||
: mistralParserTool.request!.headers
|
||||
|
||||
if (url.includes('/api/tools/mistral/parse')) {
|
||||
const { generateInternalToken } = await import('@/lib/auth/internal')
|
||||
const internalToken = await generateInternalToken()
|
||||
headers = {
|
||||
...headers,
|
||||
Authorization: `Bearer ${internalToken}`,
|
||||
}
|
||||
}
|
||||
|
||||
const requestBody = mistralParserTool.request!.body!(params) as OCRRequestBody
|
||||
return makeOCRRequest(url, headers as Record<string, string>, requestBody)
|
||||
},
|
||||
|
||||
@@ -227,7 +227,7 @@ export async function executeTool(
|
||||
const isInternalRoute = endpointUrl.startsWith('/api/')
|
||||
|
||||
if (isInternalRoute || skipProxy) {
|
||||
const result = await handleInternalRequest(toolId, tool, contextParams)
|
||||
const result = await handleInternalRequest(toolId, tool, contextParams, executionContext)
|
||||
|
||||
// Apply post-processing if available and not skipped
|
||||
let finalResult = result
|
||||
@@ -414,7 +414,8 @@ function isErrorResponse(
|
||||
async function handleInternalRequest(
|
||||
toolId: string,
|
||||
tool: ToolConfig,
|
||||
params: Record<string, any>
|
||||
params: Record<string, any>,
|
||||
executionContext?: ExecutionContext
|
||||
): Promise<ToolResponse> {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
@@ -427,7 +428,11 @@ async function handleInternalRequest(
|
||||
const endpointUrl =
|
||||
typeof tool.request.url === 'function' ? tool.request.url(params) : tool.request.url
|
||||
|
||||
const fullUrl = new URL(endpointUrl, baseUrl).toString()
|
||||
const fullUrlObj = new URL(endpointUrl, baseUrl)
|
||||
if (executionContext?.workflowId && typeof window === 'undefined') {
|
||||
fullUrlObj.searchParams.set('workflowId', executionContext.workflowId)
|
||||
}
|
||||
const fullUrl = fullUrlObj.toString()
|
||||
|
||||
// For custom tools, validate parameters on the client side before sending
|
||||
if (toolId.startsWith('custom_') && tool.request.body) {
|
||||
@@ -445,10 +450,26 @@ async function handleInternalRequest(
|
||||
}
|
||||
}
|
||||
|
||||
const headers = new Headers(requestParams.headers)
|
||||
const isInternalRoute = endpointUrl.startsWith('/api/')
|
||||
if (typeof window === 'undefined') {
|
||||
if (isInternalRoute) {
|
||||
try {
|
||||
const internalToken = await generateInternalToken()
|
||||
headers.set('Authorization', `Bearer ${internalToken}`)
|
||||
logger.info(`[${requestId}] Added internal auth token for ${toolId}`)
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Failed to generate internal token for ${toolId}:`, error)
|
||||
}
|
||||
} else {
|
||||
logger.info(`[${requestId}] Skipping internal auth token for external URL: ${endpointUrl}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare request options
|
||||
const requestOptions = {
|
||||
method: requestParams.method,
|
||||
headers: new Headers(requestParams.headers),
|
||||
headers: headers,
|
||||
body: requestParams.body,
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ import {
|
||||
supabaseQueryTool,
|
||||
supabaseUpdateTool,
|
||||
supabaseUpsertTool,
|
||||
supabaseVectorSearchTool,
|
||||
} from '@/tools/supabase'
|
||||
import { tavilyExtractTool, tavilySearchTool } from '@/tools/tavily'
|
||||
import {
|
||||
@@ -271,6 +272,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
supabase_update: supabaseUpdateTool,
|
||||
supabase_delete: supabaseDeleteTool,
|
||||
supabase_upsert: supabaseUpsertTool,
|
||||
supabase_vector_search: supabaseVectorSearchTool,
|
||||
typeform_responses: typeformResponsesTool,
|
||||
typeform_files: typeformFilesTool,
|
||||
typeform_insights: typeformInsightsTool,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { insertTool } from '@/tools/supabase/insert'
|
||||
import { queryTool } from '@/tools/supabase/query'
|
||||
import { updateTool } from '@/tools/supabase/update'
|
||||
import { upsertTool } from '@/tools/supabase/upsert'
|
||||
import { vectorSearchTool } from '@/tools/supabase/vector_search'
|
||||
|
||||
export const supabaseQueryTool = queryTool
|
||||
export const supabaseInsertTool = insertTool
|
||||
@@ -11,3 +12,4 @@ export const supabaseGetRowTool = getRowTool
|
||||
export const supabaseUpdateTool = updateTool
|
||||
export const supabaseDeleteTool = deleteTool
|
||||
export const supabaseUpsertTool = upsertTool
|
||||
export const supabaseVectorSearchTool = vectorSearchTool
|
||||
|
||||
@@ -45,6 +45,15 @@ export interface SupabaseUpsertParams {
|
||||
data: any
|
||||
}
|
||||
|
||||
export interface SupabaseVectorSearchParams {
|
||||
apiKey: string
|
||||
projectId: string
|
||||
functionName: string
|
||||
queryEmbedding: number[]
|
||||
matchThreshold?: number
|
||||
matchCount?: number
|
||||
}
|
||||
|
||||
export interface SupabaseBaseResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
@@ -65,4 +74,6 @@ export interface SupabaseDeleteResponse extends SupabaseBaseResponse {}
|
||||
|
||||
export interface SupabaseUpsertResponse extends SupabaseBaseResponse {}
|
||||
|
||||
export interface SupabaseVectorSearchResponse extends SupabaseBaseResponse {}
|
||||
|
||||
export interface SupabaseResponse extends SupabaseBaseResponse {}
|
||||
|
||||
125
apps/sim/tools/supabase/vector_search.ts
Normal file
125
apps/sim/tools/supabase/vector_search.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import type {
|
||||
SupabaseVectorSearchParams,
|
||||
SupabaseVectorSearchResponse,
|
||||
} from '@/tools/supabase/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const vectorSearchTool: ToolConfig<
|
||||
SupabaseVectorSearchParams,
|
||||
SupabaseVectorSearchResponse
|
||||
> = {
|
||||
id: 'supabase_vector_search',
|
||||
name: 'Supabase Vector Search',
|
||||
description: 'Perform similarity search using pgvector in a Supabase table',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
projectId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)',
|
||||
},
|
||||
functionName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The name of the PostgreSQL function that performs vector search (e.g., match_documents)',
|
||||
},
|
||||
queryEmbedding: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The query vector/embedding to search for similar items',
|
||||
},
|
||||
matchThreshold: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum similarity threshold (0-1), typically 0.7-0.9',
|
||||
},
|
||||
matchCount: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return (default: 10)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Supabase service role secret key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
// Use RPC endpoint for calling PostgreSQL functions
|
||||
return `https://${params.projectId}.supabase.co/rest/v1/rpc/${params.functionName}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
apikey: params.apiKey,
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
// Build the RPC call parameters
|
||||
const rpcParams: Record<string, any> = {
|
||||
query_embedding: params.queryEmbedding,
|
||||
}
|
||||
|
||||
// Add optional parameters if provided
|
||||
if (params.matchThreshold !== undefined) {
|
||||
rpcParams.match_threshold = params.matchThreshold
|
||||
}
|
||||
|
||||
if (params.matchCount !== undefined) {
|
||||
rpcParams.match_count = params.matchCount
|
||||
}
|
||||
|
||||
return rpcParams
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
let data
|
||||
try {
|
||||
data = await response.json()
|
||||
} catch (parseError) {
|
||||
throw new Error(`Failed to parse Supabase vector search response: ${parseError}`)
|
||||
}
|
||||
|
||||
const resultCount = Array.isArray(data) ? data.length : 0
|
||||
|
||||
if (resultCount === 0) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: 'No similar vectors found matching the search criteria',
|
||||
results: data,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: `Successfully found ${resultCount} similar vector${resultCount === 1 ? '' : 's'}`,
|
||||
results: data,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
results: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Array of records with similarity scores from the vector search. Each record includes a similarity field (0-1) indicating how similar it is to the query vector.',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -629,11 +629,20 @@ helm uninstall sim
|
||||
|
||||
For production deployments, make sure to:
|
||||
|
||||
1. **Change default secrets**: Update `BETTER_AUTH_SECRET` and `ENCRYPTION_KEY` with secure, randomly generated values
|
||||
1. **Change default secrets**: Update `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, and `INTERNAL_API_SECRET` with secure, randomly generated values using `openssl rand -hex 32`
|
||||
2. **Use strong database passwords**: Set `postgresql.auth.password` to a strong password
|
||||
3. **Enable TLS**: Configure `postgresql.tls.enabled=true` and provide proper certificates
|
||||
4. **Configure ingress TLS**: Enable HTTPS with proper SSL certificates
|
||||
|
||||
**Required Secrets:**
|
||||
- `BETTER_AUTH_SECRET`: Authentication JWT signing (minimum 32 characters)
|
||||
- `ENCRYPTION_KEY`: Encrypts sensitive data like environment variables (minimum 32 characters)
|
||||
- `INTERNAL_API_SECRET`: Internal service-to-service authentication (minimum 32 characters)
|
||||
|
||||
**Optional Security (Recommended for Production):**
|
||||
- `CRON_SECRET`: Authenticates scheduled job requests to API endpoints (required only if `cronjobs.enabled=true`)
|
||||
- `API_ENCRYPTION_KEY`: Encrypts API keys at rest in database (must be exactly 64 hex characters). If not set, API keys are stored in plain text. Generate using: `openssl rand -hex 32` (outputs 64 hex chars representing 32 bytes)
|
||||
|
||||
### Example secure values:
|
||||
|
||||
```yaml
|
||||
@@ -641,6 +650,9 @@ app:
|
||||
env:
|
||||
BETTER_AUTH_SECRET: "your-secure-random-string-here"
|
||||
ENCRYPTION_KEY: "your-secure-encryption-key-here"
|
||||
INTERNAL_API_SECRET: "your-secure-internal-api-secret-here"
|
||||
CRON_SECRET: "your-secure-cron-secret-here"
|
||||
API_ENCRYPTION_KEY: "your-64-char-hex-string-for-api-key-encryption" # Optional but recommended
|
||||
|
||||
postgresql:
|
||||
auth:
|
||||
|
||||
@@ -32,8 +32,15 @@ app:
|
||||
NEXT_PUBLIC_SOCKET_URL: "https://simstudio-ws.acme.com"
|
||||
|
||||
# Security settings (REQUIRED - replace with your own secure secrets)
|
||||
# Generate using: openssl rand -hex 32
|
||||
BETTER_AUTH_SECRET: "your-secure-production-auth-secret-here"
|
||||
ENCRYPTION_KEY: "your-secure-production-encryption-key-here"
|
||||
INTERNAL_API_SECRET: "your-secure-production-internal-api-secret-here"
|
||||
CRON_SECRET: "your-secure-production-cron-secret-here"
|
||||
|
||||
# Optional: API Key Encryption (RECOMMENDED for production)
|
||||
# Generate 64-character hex string using: openssl rand -hex 32
|
||||
API_ENCRYPTION_KEY: "your-64-char-hex-api-encryption-key-here" # Optional but recommended
|
||||
|
||||
NODE_ENV: "production"
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
|
||||
@@ -30,8 +30,15 @@ app:
|
||||
NEXT_PUBLIC_SOCKET_URL: "https://simstudio-ws.acme.com"
|
||||
|
||||
# Security settings (REQUIRED - replace with your own secure secrets)
|
||||
# Generate using: openssl rand -hex 32
|
||||
BETTER_AUTH_SECRET: "your-secure-production-auth-secret-here"
|
||||
ENCRYPTION_KEY: "your-secure-production-encryption-key-here"
|
||||
INTERNAL_API_SECRET: "your-secure-production-internal-api-secret-here"
|
||||
CRON_SECRET: "your-secure-production-cron-secret-here"
|
||||
|
||||
# Optional: API Key Encryption (RECOMMENDED for production)
|
||||
# Generate 64-character hex string using: openssl rand -hex 32
|
||||
API_ENCRYPTION_KEY: "your-64-char-hex-api-encryption-key-here" # Optional but recommended
|
||||
|
||||
NODE_ENV: "production"
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
|
||||
@@ -26,8 +26,15 @@ app:
|
||||
NEXT_PUBLIC_SOCKET_URL: "http://localhost:3002"
|
||||
|
||||
# Example secrets for development (replace with secure values for production)
|
||||
# For production, generate using: openssl rand -hex 32
|
||||
BETTER_AUTH_SECRET: "dev-32-char-auth-secret-not-secure-dev"
|
||||
ENCRYPTION_KEY: "dev-32-char-encryption-key-not-secure"
|
||||
INTERNAL_API_SECRET: "dev-32-char-internal-secret-not-secure"
|
||||
CRON_SECRET: "dev-32-char-cron-secret-not-for-prod"
|
||||
|
||||
# Optional: API Key Encryption (leave empty for dev, encrypts API keys at rest)
|
||||
# For production, generate 64-char hex using: openssl rand -hex 32
|
||||
API_ENCRYPTION_KEY: "" # Optional - if not set, API keys stored in plain text
|
||||
|
||||
# Realtime service
|
||||
realtime:
|
||||
|
||||
@@ -26,8 +26,15 @@ app:
|
||||
NEXT_PUBLIC_SOCKET_URL: "https://simstudio-ws.acme.com"
|
||||
|
||||
# Security settings (REQUIRED - replace with your own secure secrets)
|
||||
# Generate using: openssl rand -hex 32
|
||||
BETTER_AUTH_SECRET: "" # Set via --set flag or external secret manager
|
||||
ENCRYPTION_KEY: "" # Set via --set flag or external secret manager
|
||||
INTERNAL_API_SECRET: "" # Set via --set flag or external secret manager
|
||||
CRON_SECRET: "" # Set via --set flag or external secret manager
|
||||
|
||||
# Optional: API Key Encryption (RECOMMENDED for production)
|
||||
# Generate 64-character hex string using: openssl rand -hex 32
|
||||
API_ENCRYPTION_KEY: "" # Optional but recommended - encrypts API keys at rest
|
||||
|
||||
NODE_ENV: "production"
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
@@ -150,4 +157,7 @@ networkPolicy:
|
||||
# --set externalDatabase.database="your-db-name" \
|
||||
# --set app.env.BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \
|
||||
# --set app.env.ENCRYPTION_KEY="$(openssl rand -hex 32)" \
|
||||
# --set app.env.INTERNAL_API_SECRET="$(openssl rand -hex 32)" \
|
||||
# --set app.env.CRON_SECRET="$(openssl rand -hex 32)" \
|
||||
# --set app.env.API_ENCRYPTION_KEY="$(openssl rand -hex 32)" \
|
||||
# --set realtime.env.BETTER_AUTH_SECRET="$(openssl rand -hex 32)"
|
||||
@@ -32,8 +32,15 @@ app:
|
||||
NEXT_PUBLIC_SOCKET_URL: "https://simstudio-ws.acme.com"
|
||||
|
||||
# Security settings (REQUIRED - replace with your own secure secrets)
|
||||
# Generate using: openssl rand -hex 32
|
||||
BETTER_AUTH_SECRET: "your-secure-production-auth-secret-here"
|
||||
ENCRYPTION_KEY: "your-secure-production-encryption-key-here"
|
||||
INTERNAL_API_SECRET: "your-secure-production-internal-api-secret-here"
|
||||
CRON_SECRET: "your-secure-production-cron-secret-here"
|
||||
|
||||
# Optional: API Key Encryption (RECOMMENDED for production)
|
||||
# Generate 64-character hex string using: openssl rand -hex 32
|
||||
API_ENCRYPTION_KEY: "your-64-char-hex-api-encryption-key-here" # Optional but recommended
|
||||
|
||||
NODE_ENV: "production"
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
|
||||
@@ -27,8 +27,15 @@ app:
|
||||
NEXT_PUBLIC_SOCKET_URL: "https://sim-ws.acme.ai"
|
||||
|
||||
# Security settings (REQUIRED - replace with your own secure secrets)
|
||||
# Generate using: openssl rand -hex 32
|
||||
BETTER_AUTH_SECRET: "your-production-auth-secret-here"
|
||||
ENCRYPTION_KEY: "your-production-encryption-key-here"
|
||||
INTERNAL_API_SECRET: "your-production-internal-api-secret-here"
|
||||
CRON_SECRET: "your-production-cron-secret-here"
|
||||
|
||||
# Optional: API Key Encryption (RECOMMENDED for production)
|
||||
# Generate 64-character hex string using: openssl rand -hex 32
|
||||
API_ENCRYPTION_KEY: "your-64-char-hex-api-encryption-key-here" # Optional but recommended
|
||||
|
||||
# Email verification (set to true if you want to require email verification)
|
||||
EMAIL_VERIFICATION_ENABLED: "false"
|
||||
|
||||
@@ -20,8 +20,15 @@ app:
|
||||
NEXT_PUBLIC_SOCKET_URL: "https://sim-ws.acme.ai"
|
||||
|
||||
# Security settings (REQUIRED)
|
||||
# Generate using: openssl rand -hex 32
|
||||
BETTER_AUTH_SECRET: "your-production-auth-secret-here"
|
||||
ENCRYPTION_KEY: "your-production-encryption-key-here"
|
||||
INTERNAL_API_SECRET: "your-production-internal-api-secret-here"
|
||||
CRON_SECRET: "your-production-cron-secret-here"
|
||||
|
||||
# Optional: API Key Encryption (RECOMMENDED for production)
|
||||
# Generate 64-character hex string using: openssl rand -hex 32
|
||||
API_ENCRYPTION_KEY: "your-64-char-hex-api-encryption-key-here" # Optional but recommended
|
||||
|
||||
# UI Branding & Whitelabeling Configuration
|
||||
NEXT_PUBLIC_BRAND_NAME: "Acme AI Studio"
|
||||
|
||||
@@ -63,6 +63,15 @@ app:
|
||||
# Generate secure 32-character secrets using: openssl rand -hex 32
|
||||
BETTER_AUTH_SECRET: "" # REQUIRED - set via --set flag or external secret manager
|
||||
ENCRYPTION_KEY: "" # REQUIRED - set via --set flag or external secret manager
|
||||
INTERNAL_API_SECRET: "" # REQUIRED - set via --set flag or external secret manager, used for internal service-to-service authentication
|
||||
|
||||
# Optional: Scheduled Jobs Authentication
|
||||
# Generate using: openssl rand -hex 32
|
||||
CRON_SECRET: "" # OPTIONAL - required only if cronjobs.enabled=true, authenticates scheduled job requests
|
||||
|
||||
# Optional: API Key Encryption (RECOMMENDED for production)
|
||||
# Generate 64-character hex string using: openssl rand -hex 32 (outputs 64 hex chars = 32 bytes)
|
||||
API_ENCRYPTION_KEY: "" # OPTIONAL - encrypts API keys at rest, must be exactly 64 hex characters, if not set keys stored in plain text
|
||||
|
||||
# Email & Communication
|
||||
EMAIL_VERIFICATION_ENABLED: "false" # Enable email verification for user registration and login (defaults to false)
|
||||
|
||||
@@ -838,7 +838,7 @@ async function generateBlockDoc(blockPath: string, icons: Record<string, string>
|
||||
return
|
||||
}
|
||||
|
||||
if (blockConfig.type.includes('_trigger')) {
|
||||
if (blockConfig.type.includes('_trigger') || blockConfig.type.includes('_webhook')) {
|
||||
console.log(`Skipping ${blockConfig.type} - contains '_trigger'`)
|
||||
return
|
||||
}
|
||||
@@ -1111,7 +1111,7 @@ function updateMetaJson() {
|
||||
]
|
||||
|
||||
const metaJson = {
|
||||
items,
|
||||
pages: items,
|
||||
}
|
||||
|
||||
fs.writeFileSync(metaJsonPath, JSON.stringify(metaJson, null, 2))
|
||||
|
||||
Reference in New Issue
Block a user