diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.test.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.test.ts index 148b793e5a..302d5f0b1b 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.test.ts @@ -218,8 +218,15 @@ describe('Document By ID API Route', () => { }), } + // Mock transaction + mockDbChain.transaction.mockImplementation(async (callback) => { + const mockTx = { + update: vi.fn().mockReturnValue(updateChain), + } + await callback(mockTx) + }) + // Mock db operations in sequence - mockDbChain.update.mockReturnValue(updateChain) mockDbChain.select.mockReturnValue(selectChain) const req = createMockRequest('PUT', validUpdateData) @@ -231,7 +238,7 @@ describe('Document By ID API Route', () => { expect(data.success).toBe(true) expect(data.data.filename).toBe('updated-document.pdf') expect(data.data.enabled).toBe(false) - expect(mockDbChain.update).toHaveBeenCalled() + expect(mockDbChain.transaction).toHaveBeenCalled() expect(mockDbChain.select).toHaveBeenCalled() }) @@ -298,8 +305,15 @@ describe('Document By ID API Route', () => { }), } + // Mock transaction + mockDbChain.transaction.mockImplementation(async (callback) => { + const mockTx = { + update: vi.fn().mockReturnValue(updateChain), + } + await callback(mockTx) + }) + // Mock db operations in sequence - mockDbChain.update.mockReturnValue(updateChain) mockDbChain.select.mockReturnValue(selectChain) const req = createMockRequest('PUT', { markFailedDueToTimeout: true }) @@ -309,7 +323,7 @@ describe('Document By ID API Route', () => { expect(response.status).toBe(200) expect(data.success).toBe(true) - expect(mockDbChain.update).toHaveBeenCalled() + expect(mockDbChain.transaction).toHaveBeenCalled() expect(updateChain.set).toHaveBeenCalledWith( expect.objectContaining({ processingStatus: 'failed', @@ -479,7 +493,9 @@ describe('Document By ID API Route', () => { document: mockDocument, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, }) - mockDbChain.set.mockRejectedValue(new Error('Database error')) + + // Mock transaction to throw an error + mockDbChain.transaction.mockRejectedValue(new Error('Database error')) const req = createMockRequest('PUT', validUpdateData) const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route') diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts index dbf5b6fcfd..c3912f47e8 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts @@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' +import { TAG_SLOTS } from '@/lib/constants/knowledge' import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' @@ -26,6 +27,14 @@ const UpdateDocumentSchema = z.object({ processingError: z.string().optional(), markFailedDueToTimeout: z.boolean().optional(), retryProcessing: z.boolean().optional(), + // Tag fields + tag1: z.string().optional(), + tag2: z.string().optional(), + tag3: z.string().optional(), + tag4: z.string().optional(), + tag5: z.string().optional(), + tag6: z.string().optional(), + tag7: z.string().optional(), }) export async function GET( @@ -213,9 +222,36 @@ export async function PUT( updateData.processingStatus = validatedData.processingStatus if (validatedData.processingError !== undefined) updateData.processingError = validatedData.processingError + + // Tag field updates + TAG_SLOTS.forEach((slot) => { + if ((validatedData as any)[slot] !== undefined) { + ;(updateData as any)[slot] = (validatedData as any)[slot] + } + }) } - await db.update(document).set(updateData).where(eq(document.id, documentId)) + await db.transaction(async (tx) => { + // Update the document + await tx.update(document).set(updateData).where(eq(document.id, documentId)) + + // If any tag fields were updated, also update the embeddings + const hasTagUpdates = TAG_SLOTS.some((field) => (validatedData as any)[field] !== undefined) + + if (hasTagUpdates) { + const embeddingUpdateData: Record = {} + TAG_SLOTS.forEach((field) => { + if ((validatedData as any)[field] !== undefined) { + embeddingUpdateData[field] = (validatedData as any)[field] || null + } + }) + + await tx + .update(embedding) + .set(embeddingUpdateData) + .where(eq(embedding.documentId, documentId)) + } + }) // Fetch the updated document const updatedDocument = await db diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts new file mode 100644 index 0000000000..135c74b8b7 --- /dev/null +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts @@ -0,0 +1,367 @@ +import { randomUUID } from 'crypto' +import { and, eq, sql } from 'drizzle-orm' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { getSession } from '@/lib/auth' +import { MAX_TAG_SLOTS, TAG_SLOTS } from '@/lib/constants/knowledge' +import { createLogger } from '@/lib/logs/console/logger' +import { checkKnowledgeBaseAccess, checkKnowledgeBaseWriteAccess } from '@/app/api/knowledge/utils' +import { db } from '@/db' +import { document, knowledgeBaseTagDefinitions } from '@/db/schema' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('DocumentTagDefinitionsAPI') + +const TagDefinitionSchema = z.object({ + tagSlot: z.enum(TAG_SLOTS as [string, ...string[]]), + displayName: z.string().min(1, 'Display name is required').max(100, 'Display name too long'), + fieldType: z.string().default('text'), // Currently only 'text', future: 'date', 'number', 'range' +}) + +const BulkTagDefinitionsSchema = z.object({ + definitions: z + .array(TagDefinitionSchema) + .max(MAX_TAG_SLOTS, `Cannot define more than ${MAX_TAG_SLOTS} tags`), +}) + +// Helper function to clean up unused tag definitions +async function cleanupUnusedTagDefinitions(knowledgeBaseId: string, requestId: string) { + try { + logger.info(`[${requestId}] Starting cleanup for KB ${knowledgeBaseId}`) + + // Get all tag definitions for this KB + const allDefinitions = await db + .select() + .from(knowledgeBaseTagDefinitions) + .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, knowledgeBaseId)) + + logger.info(`[${requestId}] Found ${allDefinitions.length} tag definitions to check`) + + if (allDefinitions.length === 0) { + return 0 + } + + let cleanedCount = 0 + + // For each tag definition, check if any documents use that tag slot + for (const definition of allDefinitions) { + const slot = definition.tagSlot + + // Use raw SQL with proper column name injection + const countResult = await db.execute(sql` + SELECT count(*) as count + FROM document + WHERE knowledge_base_id = ${knowledgeBaseId} + AND ${sql.raw(slot)} IS NOT NULL + AND trim(${sql.raw(slot)}) != '' + `) + const count = Number(countResult[0]?.count) || 0 + + logger.info( + `[${requestId}] Tag ${definition.displayName} (${slot}): ${count} documents using it` + ) + + // If count is 0, remove this tag definition + if (count === 0) { + await db + .delete(knowledgeBaseTagDefinitions) + .where(eq(knowledgeBaseTagDefinitions.id, definition.id)) + + cleanedCount++ + logger.info( + `[${requestId}] Removed unused tag definition: ${definition.displayName} (${definition.tagSlot})` + ) + } + } + + return cleanedCount + } catch (error) { + logger.warn(`[${requestId}] Failed to cleanup unused tag definitions:`, error) + return 0 // Don't fail the main operation if cleanup fails + } +} + +// GET /api/knowledge/[id]/documents/[documentId]/tag-definitions - Get tag definitions for a document +export async function GET( + req: NextRequest, + { params }: { params: Promise<{ id: string; documentId: string }> } +) { + const requestId = randomUUID().slice(0, 8) + const { id: knowledgeBaseId, documentId } = await params + + try { + logger.info(`[${requestId}] Getting tag definitions for document ${documentId}`) + + const session = await getSession() + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + // Check if user has access to the knowledge base + const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, session.user.id) + if (!accessCheck.hasAccess) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) + } + + // Verify document exists and belongs to the knowledge base + const documentExists = await db + .select({ id: document.id }) + .from(document) + .where(and(eq(document.id, documentId), eq(document.knowledgeBaseId, knowledgeBaseId))) + .limit(1) + + if (documentExists.length === 0) { + return NextResponse.json({ error: 'Document not found' }, { status: 404 }) + } + + // Get tag definitions for the knowledge base + const tagDefinitions = await db + .select({ + id: knowledgeBaseTagDefinitions.id, + tagSlot: knowledgeBaseTagDefinitions.tagSlot, + displayName: knowledgeBaseTagDefinitions.displayName, + fieldType: knowledgeBaseTagDefinitions.fieldType, + createdAt: knowledgeBaseTagDefinitions.createdAt, + updatedAt: knowledgeBaseTagDefinitions.updatedAt, + }) + .from(knowledgeBaseTagDefinitions) + .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, knowledgeBaseId)) + + logger.info(`[${requestId}] Retrieved ${tagDefinitions.length} tag definitions`) + + return NextResponse.json({ + success: true, + data: tagDefinitions, + }) + } catch (error) { + logger.error(`[${requestId}] Error getting tag definitions`, error) + return NextResponse.json({ error: 'Failed to get tag definitions' }, { status: 500 }) + } +} + +// POST /api/knowledge/[id]/documents/[documentId]/tag-definitions - Create/update tag definitions +export async function POST( + req: NextRequest, + { params }: { params: Promise<{ id: string; documentId: string }> } +) { + const requestId = randomUUID().slice(0, 8) + const { id: knowledgeBaseId, documentId } = await params + + try { + logger.info(`[${requestId}] Creating/updating tag definitions for document ${documentId}`) + + const session = await getSession() + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + // Check if user has write access to the knowledge base + const accessCheck = await checkKnowledgeBaseWriteAccess(knowledgeBaseId, session.user.id) + if (!accessCheck.hasAccess) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) + } + + // Verify document exists and belongs to the knowledge base + const documentExists = await db + .select({ id: document.id }) + .from(document) + .where(and(eq(document.id, documentId), eq(document.knowledgeBaseId, knowledgeBaseId))) + .limit(1) + + if (documentExists.length === 0) { + return NextResponse.json({ error: 'Document not found' }, { status: 404 }) + } + + let body + try { + body = await req.json() + } catch (error) { + logger.error(`[${requestId}] Failed to parse JSON body:`, error) + return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 }) + } + + if (!body || typeof body !== 'object') { + logger.error(`[${requestId}] Invalid request body:`, body) + return NextResponse.json( + { error: 'Request body must be a valid JSON object' }, + { status: 400 } + ) + } + + const validatedData = BulkTagDefinitionsSchema.parse(body) + + // Validate no duplicate tag slots + const tagSlots = validatedData.definitions.map((def) => def.tagSlot) + const uniqueTagSlots = new Set(tagSlots) + if (tagSlots.length !== uniqueTagSlots.size) { + return NextResponse.json({ error: 'Duplicate tag slots not allowed' }, { status: 400 }) + } + + const now = new Date() + const createdDefinitions: (typeof knowledgeBaseTagDefinitions.$inferSelect)[] = [] + + // Get existing definitions count before transaction for cleanup check + const existingDefinitions = await db + .select() + .from(knowledgeBaseTagDefinitions) + .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, knowledgeBaseId)) + + // Check if we're trying to create more tag definitions than available slots + const existingTagNames = new Set(existingDefinitions.map((def) => def.displayName)) + const trulyNewTags = validatedData.definitions.filter( + (def) => !existingTagNames.has(def.displayName) + ) + + if (existingDefinitions.length + trulyNewTags.length > MAX_TAG_SLOTS) { + return NextResponse.json( + { + error: `Cannot create ${trulyNewTags.length} new tags. Knowledge base already has ${existingDefinitions.length} tag definitions. Maximum is ${MAX_TAG_SLOTS} total.`, + }, + { status: 400 } + ) + } + + // Use transaction to ensure consistency + await db.transaction(async (tx) => { + // Create maps for lookups + const existingByName = new Map(existingDefinitions.map((def) => [def.displayName, def])) + const existingBySlot = new Map(existingDefinitions.map((def) => [def.tagSlot, def])) + + // Process each new definition + for (const definition of validatedData.definitions) { + const existingByDisplayName = existingByName.get(definition.displayName) + const existingByTagSlot = existingBySlot.get(definition.tagSlot) + + if (existingByDisplayName) { + // Update existing definition (same display name) + if (existingByDisplayName.tagSlot !== definition.tagSlot) { + // Slot is changing - check if target slot is available + if (existingByTagSlot && existingByTagSlot.id !== existingByDisplayName.id) { + // Target slot is occupied by a different definition - this is a conflict + // For now, keep the existing slot to avoid constraint violation + logger.warn( + `[${requestId}] Slot conflict for ${definition.displayName}: keeping existing slot ${existingByDisplayName.tagSlot}` + ) + createdDefinitions.push(existingByDisplayName) + continue + } + } + + await tx + .update(knowledgeBaseTagDefinitions) + .set({ + tagSlot: definition.tagSlot, + fieldType: definition.fieldType, + updatedAt: now, + }) + .where(eq(knowledgeBaseTagDefinitions.id, existingByDisplayName.id)) + + createdDefinitions.push({ + ...existingByDisplayName, + tagSlot: definition.tagSlot, + fieldType: definition.fieldType, + updatedAt: now, + }) + } else if (existingByTagSlot) { + // Slot is occupied by a different display name - update it + await tx + .update(knowledgeBaseTagDefinitions) + .set({ + displayName: definition.displayName, + fieldType: definition.fieldType, + updatedAt: now, + }) + .where(eq(knowledgeBaseTagDefinitions.id, existingByTagSlot.id)) + + createdDefinitions.push({ + ...existingByTagSlot, + displayName: definition.displayName, + fieldType: definition.fieldType, + updatedAt: now, + }) + } else { + // Create new definition + const newDefinition = { + id: randomUUID(), + knowledgeBaseId, + tagSlot: definition.tagSlot, + displayName: definition.displayName, + fieldType: definition.fieldType, + createdAt: now, + updatedAt: now, + } + + await tx.insert(knowledgeBaseTagDefinitions).values(newDefinition) + createdDefinitions.push(newDefinition) + } + } + }) + + logger.info(`[${requestId}] Created/updated ${createdDefinitions.length} tag definitions`) + + return NextResponse.json({ + success: true, + data: createdDefinitions, + }) + } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error(`[${requestId}] Error creating/updating tag definitions`, error) + return NextResponse.json({ error: 'Failed to create/update tag definitions' }, { status: 500 }) + } +} + +// DELETE /api/knowledge/[id]/documents/[documentId]/tag-definitions - Delete all tag definitions for a document +export async function DELETE( + req: NextRequest, + { params }: { params: Promise<{ id: string; documentId: string }> } +) { + const requestId = randomUUID().slice(0, 8) + const { id: knowledgeBaseId, documentId } = await params + const { searchParams } = new URL(req.url) + const action = searchParams.get('action') // 'cleanup' or 'all' + + try { + const session = await getSession() + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + // Check if user has write access to the knowledge base + const accessCheck = await checkKnowledgeBaseWriteAccess(knowledgeBaseId, session.user.id) + if (!accessCheck.hasAccess) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) + } + + if (action === 'cleanup') { + // Just run cleanup + logger.info(`[${requestId}] Running cleanup for KB ${knowledgeBaseId}`) + const cleanedUpCount = await cleanupUnusedTagDefinitions(knowledgeBaseId, requestId) + + return NextResponse.json({ + success: true, + data: { cleanedUp: cleanedUpCount }, + }) + } + // Delete all tag definitions (original behavior) + logger.info(`[${requestId}] Deleting all tag definitions for KB ${knowledgeBaseId}`) + + const result = await db + .delete(knowledgeBaseTagDefinitions) + .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, knowledgeBaseId)) + + return NextResponse.json({ + success: true, + message: 'Tag definitions deleted successfully', + }) + } catch (error) { + logger.error(`[${requestId}] Error with tag definitions operation`, error) + return NextResponse.json({ error: 'Failed to process tag definitions' }, { status: 500 }) + } +} diff --git a/apps/sim/app/api/knowledge/[id]/documents/route.ts b/apps/sim/app/api/knowledge/[id]/documents/route.ts index 330bf77aa9..7619a330f7 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/route.ts @@ -3,6 +3,7 @@ import { and, desc, eq, inArray, isNull, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' +import { TAG_SLOTS } from '@/lib/constants/knowledge' import { createLogger } from '@/lib/logs/console/logger' import { getUserId } from '@/app/api/auth/oauth/utils' import { @@ -11,7 +12,7 @@ import { processDocumentAsync, } from '@/app/api/knowledge/utils' import { db } from '@/db' -import { document } from '@/db/schema' +import { document, knowledgeBaseTagDefinitions } from '@/db/schema' const logger = createLogger('DocumentsAPI') @@ -22,6 +23,88 @@ const PROCESSING_CONFIG = { delayBetweenDocuments: 500, } +// Helper function to process structured document tags +async function processDocumentTags( + knowledgeBaseId: string, + tagData: Array<{ tagName: string; fieldType: string; value: string }>, + requestId: string +): Promise> { + const result: Record = {} + + // Initialize all tag slots to null + TAG_SLOTS.forEach((slot) => { + result[slot] = null + }) + + if (!Array.isArray(tagData) || tagData.length === 0) { + return result + } + + try { + // Get existing tag definitions + const existingDefinitions = await db + .select() + .from(knowledgeBaseTagDefinitions) + .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, knowledgeBaseId)) + + const existingByName = new Map(existingDefinitions.map((def) => [def.displayName, def])) + const existingBySlot = new Map(existingDefinitions.map((def) => [def.tagSlot, def])) + + // Process each tag + for (const tag of tagData) { + if (!tag.tagName?.trim() || !tag.value?.trim()) continue + + const tagName = tag.tagName.trim() + const fieldType = tag.fieldType || 'text' + const value = tag.value.trim() + + let targetSlot: string | null = null + + // Check if tag definition already exists + const existingDef = existingByName.get(tagName) + if (existingDef) { + targetSlot = existingDef.tagSlot + } else { + // Find next available slot + for (const slot of TAG_SLOTS) { + if (!existingBySlot.has(slot)) { + targetSlot = slot + break + } + } + + // Create new tag definition if we have a slot + if (targetSlot) { + const newDefinition = { + id: crypto.randomUUID(), + knowledgeBaseId, + tagSlot: targetSlot as any, + displayName: tagName, + fieldType, + createdAt: new Date(), + updatedAt: new Date(), + } + + await db.insert(knowledgeBaseTagDefinitions).values(newDefinition) + existingBySlot.set(targetSlot as any, newDefinition) + + logger.info(`[${requestId}] Created tag definition: ${tagName} -> ${targetSlot}`) + } + } + + // Assign value to the slot + if (targetSlot) { + result[targetSlot] = value + } + } + + return result + } catch (error) { + logger.error(`[${requestId}] Error processing document tags:`, error) + return result + } +} + async function processDocumentsWithConcurrencyControl( createdDocuments: Array<{ documentId: string @@ -158,7 +241,7 @@ const CreateDocumentSchema = z.object({ fileUrl: z.string().url('File URL must be valid'), fileSize: z.number().min(1, 'File size must be greater than 0'), mimeType: z.string().min(1, 'MIME type is required'), - // Document tags for filtering + // Document tags for filtering (legacy format) tag1: z.string().optional(), tag2: z.string().optional(), tag3: z.string().optional(), @@ -166,6 +249,8 @@ const CreateDocumentSchema = z.object({ tag5: z.string().optional(), tag6: z.string().optional(), tag7: z.string().optional(), + // Structured tag data (new format) + documentTagsData: z.string().optional(), }) const BulkCreateDocumentsSchema = z.object({ @@ -350,6 +435,31 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: const documentId = crypto.randomUUID() const now = new Date() + // Process documentTagsData if provided (for knowledge base block) + let processedTags: Record = { + tag1: null, + tag2: null, + tag3: null, + tag4: null, + tag5: null, + tag6: null, + tag7: null, + } + + if (docData.documentTagsData) { + try { + const tagData = JSON.parse(docData.documentTagsData) + if (Array.isArray(tagData)) { + processedTags = await processDocumentTags(knowledgeBaseId, tagData, requestId) + } + } catch (error) { + logger.warn( + `[${requestId}] Failed to parse documentTagsData for bulk document:`, + error + ) + } + } + const newDocument = { id: documentId, knowledgeBaseId, @@ -363,14 +473,14 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: processingStatus: 'pending' as const, enabled: true, uploadedAt: now, - // Include tags from upload - tag1: docData.tag1 || null, - tag2: docData.tag2 || null, - tag3: docData.tag3 || null, - tag4: docData.tag4 || null, - tag5: docData.tag5 || null, - tag6: docData.tag6 || null, - tag7: docData.tag7 || null, + // Use processed tags if available, otherwise fall back to individual tag fields + tag1: processedTags.tag1 || docData.tag1 || null, + tag2: processedTags.tag2 || docData.tag2 || null, + tag3: processedTags.tag3 || docData.tag3 || null, + tag4: processedTags.tag4 || docData.tag4 || null, + tag5: processedTags.tag5 || docData.tag5 || null, + tag6: processedTags.tag6 || docData.tag6 || null, + tag7: processedTags.tag7 || docData.tag7 || null, } await tx.insert(document).values(newDocument) @@ -433,6 +543,29 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: const documentId = crypto.randomUUID() const now = new Date() + // Process structured tag data if provided + let processedTags: Record = { + tag1: validatedData.tag1 || null, + tag2: validatedData.tag2 || null, + tag3: validatedData.tag3 || null, + tag4: validatedData.tag4 || null, + tag5: validatedData.tag5 || null, + tag6: validatedData.tag6 || null, + tag7: validatedData.tag7 || null, + } + + if (validatedData.documentTagsData) { + try { + const tagData = JSON.parse(validatedData.documentTagsData) + if (Array.isArray(tagData)) { + // Process structured tag data and create tag definitions + processedTags = await processDocumentTags(knowledgeBaseId, tagData, requestId) + } + } catch (error) { + logger.warn(`[${requestId}] Failed to parse documentTagsData:`, error) + } + } + const newDocument = { id: documentId, knowledgeBaseId, @@ -445,14 +578,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: characterCount: 0, enabled: true, uploadedAt: now, - // Include tags from upload - tag1: validatedData.tag1 || null, - tag2: validatedData.tag2 || null, - tag3: validatedData.tag3 || null, - tag4: validatedData.tag4 || null, - tag5: validatedData.tag5 || null, - tag6: validatedData.tag6 || null, - tag7: validatedData.tag7 || null, + ...processedTags, } await db.insert(document).values(newDocument) diff --git a/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts b/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts new file mode 100644 index 0000000000..6d43155f03 --- /dev/null +++ b/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts @@ -0,0 +1,57 @@ +import { randomUUID } from 'crypto' +import { eq } from 'drizzle-orm' +import { type NextRequest, NextResponse } from 'next/server' +import { getSession } from '@/lib/auth' +import { createLogger } from '@/lib/logs/console/logger' +import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' +import { db } from '@/db' +import { knowledgeBaseTagDefinitions } from '@/db/schema' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('KnowledgeBaseTagDefinitionsAPI') + +// GET /api/knowledge/[id]/tag-definitions - Get all tag definitions for a knowledge base +export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { + const requestId = randomUUID().slice(0, 8) + const { id: knowledgeBaseId } = await params + + try { + logger.info(`[${requestId}] Getting tag definitions for knowledge base ${knowledgeBaseId}`) + + const session = await getSession() + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + // Check if user has access to the knowledge base + const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, session.user.id) + if (!accessCheck.hasAccess) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) + } + + // Get tag definitions for the knowledge base + const tagDefinitions = await db + .select({ + id: knowledgeBaseTagDefinitions.id, + tagSlot: knowledgeBaseTagDefinitions.tagSlot, + displayName: knowledgeBaseTagDefinitions.displayName, + fieldType: knowledgeBaseTagDefinitions.fieldType, + createdAt: knowledgeBaseTagDefinitions.createdAt, + updatedAt: knowledgeBaseTagDefinitions.updatedAt, + }) + .from(knowledgeBaseTagDefinitions) + .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, knowledgeBaseId)) + .orderBy(knowledgeBaseTagDefinitions.tagSlot) + + logger.info(`[${requestId}] Retrieved ${tagDefinitions.length} tag definitions`) + + return NextResponse.json({ + success: true, + data: tagDefinitions, + }) + } catch (error) { + logger.error(`[${requestId}] Error getting tag definitions`, error) + return NextResponse.json({ error: 'Failed to get tag definitions' }, { status: 500 }) + } +} diff --git a/apps/sim/app/api/knowledge/search/route.ts b/apps/sim/app/api/knowledge/search/route.ts index 4ead4be428..d3bc6ae1e5 100644 --- a/apps/sim/app/api/knowledge/search/route.ts +++ b/apps/sim/app/api/knowledge/search/route.ts @@ -1,6 +1,7 @@ import { and, eq, inArray, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' +import { TAG_SLOTS } from '@/lib/constants/knowledge' import { retryWithExponentialBackoff } from '@/lib/documents/utils' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' @@ -8,31 +9,50 @@ import { estimateTokenCount } from '@/lib/tokenization/estimators' import { getUserId } from '@/app/api/auth/oauth/utils' import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' import { db } from '@/db' -import { embedding } from '@/db/schema' +import { embedding, knowledgeBaseTagDefinitions } from '@/db/schema' import { calculateCost } from '@/providers/utils' const logger = createLogger('VectorSearchAPI') function getTagFilters(filters: Record, embedding: any) { return Object.entries(filters).map(([key, value]) => { - switch (key) { - case 'tag1': - return sql`LOWER(${embedding.tag1}) = LOWER(${value})` - case 'tag2': - return sql`LOWER(${embedding.tag2}) = LOWER(${value})` - case 'tag3': - return sql`LOWER(${embedding.tag3}) = LOWER(${value})` - case 'tag4': - return sql`LOWER(${embedding.tag4}) = LOWER(${value})` - case 'tag5': - return sql`LOWER(${embedding.tag5}) = LOWER(${value})` - case 'tag6': - return sql`LOWER(${embedding.tag6}) = LOWER(${value})` - case 'tag7': - return sql`LOWER(${embedding.tag7}) = LOWER(${value})` - default: - return sql`1=1` // No-op for unknown keys + // Handle OR logic within same tag + const values = value.includes('|OR|') ? value.split('|OR|') : [value] + logger.debug(`[getTagFilters] Processing ${key}="${value}" -> values:`, values) + + const getColumnForKey = (key: string) => { + switch (key) { + case 'tag1': + return embedding.tag1 + case 'tag2': + return embedding.tag2 + case 'tag3': + return embedding.tag3 + case 'tag4': + return embedding.tag4 + case 'tag5': + return embedding.tag5 + case 'tag6': + return embedding.tag6 + case 'tag7': + return embedding.tag7 + default: + return null + } + } + + const column = getColumnForKey(key) + if (!column) return sql`1=1` // No-op for unknown keys + + if (values.length === 1) { + // Single value - simple equality + logger.debug(`[getTagFilters] Single value filter: ${key} = ${values[0]}`) + return sql`LOWER(${column}) = LOWER(${values[0]})` } + // Multiple values - OR logic + logger.debug(`[getTagFilters] OR filter: ${key} IN (${values.join(', ')})`) + const orConditions = values.map((v) => sql`LOWER(${column}) = LOWER(${v})`) + return sql`(${sql.join(orConditions, sql` OR `)})` }) } @@ -53,17 +73,7 @@ const VectorSearchSchema = z.object({ ]), query: z.string().min(1, 'Search query is required'), topK: z.number().min(1).max(100).default(10), - filters: z - .object({ - tag1: z.string().optional(), - tag2: z.string().optional(), - tag3: z.string().optional(), - tag4: z.string().optional(), - tag5: z.string().optional(), - tag6: z.string().optional(), - tag7: z.string().optional(), - }) - .optional(), + filters: z.record(z.string()).optional(), // Allow dynamic filter keys (display names) }) async function generateSearchEmbedding(query: string): Promise { @@ -187,6 +197,7 @@ async function executeSingleQuery( distanceThreshold: number, filters?: Record ) { + logger.debug(`[executeSingleQuery] Called with filters:`, filters) return await db .select({ id: embedding.id, @@ -201,6 +212,7 @@ async function executeSingleQuery( tag6: embedding.tag6, tag7: embedding.tag7, distance: sql`${embedding.embedding} <=> ${queryVector}::vector`.as('distance'), + knowledgeBaseId: embedding.knowledgeBaseId, }) .from(embedding) .where( @@ -208,28 +220,7 @@ async function executeSingleQuery( inArray(embedding.knowledgeBaseId, knowledgeBaseIds), eq(embedding.enabled, true), sql`${embedding.embedding} <=> ${queryVector}::vector < ${distanceThreshold}`, - ...(filters - ? Object.entries(filters).map(([key, value]) => { - switch (key) { - case 'tag1': - return sql`LOWER(${embedding.tag1}) = LOWER(${value})` - case 'tag2': - return sql`LOWER(${embedding.tag2}) = LOWER(${value})` - case 'tag3': - return sql`LOWER(${embedding.tag3}) = LOWER(${value})` - case 'tag4': - return sql`LOWER(${embedding.tag4}) = LOWER(${value})` - case 'tag5': - return sql`LOWER(${embedding.tag5}) = LOWER(${value})` - case 'tag6': - return sql`LOWER(${embedding.tag6}) = LOWER(${value})` - case 'tag7': - return sql`LOWER(${embedding.tag7}) = LOWER(${value})` - default: - return sql`1=1` // No-op for unknown keys - } - }) - : []) + ...(filters ? getTagFilters(filters, embedding) : []) ) ) .orderBy(sql`${embedding.embedding} <=> ${queryVector}::vector`) @@ -271,6 +262,54 @@ export async function POST(request: NextRequest) { } } + // Map display names to tag slots for filtering + let mappedFilters: Record = {} + if (validatedData.filters && accessibleKbIds.length > 0) { + try { + // Fetch tag definitions for the first accessible KB (since we're using single KB now) + const kbId = accessibleKbIds[0] + const tagDefs = await db + .select({ + tagSlot: knowledgeBaseTagDefinitions.tagSlot, + displayName: knowledgeBaseTagDefinitions.displayName, + }) + .from(knowledgeBaseTagDefinitions) + .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, kbId)) + + logger.debug(`[${requestId}] Found tag definitions:`, tagDefs) + logger.debug(`[${requestId}] Original filters:`, validatedData.filters) + + // Create mapping from display name to tag slot + const displayNameToSlot: Record = {} + tagDefs.forEach((def) => { + displayNameToSlot[def.displayName] = def.tagSlot + }) + + // Map the filters and handle OR logic + Object.entries(validatedData.filters).forEach(([key, value]) => { + if (value) { + const tagSlot = displayNameToSlot[key] || key // Fallback to key if no mapping found + + // Check if this is an OR filter (contains |OR| separator) + if (value.includes('|OR|')) { + logger.debug( + `[${requestId}] OR filter detected: "${key}" -> "${tagSlot}" = "${value}"` + ) + } + + mappedFilters[tagSlot] = value + logger.debug(`[${requestId}] Mapped filter: "${key}" -> "${tagSlot}" = "${value}"`) + } + }) + + logger.debug(`[${requestId}] Final mapped filters:`, mappedFilters) + } catch (error) { + logger.error(`[${requestId}] Filter mapping error:`, error) + // If mapping fails, use original filters + mappedFilters = validatedData.filters + } + } + if (accessibleKbIds.length === 0) { return NextResponse.json( { error: 'Knowledge base not found or access denied' }, @@ -299,22 +338,24 @@ export async function POST(request: NextRequest) { if (strategy.useParallel) { // Execute parallel queries for better performance with many KBs + logger.debug(`[${requestId}] Executing parallel queries with filters:`, mappedFilters) const parallelResults = await executeParallelQueries( accessibleKbIds, queryVector, validatedData.topK, strategy.distanceThreshold, - validatedData.filters + mappedFilters ) results = mergeAndRankResults(parallelResults, validatedData.topK) } else { // Execute single optimized query for fewer KBs + logger.debug(`[${requestId}] Executing single query with filters:`, mappedFilters) results = await executeSingleQuery( accessibleKbIds, queryVector, validatedData.topK, strategy.distanceThreshold, - validatedData.filters + mappedFilters ) } @@ -331,23 +372,64 @@ export async function POST(request: NextRequest) { // Continue without cost information rather than failing the search } + // Fetch tag definitions for display name mapping (reuse the same fetch from filtering) + const tagDefinitionsMap: Record> = {} + for (const kbId of accessibleKbIds) { + try { + const tagDefs = await db + .select({ + tagSlot: knowledgeBaseTagDefinitions.tagSlot, + displayName: knowledgeBaseTagDefinitions.displayName, + }) + .from(knowledgeBaseTagDefinitions) + .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, kbId)) + + tagDefinitionsMap[kbId] = {} + tagDefs.forEach((def) => { + tagDefinitionsMap[kbId][def.tagSlot] = def.displayName + }) + logger.debug( + `[${requestId}] Display mapping - KB ${kbId} tag definitions:`, + tagDefinitionsMap[kbId] + ) + } catch (error) { + logger.warn(`[${requestId}] Failed to fetch tag definitions for display mapping:`, error) + tagDefinitionsMap[kbId] = {} + } + } + return NextResponse.json({ success: true, data: { - results: results.map((result) => ({ - id: result.id, - content: result.content, - documentId: result.documentId, - chunkIndex: result.chunkIndex, - tag1: result.tag1, - tag2: result.tag2, - tag3: result.tag3, - tag4: result.tag4, - tag5: result.tag5, - tag6: result.tag6, - tag7: result.tag7, - similarity: 1 - result.distance, - })), + results: results.map((result) => { + const kbTagMap = tagDefinitionsMap[result.knowledgeBaseId] || {} + logger.debug( + `[${requestId}] Result KB: ${result.knowledgeBaseId}, available mappings:`, + kbTagMap + ) + + // Create tags object with display names + const tags: Record = {} + + TAG_SLOTS.forEach((slot) => { + if (result[slot]) { + const displayName = kbTagMap[slot] || slot + logger.debug( + `[${requestId}] Mapping ${slot}="${result[slot]}" -> "${displayName}"="${result[slot]}"` + ) + tags[displayName] = result[slot] + } + }) + + return { + id: result.id, + content: result.content, + documentId: result.documentId, + chunkIndex: result.chunkIndex, + tags, // Clean display name mapped tags + similarity: 1 - result.distance, + } + }), query: validatedData.query, knowledgeBaseIds: accessibleKbIds, knowledgeBaseId: accessibleKbIds[0], diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx index 25ac0dad27..901b0a74bb 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx @@ -11,6 +11,7 @@ import { TooltipContent, TooltipTrigger, } from '@/components/ui' +import { TAG_SLOTS } from '@/lib/constants/knowledge' import { createLogger } from '@/lib/logs/console/logger' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/components/providers/workspace-permissions-provider' import { @@ -21,7 +22,12 @@ import { } from '@/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components' import { ActionBar } from '@/app/workspace/[workspaceId]/knowledge/[id]/components' import { KnowledgeHeader, SearchInput } from '@/app/workspace/[workspaceId]/knowledge/components' +import { + type DocumentTag, + DocumentTagEntry, +} from '@/app/workspace/[workspaceId]/knowledge/components/document-tag-entry/document-tag-entry' import { useDocumentChunks } from '@/hooks/use-knowledge' +import { useTagDefinitions } from '@/hooks/use-tag-definitions' import { type ChunkData, type DocumentData, useKnowledgeStore } from '@/stores/knowledge/store' const logger = createLogger('Document') @@ -50,7 +56,11 @@ export function Document({ knowledgeBaseName, documentName, }: DocumentProps) { - const { getCachedKnowledgeBase, getCachedDocuments } = useKnowledgeStore() + const { + getCachedKnowledgeBase, + getCachedDocuments, + updateDocument: updateDocumentInStore, + } = useKnowledgeStore() const { workspaceId } = useParams() const router = useRouter() const searchParams = useSearchParams() @@ -60,7 +70,6 @@ export function Document({ const { chunks: paginatedChunks, allChunks, - filteredChunks, searchQuery, setSearchQuery, currentPage, @@ -81,15 +90,102 @@ export function Document({ const [selectedChunks, setSelectedChunks] = useState>(new Set()) const [selectedChunk, setSelectedChunk] = useState(null) const [isModalOpen, setIsModalOpen] = useState(false) + + const [documentTags, setDocumentTags] = useState([]) + const [documentData, setDocumentData] = useState(null) + const [isLoadingDocument, setIsLoadingDocument] = useState(true) + const [error, setError] = useState(null) + + // Use tag definitions hook for custom labels + const { tagDefinitions, fetchTagDefinitions } = useTagDefinitions(knowledgeBaseId, documentId) + + // Function to build document tags from data and definitions + const buildDocumentTags = useCallback( + (docData: DocumentData, definitions: any[], currentTags?: DocumentTag[]) => { + const tags: DocumentTag[] = [] + const tagSlots = TAG_SLOTS + + tagSlots.forEach((slot) => { + const value = (docData as any)[slot] as string | null | undefined + const definition = definitions.find((def) => def.tagSlot === slot) + const currentTag = currentTags?.find((tag) => tag.slot === slot) + + // Only include tag if the document actually has a value for it + if (value?.trim()) { + tags.push({ + slot, + // Preserve existing displayName if definition is not found yet + displayName: definition?.displayName || currentTag?.displayName || '', + fieldType: definition?.fieldType || currentTag?.fieldType || 'text', + value: value.trim(), + }) + } + }) + + return tags + }, + [] + ) + + // Handle tag updates (local state only, no API calls) + const handleTagsChange = useCallback((newTags: DocumentTag[]) => { + // Only update local state, don't save to API + setDocumentTags(newTags) + }, []) + + // Handle saving document tag values to the API + const handleSaveDocumentTags = useCallback( + async (tagsToSave: DocumentTag[]) => { + if (!documentData) return + + try { + // Convert DocumentTag array to tag data for API + const tagData: Record = {} + const tagSlots = TAG_SLOTS + + // Clear all tags first + tagSlots.forEach((slot) => { + tagData[slot] = '' + }) + + // Set values from tagsToSave + tagsToSave.forEach((tag) => { + if (tag.value.trim()) { + tagData[tag.slot] = tag.value.trim() + } + }) + + // Update document via API + const response = await fetch(`/api/knowledge/${knowledgeBaseId}/documents/${documentId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(tagData), + }) + + if (!response.ok) { + throw new Error('Failed to update document tags') + } + + // Update the document in the store and local state + updateDocumentInStore(knowledgeBaseId, documentId, tagData) + setDocumentData((prev) => (prev ? { ...prev, ...tagData } : null)) + + // Refresh tag definitions to update the display + await fetchTagDefinitions() + } catch (error) { + logger.error('Error updating document tags:', error) + throw error // Re-throw so the component can handle it + } + }, + [documentData, knowledgeBaseId, documentId, updateDocumentInStore, fetchTagDefinitions] + ) const [isCreateChunkModalOpen, setIsCreateChunkModalOpen] = useState(false) const [chunkToDelete, setChunkToDelete] = useState(null) const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) const [isBulkOperating, setIsBulkOperating] = useState(false) - const [document, setDocument] = useState(null) - const [isLoadingDocument, setIsLoadingDocument] = useState(true) - const [error, setError] = useState(null) - const combinedError = error || chunksError // URL synchronization for pagination @@ -121,7 +217,10 @@ export function Document({ const cachedDoc = cachedDocuments?.documents?.find((d) => d.id === documentId) if (cachedDoc) { - setDocument(cachedDoc) + setDocumentData(cachedDoc) + // Initialize tags from cached document + const initialTags = buildDocumentTags(cachedDoc, tagDefinitions) + setDocumentTags(initialTags) setIsLoadingDocument(false) return } @@ -138,7 +237,10 @@ export function Document({ const result = await response.json() if (result.success) { - setDocument(result.data) + setDocumentData(result.data) + // Initialize tags from fetched document + const initialTags = buildDocumentTags(result.data, tagDefinitions, []) + setDocumentTags(initialTags) } else { throw new Error(result.error || 'Failed to fetch document') } @@ -153,11 +255,19 @@ export function Document({ if (knowledgeBaseId && documentId) { fetchDocument() } - }, [knowledgeBaseId, documentId, getCachedDocuments]) + }, [knowledgeBaseId, documentId, getCachedDocuments, buildDocumentTags]) + + // Separate effect to rebuild tags when tag definitions change (without re-fetching document) + useEffect(() => { + if (documentData) { + const rebuiltTags = buildDocumentTags(documentData, tagDefinitions, documentTags) + setDocumentTags(rebuiltTags) + } + }, [documentData, tagDefinitions, buildDocumentTags]) const knowledgeBase = getCachedKnowledgeBase(knowledgeBaseId) const effectiveKnowledgeBaseName = knowledgeBase?.name || knowledgeBaseName || 'Knowledge Base' - const effectiveDocumentName = document?.filename || documentName || 'Document' + const effectiveDocumentName = documentData?.filename || documentName || 'Document' const breadcrumbs = [ { label: 'Knowledge', href: `/workspace/${workspaceId}/knowledge` }, @@ -254,7 +364,7 @@ export function Document({ } } - const handleChunkCreated = async (newChunk: ChunkData) => { + const handleChunkCreated = async () => { // Refresh the chunks list to include the new chunk await refreshChunks() } @@ -396,16 +506,16 @@ export function Document({ value={searchQuery} onChange={setSearchQuery} placeholder={ - document?.processingStatus === 'completed' + documentData?.processingStatus === 'completed' ? 'Search chunks...' : 'Document processing...' } - disabled={document?.processingStatus !== 'completed'} + disabled={documentData?.processingStatus !== 'completed'} /> - {/* Document Tags Display */} - {document && - (() => { - const tags = [ - { label: 'Tag 1', value: document.tag1 }, - { label: 'Tag 2', value: document.tag2 }, - { label: 'Tag 3', value: document.tag3 }, - { label: 'Tag 4', value: document.tag4 }, - { label: 'Tag 5', value: document.tag5 }, - { label: 'Tag 6', value: document.tag6 }, - { label: 'Tag 7', value: document.tag7 }, - ].filter((tag) => tag.value?.trim()) - - return tags.length > 0 ? ( -
-

Document Tags:

-
- {tags.map((tag, index) => ( - - {tag.label}: - {tag.value} - - ))} -
-
- ) : null - })()} + {/* Document Tag Entry */} + {userPermissions.canEdit && ( +
+ +
+ )} {/* Error State for chunks */} {combinedError && !isLoadingAllChunks && ( @@ -472,7 +565,8 @@ export function Document({ checked={isAllSelected} onCheckedChange={handleSelectAll} disabled={ - document?.processingStatus !== 'completed' || !userPermissions.canEdit + documentData?.processingStatus !== 'completed' || + !userPermissions.canEdit } aria-label='Select all chunks' className='h-3.5 w-3.5 border-gray-300 focus-visible:ring-[#701FFC]/20 data-[state=checked]:border-[#701FFC] data-[state=checked]:bg-[#701FFC] [&>*]:h-3 [&>*]:w-3' @@ -514,7 +608,7 @@ export function Document({ - {document?.processingStatus !== 'completed' ? ( + {documentData?.processingStatus !== 'completed' ? (
@@ -526,13 +620,13 @@ export function Document({
- {document?.processingStatus === 'pending' && + {documentData?.processingStatus === 'pending' && 'Document processing pending...'} - {document?.processingStatus === 'processing' && + {documentData?.processingStatus === 'processing' && 'Document processing in progress...'} - {document?.processingStatus === 'failed' && + {documentData?.processingStatus === 'failed' && 'Document processing failed'} - {!document?.processingStatus && 'Document not ready'} + {!documentData?.processingStatus && 'Document not ready'}
@@ -558,7 +652,7 @@ export function Document({
- {document?.processingStatus === 'completed' + {documentData?.processingStatus === 'completed' ? searchQuery.trim() ? 'No chunks match your search' : 'No chunks found' @@ -708,7 +802,7 @@ export function Document({
{/* Pagination Controls */} - {document?.processingStatus === 'completed' && totalPages > 1 && ( + {documentData?.processingStatus === 'completed' && totalPages > 1 && (
+
+ ))} +
+ + {/* Add Tag Dropdown Selector */} + + + + + + {/* Existing tag names */} + {availableTagNames.length > 0 && ( + <> + {availableTagNames.map((tagName) => { + const tagDefinition = kbTagDefinitions.find((def) => def.displayName === tagName) + return ( + handleSuggestionClick(tagName)} + className='flex items-center justify-between' + > + {tagName} + + {tagDefinition?.fieldType || 'text'} + + + ) + })} +
+ + )} + + {/* Create new tag option or disabled message */} + {canAddMoreTags ? ( + { + setEditingTag({ index: -1, value: '', tagName: '', isNew: true }) + }} + className='flex items-center gap-2 text-blue-600' + > + + Create new tag + + ) : ( +
+ All {MAX_TAG_SLOTS} tag slots used in this knowledge base +
+ )} + + + + {/* Edit Tag Value Modal */} + {editingTag !== null && ( + t.displayName === editingTag.tagName)?.fieldType + } + onSave={(value, type, newTagName) => { + if (editingTag.index === -1) { + // Creating new tag - use newTagName if provided, otherwise fall back to editingTag.tagName + const tagName = newTagName || editingTag.tagName + handleCreateNewTag(tagName, value, type) + } else { + // Updating existing tag + handleUpdateTag(editingTag.index, value) + } + setEditingTag(null) + }} + onCancel={() => { + setEditingTag(null) + }} + /> + )} + + {/* Tag count display */} + {kbTagDefinitions.length > 0 && ( +
+ {kbTagDefinitions.length} of {MAX_TAG_SLOTS} tag slots used in this knowledge base +
+ )} +
+ ) +} + +// Simple modal for editing tag values +interface EditTagModalProps { + tagName: string + initialValue: string + isNew: boolean + existingType?: string + onSave: (value: string, type?: string, newTagName?: string) => void + onCancel: () => void +} + +function EditTagModal({ + tagName, + initialValue, + isNew, + existingType, + onSave, + onCancel, +}: EditTagModalProps) { + const [value, setValue] = useState(initialValue) + const [fieldType, setFieldType] = useState(existingType || 'text') + const [newTagName, setNewTagName] = useState(tagName) + const inputRef = useRef(null) + + useEffect(() => { + inputRef.current?.focus() + }, []) + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (value.trim() && (isNew ? newTagName.trim() : true)) { + onSave(value.trim(), fieldType, isNew ? newTagName.trim() : undefined) + } + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + onCancel() + } + } + + return ( +
+
+
+

+ {isNew ? 'Create new tag' : `Edit "${tagName}" value`} +

+ {/* Type Badge in Top Right */} + {!isNew && existingType && ( + + {existingType.toUpperCase()} + + )} +
+
+ {/* Tag Name Input for New Tags */} + {isNew && ( +
+ + setNewTagName(e.target.value)} + placeholder='Enter tag name' + className='mt-1 text-sm' + /> +
+ )} + + {/* Type Selection for New Tags */} + {isNew && ( +
+ + +
+ )} + + {/* Value Input */} +
+ + setValue(e.target.value)} + onKeyDown={handleKeyDown} + placeholder='Enter tag value' + className='mt-1 text-sm' + /> +
+ +
+ + +
+
+
+
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/tag-input/tag-input.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/tag-input/tag-input.tsx index 3489714e0f..1657967dbc 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/tag-input/tag-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/tag-input/tag-input.tsx @@ -6,15 +6,11 @@ import { Button } from '@/components/ui/button' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import { TAG_SLOTS, type TagSlot } from '@/lib/constants/knowledge' +import { useKnowledgeBaseTagDefinitions } from '@/hooks/use-knowledge-base-tag-definitions' -export interface TagData { - tag1?: string - tag2?: string - tag3?: string - tag4?: string - tag5?: string - tag6?: string - tag7?: string +export type TagData = { + [K in TagSlot]?: string } interface TagInputProps { @@ -22,22 +18,30 @@ interface TagInputProps { onTagsChange: (tags: TagData) => void disabled?: boolean className?: string + knowledgeBaseId?: string | null + documentId?: string | null } -const TAG_LABELS = [ - { key: 'tag1' as keyof TagData, label: 'Tag 1', placeholder: 'Enter tag value' }, - { key: 'tag2' as keyof TagData, label: 'Tag 2', placeholder: 'Enter tag value' }, - { key: 'tag3' as keyof TagData, label: 'Tag 3', placeholder: 'Enter tag value' }, - { key: 'tag4' as keyof TagData, label: 'Tag 4', placeholder: 'Enter tag value' }, - { key: 'tag5' as keyof TagData, label: 'Tag 5', placeholder: 'Enter tag value' }, - { key: 'tag6' as keyof TagData, label: 'Tag 6', placeholder: 'Enter tag value' }, - { key: 'tag7' as keyof TagData, label: 'Tag 7', placeholder: 'Enter tag value' }, -] - -export function TagInput({ tags, onTagsChange, disabled = false, className = '' }: TagInputProps) { +const TAG_LABELS = TAG_SLOTS.map((slot, index) => ({ + key: slot as keyof TagData, + label: `Tag ${index + 1}`, + placeholder: 'Enter tag value', +})) + +export function TagInput({ + tags, + onTagsChange, + disabled = false, + className = '', + knowledgeBaseId = null, + documentId = null, +}: TagInputProps) { const [isOpen, setIsOpen] = useState(false) const [showAllTags, setShowAllTags] = useState(false) + // Use custom tag definitions if available + const { getTagLabel } = useKnowledgeBaseTagDefinitions(knowledgeBaseId) + const handleTagChange = (tagKey: keyof TagData, value: string) => { onTagsChange({ ...tags, @@ -53,7 +57,15 @@ export function TagInput({ tags, onTagsChange, disabled = false, className = '' } const hasAnyTags = Object.values(tags).some((tag) => tag?.trim()) - const visibleTags = showAllTags ? TAG_LABELS : TAG_LABELS.slice(0, 2) + + // Create tag labels using custom definitions or fallback to defaults + const tagLabels = TAG_LABELS.map(({ key, placeholder }) => ({ + key, + label: getTagLabel(key), + placeholder, + })) + + const visibleTags = showAllTags ? tagLabels : tagLabels.slice(0, 2) return (
@@ -153,7 +165,7 @@ export function TagInput({ tags, onTagsChange, disabled = false, className = ''
{Object.entries(tags).map(([key, value]) => { if (!value?.trim()) return null - const tagLabel = TAG_LABELS.find((t) => t.key === key)?.label || key + const tagLabel = getTagLabel(key) return ( ({ ...file, - // Extract tags from file if they exist (added by upload modal) - tag1: (file as any).tag1, - tag2: (file as any).tag2, - tag3: (file as any).tag3, - tag4: (file as any).tag4, - tag5: (file as any).tag5, - tag6: (file as any).tag6, - tag7: (file as any).tag7, + // Tags are already included in the file object from createUploadedFile })), processingOptions: { chunkSize: processingOptions.chunkSize || 1024, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/document-tag-entry/document-tag-entry.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/document-tag-entry/document-tag-entry.tsx new file mode 100644 index 0000000000..916b461bd6 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/document-tag-entry/document-tag-entry.tsx @@ -0,0 +1,218 @@ +'use client' + +import { Plus, X } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { MAX_TAG_SLOTS } from '@/lib/constants/knowledge' +import type { SubBlockConfig } from '@/blocks/types' +import { useKnowledgeBaseTagDefinitions } from '@/hooks/use-knowledge-base-tag-definitions' +import { useSubBlockValue } from '../../hooks/use-sub-block-value' + +interface DocumentTag { + id: string + tagName: string // This will be mapped to displayName for API + fieldType: string + value: string +} + +interface DocumentTagEntryProps { + blockId: string + subBlock: SubBlockConfig + disabled?: boolean + isPreview?: boolean + previewValue?: any + isConnecting?: boolean +} + +export function DocumentTagEntry({ + blockId, + subBlock, + disabled = false, + isPreview = false, + previewValue, + isConnecting = false, +}: DocumentTagEntryProps) { + const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id) + + // Get the knowledge base ID from other sub-blocks + const [knowledgeBaseIdValue] = useSubBlockValue(blockId, 'knowledgeBaseId') + const knowledgeBaseId = knowledgeBaseIdValue || null + + // Use KB tag definitions hook to get available tags + const { tagDefinitions, isLoading } = useKnowledgeBaseTagDefinitions(knowledgeBaseId) + + // Parse the current value to extract tags + const parseTags = (tagValue: string): DocumentTag[] => { + if (!tagValue) return [] + try { + return JSON.parse(tagValue) + } catch { + return [] + } + } + + const currentValue = isPreview ? previewValue : storeValue + const tags = parseTags(currentValue || '') + + const updateTags = (newTags: DocumentTag[]) => { + if (isPreview) return + const value = newTags.length > 0 ? JSON.stringify(newTags) : null + setStoreValue(value) + } + + const removeTag = (tagId: string) => { + updateTags(tags.filter((t) => t.id !== tagId)) + } + + const updateTag = (tagId: string, updates: Partial) => { + updateTags(tags.map((tag) => (tag.id === tagId ? { ...tag, ...updates } : tag))) + } + + // Get available tag names that aren't already used + const usedTagNames = new Set(tags.map((tag) => tag.tagName).filter(Boolean)) + const availableTagNames = tagDefinitions + .map((def) => def.displayName) + .filter((name) => !usedTagNames.has(name)) + + if (isLoading) { + return
Loading tag definitions...
+ } + + return ( +
+ {/* Available Tags Section */} + {availableTagNames.length > 0 && ( +
+
+ Available Tags (click to add) +
+
+ {availableTagNames.map((tagName) => { + const tagDef = tagDefinitions.find((def) => def.displayName === tagName) + return ( + + ) + })} +
+
+ )} + + {/* Selected Tags Section */} + {tags.length > 0 && ( +
+
+ {tags.map((tag) => ( +
+ {/* Tag Name */} +
+
+ {tag.tagName || 'Unnamed Tag'} +
+
{tag.fieldType}
+
+ + {/* Value Input */} +
+ updateTag(tag.id, { value: e.target.value })} + placeholder='Value' + disabled={disabled || isConnecting} + className='h-9 placeholder:text-xs' + type={tag.fieldType === 'number' ? 'number' : 'text'} + /> +
+ + {/* Remove Button */} + +
+ ))} +
+
+ )} + + {/* Create New Tag Section */} +
+
Create New Tag
+
+
+ = MAX_TAG_SLOTS ? '' : 'Tag name'} + disabled={disabled || isConnecting || tagDefinitions.length >= MAX_TAG_SLOTS} + className='h-9 border-0 bg-transparent p-0 placeholder:text-xs focus-visible:ring-0' + onKeyDown={(e) => { + if (e.key === 'Enter' && e.currentTarget.value.trim()) { + const tagName = e.currentTarget.value.trim() + + // Check for duplicates + if (usedTagNames.has(tagName)) { + // Visual feedback for duplicate - could add toast notification here + e.currentTarget.style.borderColor = '#ef4444' + setTimeout(() => { + e.currentTarget.style.borderColor = '' + }, 1000) + return + } + + const newTag: DocumentTag = { + id: Date.now().toString(), + tagName, + fieldType: 'text', + value: '', + } + updateTags([...tags, newTag]) + e.currentTarget.value = '' + } + }} + /> +
+
+ {tagDefinitions.length >= MAX_TAG_SLOTS + ? `All ${MAX_TAG_SLOTS} tag slots used in this knowledge base` + : usedTagNames.size > 0 + ? 'Press Enter (no duplicates)' + : 'Press Enter to add'} +
+
+
+ + {/* Empty State */} + {tags.length === 0 && availableTagNames.length === 0 && ( +
+
No tags available
+
Create a new tag above to get started
+
+ )} +
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/knowledge-tag-filter/knowledge-tag-filter.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/knowledge-tag-filter/knowledge-tag-filter.tsx new file mode 100644 index 0000000000..86a45ef2b7 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/knowledge-tag-filter/knowledge-tag-filter.tsx @@ -0,0 +1,118 @@ +'use client' + +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import type { SubBlockConfig } from '@/blocks/types' +import { useKnowledgeBaseTagDefinitions } from '@/hooks/use-knowledge-base-tag-definitions' +import { useSubBlockValue } from '../../hooks/use-sub-block-value' + +interface KnowledgeTagFilterProps { + blockId: string + subBlock: SubBlockConfig + disabled?: boolean + isPreview?: boolean + previewValue?: string | null + isConnecting?: boolean +} + +export function KnowledgeTagFilter({ + blockId, + subBlock, + disabled = false, + isPreview = false, + previewValue, + isConnecting = false, +}: KnowledgeTagFilterProps) { + const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id) + + // Get the knowledge base ID and document ID from other sub-blocks + const [knowledgeBaseIdValue] = useSubBlockValue(blockId, 'knowledgeBaseIds') + const [knowledgeBaseIdSingleValue] = useSubBlockValue(blockId, 'knowledgeBaseId') + const [documentIdValue] = useSubBlockValue(blockId, 'documentId') + + // Determine which knowledge base ID to use + const knowledgeBaseId = + knowledgeBaseIdSingleValue || + (typeof knowledgeBaseIdValue === 'string' ? knowledgeBaseIdValue.split(',')[0] : null) + + // Use KB tag definitions hook to get available tags + const { tagDefinitions, isLoading, getTagLabel } = useKnowledgeBaseTagDefinitions(knowledgeBaseId) + + // Parse the current value to extract tag name and value + const parseTagFilter = (filterValue: string) => { + if (!filterValue) return { tagName: '', tagValue: '' } + const [tagName, ...valueParts] = filterValue.split(':') + return { tagName: tagName?.trim() || '', tagValue: valueParts.join(':').trim() || '' } + } + + const currentValue = isPreview ? previewValue : storeValue + const { tagName, tagValue } = parseTagFilter(currentValue || '') + + const handleTagNameChange = (newTagName: string) => { + if (isPreview) return + const newValue = + newTagName && tagValue ? `${newTagName}:${tagValue}` : newTagName || tagValue || '' + setStoreValue(newValue.trim() || null) + } + + const handleTagValueChange = (newTagValue: string) => { + if (isPreview) return + const newValue = + tagName && newTagValue ? `${tagName}:${newTagValue}` : tagName || newTagValue || '' + setStoreValue(newValue.trim() || null) + } + + if (isPreview) { + return ( +
+ + +
+ ) + } + + return ( +
+ {/* Tag Name Selector */} + + + {/* Tag Value Input - only show if tag is selected */} + {tagName && ( + handleTagValueChange(e.target.value)} + placeholder={`Enter ${tagName} value`} + disabled={disabled || isConnecting} + className='text-sm' + /> + )} +
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx new file mode 100644 index 0000000000..a8074232ca --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx @@ -0,0 +1,169 @@ +'use client' + +import { Plus, X } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import type { SubBlockConfig } from '@/blocks/types' +import { useKnowledgeBaseTagDefinitions } from '@/hooks/use-knowledge-base-tag-definitions' +import { useSubBlockValue } from '../../hooks/use-sub-block-value' + +interface TagFilter { + id: string + tagName: string + tagValue: string +} + +interface KnowledgeTagFiltersProps { + blockId: string + subBlock: SubBlockConfig + disabled?: boolean + isPreview?: boolean + previewValue?: string | null + isConnecting?: boolean +} + +export function KnowledgeTagFilters({ + blockId, + subBlock, + disabled = false, + isPreview = false, + previewValue, + isConnecting = false, +}: KnowledgeTagFiltersProps) { + const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id) + + // Get the knowledge base ID from other sub-blocks + const [knowledgeBaseIdValue] = useSubBlockValue(blockId, 'knowledgeBaseId') + const knowledgeBaseId = knowledgeBaseIdValue || null + + // Use KB tag definitions hook to get available tags + const { tagDefinitions, isLoading } = useKnowledgeBaseTagDefinitions(knowledgeBaseId) + + // Parse the current value to extract filters + const parseFilters = (filterValue: string): TagFilter[] => { + if (!filterValue) return [] + try { + return JSON.parse(filterValue) + } catch { + return [] + } + } + + const currentValue = isPreview ? previewValue : storeValue + const filters = parseFilters(currentValue || '') + + const updateFilters = (newFilters: TagFilter[]) => { + if (isPreview) return + const value = newFilters.length > 0 ? JSON.stringify(newFilters) : null + setStoreValue(value) + } + + const addFilter = () => { + const newFilter: TagFilter = { + id: Date.now().toString(), + tagName: '', + tagValue: '', + } + updateFilters([...filters, newFilter]) + } + + const removeFilter = (filterId: string) => { + updateFilters(filters.filter((f) => f.id !== filterId)) + } + + const updateFilter = (filterId: string, field: keyof TagFilter, value: string) => { + updateFilters(filters.map((f) => (f.id === filterId ? { ...f, [field]: value } : f))) + } + + if (isPreview) { + return ( +
+ +
+ {filters.length > 0 ? `${filters.length} filter(s)` : 'No filters'} +
+
+ ) + } + + return ( +
+
+ +
+ + {filters.length === 0 && ( +
+ No tag filters. Click "Add Filter" to add one. +
+ )} + +
+ {filters.map((filter) => ( +
+ {/* Tag Name Selector */} +
+ +
+ + {/* Tag Value Input */} +
+ updateFilter(filter.id, 'tagValue', e.target.value)} + placeholder={filter.tagName ? `Enter ${filter.tagName} value` : 'Enter value'} + disabled={disabled || isConnecting} + className='h-8 text-sm' + /> +
+ + {/* Remove Button */} + +
+ ))} +
+
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx index db826c9c46..ef58591c80 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx @@ -32,6 +32,9 @@ import { import { getBlock } from '@/blocks/index' import type { SubBlockConfig } from '@/blocks/types' import { useWorkflowStore } from '@/stores/workflows/workflow/store' +import { DocumentTagEntry } from './components/document-tag-entry/document-tag-entry' +import { KnowledgeTagFilter } from './components/knowledge-tag-filter/knowledge-tag-filter' +import { KnowledgeTagFilters } from './components/knowledge-tag-filters/knowledge-tag-filters' interface SubBlockProps { blockId: string @@ -353,6 +356,40 @@ export function SubBlock({ previewValue={previewValue} /> ) + case 'knowledge-tag-filter': + return ( + + ) + case 'knowledge-tag-filters': + return ( + + ) + + case 'document-tag-entry': + return ( + + ) case 'document-selector': return ( { // Validate required fields for each operation - if (params.operation === 'search' && !params.knowledgeBaseIds) { - throw new Error('Knowledge base IDs are required for search operation') + if (params.operation === 'search' && !params.knowledgeBaseId) { + throw new Error('Knowledge base ID is required for search operation') } if ( (params.operation === 'upload_chunk' || params.operation === 'create_document') && @@ -49,21 +49,16 @@ export const KnowledgeBlock: BlockConfig = { }, inputs: { operation: { type: 'string', required: true }, - knowledgeBaseIds: { type: 'string', required: false }, knowledgeBaseId: { type: 'string', required: false }, query: { type: 'string', required: false }, topK: { type: 'number', required: false }, documentId: { type: 'string', required: false }, content: { type: 'string', required: false }, name: { type: 'string', required: false }, - // Tag filters for search - tag1: { type: 'string', required: false }, - tag2: { type: 'string', required: false }, - tag3: { type: 'string', required: false }, - tag4: { type: 'string', required: false }, - tag5: { type: 'string', required: false }, - tag6: { type: 'string', required: false }, - tag7: { type: 'string', required: false }, + // Dynamic tag filters for search + tagFilters: { type: 'string', required: false }, + // Document tags for create document (JSON string of tag objects) + documentTags: { type: 'string', required: false }, }, outputs: { results: 'json', @@ -83,15 +78,6 @@ export const KnowledgeBlock: BlockConfig = { ], value: () => 'search', }, - { - id: 'knowledgeBaseIds', - title: 'Knowledge Bases', - type: 'knowledge-base-selector', - layout: 'full', - placeholder: 'Select knowledge bases', - multiSelect: true, - condition: { field: 'operation', value: 'search' }, - }, { id: 'knowledgeBaseId', title: 'Knowledge Base', @@ -99,7 +85,7 @@ export const KnowledgeBlock: BlockConfig = { layout: 'full', placeholder: 'Select knowledge base', multiSelect: false, - condition: { field: 'operation', value: ['upload_chunk', 'create_document'] }, + condition: { field: 'operation', value: ['search', 'upload_chunk', 'create_document'] }, }, { id: 'query', @@ -118,65 +104,11 @@ export const KnowledgeBlock: BlockConfig = { condition: { field: 'operation', value: 'search' }, }, { - id: 'tag1', - title: 'Tag 1 Filter', - type: 'short-input', - layout: 'half', - placeholder: 'Filter by tag 1', - condition: { field: 'operation', value: 'search' }, - mode: 'advanced', - }, - { - id: 'tag2', - title: 'Tag 2 Filter', - type: 'short-input', - layout: 'half', - placeholder: 'Filter by tag 2', - condition: { field: 'operation', value: 'search' }, - mode: 'advanced', - }, - { - id: 'tag3', - title: 'Tag 3 Filter', - type: 'short-input', - layout: 'half', - placeholder: 'Filter by tag 3', - condition: { field: 'operation', value: 'search' }, - mode: 'advanced', - }, - { - id: 'tag4', - title: 'Tag 4 Filter', - type: 'short-input', - layout: 'half', - placeholder: 'Filter by tag 4', - condition: { field: 'operation', value: 'search' }, - mode: 'advanced', - }, - { - id: 'tag5', - title: 'Tag 5 Filter', - type: 'short-input', - layout: 'half', - placeholder: 'Filter by tag 5', - condition: { field: 'operation', value: 'search' }, - mode: 'advanced', - }, - { - id: 'tag6', - title: 'Tag 6 Filter', - type: 'short-input', - layout: 'half', - placeholder: 'Filter by tag 6', - condition: { field: 'operation', value: 'search' }, - mode: 'advanced', - }, - { - id: 'tag7', - title: 'Tag 7 Filter', - type: 'short-input', - layout: 'half', - placeholder: 'Filter by tag 7', + id: 'tagFilters', + title: 'Tag Filters', + type: 'knowledge-tag-filters', + layout: 'full', + placeholder: 'Add tag filters', condition: { field: 'operation', value: 'search' }, mode: 'advanced', }, @@ -214,69 +146,13 @@ export const KnowledgeBlock: BlockConfig = { rows: 6, condition: { field: 'operation', value: ['create_document'] }, }, - // Tag inputs for Create Document (in advanced mode) - { - id: 'tag1', - title: 'Tag 1', - type: 'short-input', - layout: 'half', - placeholder: 'Enter tag 1 value', - condition: { field: 'operation', value: 'create_document' }, - mode: 'advanced', - }, - { - id: 'tag2', - title: 'Tag 2', - type: 'short-input', - layout: 'half', - placeholder: 'Enter tag 2 value', - condition: { field: 'operation', value: 'create_document' }, - mode: 'advanced', - }, + // Dynamic tag entry for Create Document { - id: 'tag3', - title: 'Tag 3', - type: 'short-input', - layout: 'half', - placeholder: 'Enter tag 3 value', - condition: { field: 'operation', value: 'create_document' }, - mode: 'advanced', - }, - { - id: 'tag4', - title: 'Tag 4', - type: 'short-input', - layout: 'half', - placeholder: 'Enter tag 4 value', - condition: { field: 'operation', value: 'create_document' }, - mode: 'advanced', - }, - { - id: 'tag5', - title: 'Tag 5', - type: 'short-input', - layout: 'half', - placeholder: 'Enter tag 5 value', - condition: { field: 'operation', value: 'create_document' }, - mode: 'advanced', - }, - { - id: 'tag6', - title: 'Tag 6', - type: 'short-input', - layout: 'half', - placeholder: 'Enter tag 6 value', - condition: { field: 'operation', value: 'create_document' }, - mode: 'advanced', - }, - { - id: 'tag7', - title: 'Tag 7', - type: 'short-input', - layout: 'half', - placeholder: 'Enter tag 7 value', + id: 'documentTags', + title: 'Document Tags', + type: 'document-tag-entry', + layout: 'full', condition: { field: 'operation', value: 'create_document' }, - mode: 'advanced', }, ], } diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index bcc7a75dd5..14f2e8eae9 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -33,7 +33,10 @@ export type SubBlockType = | 'channel-selector' // Channel selector for Slack, Discord, etc. | 'folder-selector' // Folder selector for Gmail, etc. | 'knowledge-base-selector' // Knowledge base selector + | 'knowledge-tag-filter' // Dynamic tag filter for knowledge bases + | 'knowledge-tag-filters' // Multiple tag filters for knowledge bases | 'document-selector' // Document selector for knowledge bases + | 'document-tag-entry' // Document tag entry for creating documents | 'input-format' // Input structure format | 'response-format' // Response structure format | 'file-upload' // File uploader diff --git a/apps/sim/db/migrations/0063_lame_sandman.sql b/apps/sim/db/migrations/0063_lame_sandman.sql new file mode 100644 index 0000000000..3b5da7c928 --- /dev/null +++ b/apps/sim/db/migrations/0063_lame_sandman.sql @@ -0,0 +1,13 @@ +CREATE TABLE "knowledge_base_tag_definitions" ( + "id" text PRIMARY KEY NOT NULL, + "knowledge_base_id" text NOT NULL, + "tag_slot" text NOT NULL, + "display_name" text NOT NULL, + "field_type" text DEFAULT 'text' NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "knowledge_base_tag_definitions" ADD CONSTRAINT "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk" FOREIGN KEY ("knowledge_base_id") REFERENCES "public"."knowledge_base"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "kb_tag_definitions_kb_slot_idx" ON "knowledge_base_tag_definitions" USING btree ("knowledge_base_id","tag_slot");--> statement-breakpoint +CREATE INDEX "kb_tag_definitions_kb_id_idx" ON "knowledge_base_tag_definitions" USING btree ("knowledge_base_id"); \ No newline at end of file diff --git a/apps/sim/db/migrations/meta/0063_snapshot.json b/apps/sim/db/migrations/meta/0063_snapshot.json new file mode 100644 index 0000000000..3a483105c5 --- /dev/null +++ b/apps/sim/db/migrations/meta/0063_snapshot.json @@ -0,0 +1,5634 @@ +{ + "id": "d6721471-f616-4e02-bfba-e5271c59857f", + "prevId": "5fe645b1-2d33-4fd7-8144-49dc2f3a3fd6", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subdomain": { + "name": "subdomain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "subdomain_idx": { + "name": "subdomain_idx", + "columns": [ + { + "expression": "subdomain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_checkpoints": { + "name": "copilot_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "yaml": { + "name": "yaml", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_checkpoints_user_id_idx": { + "name": "copilot_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_checkpoints_workflow_id_idx": { + "name": "copilot_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_checkpoints_chat_id_idx": { + "name": "copilot_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_checkpoints_user_workflow_idx": { + "name": "copilot_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_checkpoints_workflow_chat_idx": { + "name": "copilot_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_checkpoints_created_at_idx": { + "name": "copilot_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_checkpoints_chat_created_at_idx": { + "name": "copilot_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_checkpoints_user_id_user_id_fk": { + "name": "copilot_checkpoints_user_id_user_id_fk", + "tableFrom": "copilot_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_checkpoints_workflow_id_workflow_id_fk": { + "name": "copilot_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "copilot_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "copilot_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_uploaded_at_idx": { + "name": "doc_kb_uploaded_at_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag1_idx": { + "name": "doc_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag2_idx": { + "name": "doc_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag3_idx": { + "name": "doc_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag4_idx": { + "name": "doc_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag5_idx": { + "name": "doc_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag6_idx": { + "name": "doc_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag7_idx": { + "name": "doc_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_tag1_idx": { + "name": "emb_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag2_idx": { + "name": "emb_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag3_idx": { + "name": "emb_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag4_idx": { + "name": "emb_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag5_idx": { + "name": "emb_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag6_idx": { + "name": "emb_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag7_idx": { + "name": "emb_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_not_null_check": { + "name": "embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 100, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.marketplace": { + "name": "marketplace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "marketplace_workflow_id_workflow_id_fk": { + "name": "marketplace_workflow_id_workflow_id_fk", + "tableFrom": "marketplace", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "marketplace_author_id_user_id_fk": { + "name": "marketplace_author_id_user_id_fk", + "tableFrom": "marketplace", + "tableTo": "user", + "columnsFrom": ["author_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workflow_idx": { + "name": "memory_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workflow_key_idx": { + "name": "memory_workflow_key_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workflow_id_workflow_id_fk": { + "name": "memory_workflow_id_workflow_id_fk", + "tableFrom": "memory", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_idx": { + "name": "permissions_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_fill_env_vars": { + "name": "auto_fill_env_vars", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_pan": { + "name": "auto_pan", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "console_expanded_by_default": { + "name": "console_expanded_by_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_notified_user": { + "name": "telemetry_notified_user", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "general": { + "name": "general", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR (metadata IS NOT NULL AND (metadata->>'perSeatAllowance' IS NOT NULL OR metadata->>'totalAllowance' IS NOT NULL))" + } + }, + "isRLSEnabled": false + }, + "public.template_stars": { + "name": "template_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_stars_user_id_idx": { + "name": "template_stars_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_id_idx": { + "name": "template_stars_template_id_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_idx": { + "name": "template_stars_user_template_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_user_idx": { + "name": "template_stars_template_user_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_starred_at_idx": { + "name": "template_stars_starred_at_idx", + "columns": [ + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_starred_at_idx": { + "name": "template_stars_template_starred_at_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_unique": { + "name": "template_stars_user_template_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_stars_user_id_user_id_fk": { + "name": "template_stars_user_id_user_id_fk", + "tableFrom": "template_stars", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "template_stars_template_id_templates_id_fk": { + "name": "template_stars_template_id_templates_id_fk", + "tableFrom": "template_stars", + "tableTo": "templates", + "columnsFrom": ["template_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'FileText'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "templates_workflow_id_idx": { + "name": "templates_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_user_id_idx": { + "name": "templates_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_category_idx": { + "name": "templates_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_views_idx": { + "name": "templates_views_idx", + "columns": [ + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_stars_idx": { + "name": "templates_stars_idx", + "columns": [ + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_category_views_idx": { + "name": "templates_category_views_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_category_stars_idx": { + "name": "templates_category_stars_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_user_category_idx": { + "name": "templates_user_category_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_created_at_idx": { + "name": "templates_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_updated_at_idx": { + "name": "templates_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "templates_workflow_id_workflow_id_fk": { + "name": "templates_workflow_id_workflow_id_fk", + "tableFrom": "templates", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "templates_user_id_user_id_fk": { + "name": "templates_user_id_user_id_fk", + "tableFrom": "templates", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_rate_limits": { + "name": "user_rate_limits", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "sync_api_requests": { + "name": "sync_api_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "async_api_requests": { + "name": "async_api_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "window_start": { + "name": "window_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_rate_limited": { + "name": "is_rate_limited", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "rate_limit_reset_at": { + "name": "rate_limit_reset_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_rate_limits_user_id_user_id_fk": { + "name": "user_rate_limits_user_id_user_id_fk", + "tableFrom": "user_rate_limits", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'5'" + }, + "usage_limit_set_by": { + "name": "usage_limit_set_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_idx": { + "name": "path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_block_id_workflow_blocks_id_fk": { + "name": "webhook_block_id_workflow_blocks_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_blocks", + "columnsFrom": ["block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_state": { + "name": "deployed_state", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "collaborators": { + "name": "collaborators", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "marketplace_data": { + "name": "marketplace_data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "extent": { + "name": "extent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_parent_id_idx": { + "name": "workflow_blocks_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_workflow_parent_idx": { + "name": "workflow_blocks_workflow_parent_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_workflow_type_idx": { + "name": "workflow_blocks_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_source_block_idx": { + "name": "workflow_edges_source_block_idx", + "columns": [ + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_target_block_idx": { + "name": "workflow_edges_target_block_idx", + "columns": [ + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "block_count": { + "name": "block_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "success_count": { + "name": "success_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_count": { + "name": "error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "total_input_cost": { + "name": "total_input_cost", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "total_output_cost": { + "name": "total_output_cost", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_idx": { + "name": "workflow_execution_logs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_cost_idx": { + "name": "workflow_execution_logs_cost_idx", + "columns": [ + { + "expression": "total_cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_duration_idx": { + "name": "workflow_execution_logs_duration_idx", + "columns": [ + { + "expression": "total_duration_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_folder": { + "name": "workflow_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#6B7280'" + }, + "is_expanded": { + "name": "is_expanded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_unique": { + "name": "workflow_schedule_workflow_block_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_block_id_workflow_blocks_id_fk": { + "name": "workflow_schedule_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_blocks", + "columnsFrom": ["block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_invitation": { + "name": "workspace_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_invitation_workspace_id_workspace_id_fk": { + "name": "workspace_invitation_workspace_id_workspace_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invitation_inviter_id_user_id_fk": { + "name": "workspace_invitation_inviter_id_user_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_invitation_token_unique": { + "name": "workspace_invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/apps/sim/db/migrations/meta/_journal.json b/apps/sim/db/migrations/meta/_journal.json index 427c5fae96..b907526103 100644 --- a/apps/sim/db/migrations/meta/_journal.json +++ b/apps/sim/db/migrations/meta/_journal.json @@ -435,6 +435,13 @@ "when": 1753383446084, "tag": "0062_previous_phantom_reporter", "breakpoints": true + }, + { + "idx": 63, + "version": "7", + "when": 1753558819517, + "tag": "0063_lame_sandman", + "breakpoints": true } ] } diff --git a/apps/sim/db/schema.ts b/apps/sim/db/schema.ts index 992f6557e8..d5a106570f 100644 --- a/apps/sim/db/schema.ts +++ b/apps/sim/db/schema.ts @@ -16,6 +16,7 @@ import { uuid, vector, } from 'drizzle-orm/pg-core' +import { TAG_SLOTS } from '@/lib/constants/knowledge' // Custom tsvector type for full-text search export const tsvector = customType<{ @@ -794,6 +795,32 @@ export const document = pgTable( }) ) +export const knowledgeBaseTagDefinitions = pgTable( + 'knowledge_base_tag_definitions', + { + id: text('id').primaryKey(), + knowledgeBaseId: text('knowledge_base_id') + .notNull() + .references(() => knowledgeBase.id, { onDelete: 'cascade' }), + tagSlot: text('tag_slot', { + enum: TAG_SLOTS, + }).notNull(), + displayName: text('display_name').notNull(), + fieldType: text('field_type').notNull().default('text'), // 'text', future: 'date', 'number', 'range' + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), + }, + (table) => ({ + // Ensure unique tag slot per knowledge base + kbTagSlotIdx: uniqueIndex('kb_tag_definitions_kb_slot_idx').on( + table.knowledgeBaseId, + table.tagSlot + ), + // Index for querying by knowledge base + kbIdIdx: index('kb_tag_definitions_kb_id_idx').on(table.knowledgeBaseId), + }) +) + export const embedding = pgTable( 'embedding', { diff --git a/apps/sim/hooks/use-knowledge-base-tag-definitions.ts b/apps/sim/hooks/use-knowledge-base-tag-definitions.ts new file mode 100644 index 0000000000..4d2bd6c33a --- /dev/null +++ b/apps/sim/hooks/use-knowledge-base-tag-definitions.ts @@ -0,0 +1,88 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' +import type { TagSlot } from '@/lib/constants/knowledge' +import { createLogger } from '@/lib/logs/console/logger' + +const logger = createLogger('useKnowledgeBaseTagDefinitions') + +export interface TagDefinition { + id: string + tagSlot: TagSlot + displayName: string + fieldType: string + createdAt: string + updatedAt: string +} + +/** + * Hook for fetching KB-scoped tag definitions (for filtering/selection) + * @param knowledgeBaseId - The knowledge base ID + */ +export function useKnowledgeBaseTagDefinitions(knowledgeBaseId: string | null) { + const [tagDefinitions, setTagDefinitions] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const fetchTagDefinitions = useCallback(async () => { + if (!knowledgeBaseId) { + setTagDefinitions([]) + return + } + + setIsLoading(true) + setError(null) + + try { + const response = await fetch(`/api/knowledge/${knowledgeBaseId}/tag-definitions`) + + if (!response.ok) { + throw new Error(`Failed to fetch tag definitions: ${response.statusText}`) + } + + const data = await response.json() + + if (data.success && Array.isArray(data.data)) { + setTagDefinitions(data.data) + } else { + throw new Error('Invalid response format') + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred' + logger.error('Error fetching tag definitions:', err) + setError(errorMessage) + setTagDefinitions([]) + } finally { + setIsLoading(false) + } + }, [knowledgeBaseId]) + + const getTagLabel = useCallback( + (tagSlot: string): string => { + const definition = tagDefinitions.find((def) => def.tagSlot === tagSlot) + return definition?.displayName || tagSlot + }, + [tagDefinitions] + ) + + const getTagDefinition = useCallback( + (tagSlot: string): TagDefinition | undefined => { + return tagDefinitions.find((def) => def.tagSlot === tagSlot) + }, + [tagDefinitions] + ) + + // Auto-fetch on mount and when dependencies change + useEffect(() => { + fetchTagDefinitions() + }, [fetchTagDefinitions]) + + return { + tagDefinitions, + isLoading, + error, + fetchTagDefinitions, + getTagLabel, + getTagDefinition, + } +} diff --git a/apps/sim/hooks/use-tag-definitions.ts b/apps/sim/hooks/use-tag-definitions.ts new file mode 100644 index 0000000000..603cbd6fd6 --- /dev/null +++ b/apps/sim/hooks/use-tag-definitions.ts @@ -0,0 +1,172 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' +import type { TagSlot } from '@/lib/constants/knowledge' +import { createLogger } from '@/lib/logs/console/logger' + +const logger = createLogger('useTagDefinitions') + +export interface TagDefinition { + id: string + tagSlot: TagSlot + displayName: string + fieldType: string + createdAt: string + updatedAt: string +} + +export interface TagDefinitionInput { + tagSlot: TagSlot + displayName: string + fieldType: string +} + +/** + * Hook for managing KB-scoped tag definitions + * @param knowledgeBaseId - The knowledge base ID + * @param documentId - The document ID (required for API calls) + */ +export function useTagDefinitions( + knowledgeBaseId: string | null, + documentId: string | null = null +) { + const [tagDefinitions, setTagDefinitions] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const fetchTagDefinitions = useCallback(async () => { + if (!knowledgeBaseId || !documentId) { + setTagDefinitions([]) + return + } + + setIsLoading(true) + setError(null) + + try { + const response = await fetch( + `/api/knowledge/${knowledgeBaseId}/documents/${documentId}/tag-definitions` + ) + + if (!response.ok) { + throw new Error(`Failed to fetch tag definitions: ${response.statusText}`) + } + + const data = await response.json() + + if (data.success && Array.isArray(data.data)) { + setTagDefinitions(data.data) + } else { + throw new Error('Invalid response format') + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred' + logger.error('Error fetching tag definitions:', err) + setError(errorMessage) + setTagDefinitions([]) + } finally { + setIsLoading(false) + } + }, [knowledgeBaseId, documentId]) + + const saveTagDefinitions = useCallback( + async (definitions: TagDefinitionInput[]) => { + if (!knowledgeBaseId || !documentId) { + throw new Error('Knowledge base ID and document ID are required') + } + + // Simple validation + const validDefinitions = (definitions || []).filter( + (def) => def?.tagSlot && def.displayName && def.displayName.trim() + ) + + try { + const response = await fetch( + `/api/knowledge/${knowledgeBaseId}/documents/${documentId}/tag-definitions`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ definitions: validDefinitions }), + } + ) + + if (!response.ok) { + throw new Error(`Failed to save tag definitions: ${response.statusText}`) + } + + const data = await response.json() + + if (!data.success) { + throw new Error(data.error || 'Failed to save tag definitions') + } + + // Refresh the definitions after saving + await fetchTagDefinitions() + + return data.data + } catch (err) { + logger.error('Error saving tag definitions:', err) + throw err + } + }, + [knowledgeBaseId, documentId, fetchTagDefinitions] + ) + + const deleteTagDefinitions = useCallback(async () => { + if (!knowledgeBaseId || !documentId) { + throw new Error('Knowledge base ID and document ID are required') + } + + try { + const response = await fetch( + `/api/knowledge/${knowledgeBaseId}/documents/${documentId}/tag-definitions`, + { + method: 'DELETE', + } + ) + + if (!response.ok) { + throw new Error(`Failed to delete tag definitions: ${response.statusText}`) + } + + // Refresh the definitions after deleting + await fetchTagDefinitions() + } catch (err) { + logger.error('Error deleting tag definitions:', err) + throw err + } + }, [knowledgeBaseId, documentId, fetchTagDefinitions]) + + const getTagLabel = useCallback( + (tagSlot: string): string => { + const definition = tagDefinitions.find((def) => def.tagSlot === tagSlot) + return definition?.displayName || tagSlot + }, + [tagDefinitions] + ) + + const getTagDefinition = useCallback( + (tagSlot: string): TagDefinition | undefined => { + return tagDefinitions.find((def) => def.tagSlot === tagSlot) + }, + [tagDefinitions] + ) + + // Auto-fetch on mount and when dependencies change + useEffect(() => { + fetchTagDefinitions() + }, [fetchTagDefinitions]) + + return { + tagDefinitions, + isLoading, + error, + fetchTagDefinitions, + saveTagDefinitions, + deleteTagDefinitions, + getTagLabel, + getTagDefinition, + } +} diff --git a/apps/sim/lib/constants/knowledge.ts b/apps/sim/lib/constants/knowledge.ts new file mode 100644 index 0000000000..5436b75e17 --- /dev/null +++ b/apps/sim/lib/constants/knowledge.ts @@ -0,0 +1,15 @@ +/** + * Knowledge base and document constants + */ + +// Maximum number of tag slots allowed per knowledge base +export const MAX_TAG_SLOTS = 7 + +// Tag slot names (derived from MAX_TAG_SLOTS) +export const TAG_SLOTS = Array.from({ length: MAX_TAG_SLOTS }, (_, i) => `tag${i + 1}`) as [ + string, + ...string[], +] + +// Type for tag slot names +export type TagSlot = (typeof TAG_SLOTS)[number] diff --git a/apps/sim/tools/knowledge/create_document.ts b/apps/sim/tools/knowledge/create_document.ts index f572350618..6a6bbef588 100644 --- a/apps/sim/tools/knowledge/create_document.ts +++ b/apps/sim/tools/knowledge/create_document.ts @@ -57,6 +57,11 @@ export const knowledgeCreateDocumentTool: ToolConfig `/api/knowledge/${params.knowledgeBaseId}/documents`, @@ -95,20 +100,32 @@ export const knowledgeCreateDocumentTool: ToolConfig = {} + + if (params.documentTags) { + let parsedTags = params.documentTags + + // Handle both string (JSON) and array formats + if (typeof params.documentTags === 'string') { + try { + parsedTags = JSON.parse(params.documentTags) + } catch (error) { + parsedTags = [] + } + } + + if (Array.isArray(parsedTags)) { + tagData.documentTagsData = JSON.stringify(parsedTags) + } + } + const documents = [ { filename: documentName.endsWith('.txt') ? documentName : `${documentName}.txt`, fileUrl: dataUri, fileSize: contentBytes, mimeType: 'text/plain', - // Include tags if provided - tag1: params.tag1 || undefined, - tag2: params.tag2 || undefined, - tag3: params.tag3 || undefined, - tag4: params.tag4 || undefined, - tag5: params.tag5 || undefined, - tag6: params.tag6 || undefined, - tag7: params.tag7 || undefined, + ...tagData, }, ] diff --git a/apps/sim/tools/knowledge/search.ts b/apps/sim/tools/knowledge/search.ts index 73b0ca786e..b6a3d0b4e1 100644 --- a/apps/sim/tools/knowledge/search.ts +++ b/apps/sim/tools/knowledge/search.ts @@ -4,14 +4,13 @@ import type { ToolConfig } from '@/tools/types' export const knowledgeSearchTool: ToolConfig = { id: 'knowledge_search', name: 'Knowledge Search', - description: 'Search for similar content in one or more knowledge bases using vector similarity', + description: 'Search for similar content in a knowledge base using vector similarity', version: '1.0.0', params: { - knowledgeBaseIds: { + knowledgeBaseId: { type: 'string', required: true, - description: - 'ID of the knowledge base to search in, or comma-separated IDs for multiple knowledge bases', + description: 'ID of the knowledge base to search in', }, query: { type: 'string', @@ -23,40 +22,10 @@ export const knowledgeSearchTool: ToolConfig = { required: false, description: 'Number of most similar results to return (1-100)', }, - tag1: { - type: 'string', - required: false, - description: 'Filter by tag 1 value', - }, - tag2: { - type: 'string', - required: false, - description: 'Filter by tag 2 value', - }, - tag3: { - type: 'string', - required: false, - description: 'Filter by tag 3 value', - }, - tag4: { - type: 'string', - required: false, - description: 'Filter by tag 4 value', - }, - tag5: { - type: 'string', - required: false, - description: 'Filter by tag 5 value', - }, - tag6: { - type: 'string', - required: false, - description: 'Filter by tag 6 value', - }, - tag7: { - type: 'string', + tagFilters: { + type: 'any', required: false, - description: 'Filter by tag 7 value', + description: 'Array of tag filters with tagName and tagValue properties', }, }, request: { @@ -68,25 +37,41 @@ export const knowledgeSearchTool: ToolConfig = { body: (params) => { const workflowId = params._context?.workflowId - // Handle multiple knowledge base IDs - let knowledgeBaseIds = params.knowledgeBaseIds - if (typeof knowledgeBaseIds === 'string' && knowledgeBaseIds.includes(',')) { - // Split comma-separated string into array - knowledgeBaseIds = knowledgeBaseIds - .split(',') - .map((id) => id.trim()) - .filter((id) => id.length > 0) - } + // Use single knowledge base ID + const knowledgeBaseIds = [params.knowledgeBaseId] - // Build filters object from tag parameters + // Parse dynamic tag filters and send display names to API const filters: Record = {} - if (params.tag1) filters.tag1 = params.tag1.toString() - if (params.tag2) filters.tag2 = params.tag2.toString() - if (params.tag3) filters.tag3 = params.tag3.toString() - if (params.tag4) filters.tag4 = params.tag4.toString() - if (params.tag5) filters.tag5 = params.tag5.toString() - if (params.tag6) filters.tag6 = params.tag6.toString() - if (params.tag7) filters.tag7 = params.tag7.toString() + if (params.tagFilters) { + let tagFilters = params.tagFilters + + // Handle both string (JSON) and array formats + if (typeof tagFilters === 'string') { + try { + tagFilters = JSON.parse(tagFilters) + } catch (error) { + tagFilters = [] + } + } + + if (Array.isArray(tagFilters)) { + // Group filters by tag name for OR logic within same tag + const groupedFilters: Record = {} + tagFilters.forEach((filter: any) => { + if (filter.tagName && filter.tagValue) { + if (!groupedFilters[filter.tagName]) { + groupedFilters[filter.tagName] = [] + } + groupedFilters[filter.tagName].push(filter.tagValue) + } + }) + + // Convert to filters format - for now, join multiple values with OR separator + Object.entries(groupedFilters).forEach(([tagName, values]) => { + filters[tagName] = values.join('|OR|') // Use special separator for OR logic + }) + } + } const requestBody = { knowledgeBaseIds,