diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts index 659328dda..284f06937 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts @@ -156,6 +156,7 @@ export async function POST( const validatedData = CreateChunkSchema.parse(searchParams) const docTags = { + // Text tags (7 slots) tag1: doc.tag1 ?? null, tag2: doc.tag2 ?? null, tag3: doc.tag3 ?? null, @@ -163,6 +164,19 @@ export async function POST( tag5: doc.tag5 ?? null, tag6: doc.tag6 ?? null, tag7: doc.tag7 ?? null, + // Number tags (5 slots) + number1: doc.number1 ?? null, + number2: doc.number2 ?? null, + number3: doc.number3 ?? null, + number4: doc.number4 ?? null, + number5: doc.number5 ?? null, + // Date tags (2 slots) + date1: doc.date1 ?? null, + date2: doc.date2 ?? null, + // Boolean tags (3 slots) + boolean1: doc.boolean1 ?? null, + boolean2: doc.boolean2 ?? null, + boolean3: doc.boolean3 ?? null, } const newChunk = await createChunk( 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 07f9b9846..710d9eea8 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 @@ -72,6 +72,16 @@ describe('Document By ID API Route', () => { tag5: null, tag6: null, tag7: null, + number1: null, + number2: null, + number3: null, + number4: null, + number5: null, + date1: null, + date2: null, + boolean1: null, + boolean2: null, + boolean3: null, deletedAt: null, } 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 66f15071c..6e5495aa7 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts @@ -23,7 +23,7 @@ const UpdateDocumentSchema = z.object({ processingError: z.string().optional(), markFailedDueToTimeout: z.boolean().optional(), retryProcessing: z.boolean().optional(), - // Tag fields + // Text tag fields tag1: z.string().optional(), tag2: z.string().optional(), tag3: z.string().optional(), @@ -31,6 +31,19 @@ const UpdateDocumentSchema = z.object({ tag5: z.string().optional(), tag6: z.string().optional(), tag7: z.string().optional(), + // Number tag fields + number1: z.string().optional(), + number2: z.string().optional(), + number3: z.string().optional(), + number4: z.string().optional(), + number5: z.string().optional(), + // Date tag fields + date1: z.string().optional(), + date2: z.string().optional(), + // Boolean tag fields + boolean1: z.string().optional(), + boolean2: z.string().optional(), + boolean3: z.string().optional(), }) export async function GET( diff --git a/apps/sim/app/api/knowledge/[id]/documents/route.test.ts b/apps/sim/app/api/knowledge/[id]/documents/route.test.ts index 1ff73940f..2b22613f6 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/route.test.ts @@ -80,6 +80,16 @@ describe('Knowledge Base Documents API Route', () => { tag5: null, tag6: null, tag7: null, + number1: null, + number2: null, + number3: null, + number4: null, + number5: null, + date1: null, + date2: null, + boolean1: null, + boolean2: null, + boolean3: null, deletedAt: null, } diff --git a/apps/sim/app/api/knowledge/search/route.test.ts b/apps/sim/app/api/knowledge/search/route.test.ts index 3195988f0..94f8a0a2b 100644 --- a/apps/sim/app/api/knowledge/search/route.test.ts +++ b/apps/sim/app/api/knowledge/search/route.test.ts @@ -64,6 +64,11 @@ vi.mock('@/app/api/knowledge/utils', () => ({ checkKnowledgeBaseAccess: mockCheckKnowledgeBaseAccess, })) +const mockGetDocumentTagDefinitions = vi.fn() +vi.mock('@/lib/knowledge/tags/service', () => ({ + getDocumentTagDefinitions: mockGetDocumentTagDefinitions, +})) + const mockHandleTagOnlySearch = vi.fn() const mockHandleVectorOnlySearch = vi.fn() const mockHandleTagAndVectorSearch = vi.fn() @@ -156,6 +161,7 @@ describe('Knowledge Search API Route', () => { doc1: 'Document 1', doc2: 'Document 2', }) + mockGetDocumentTagDefinitions.mockClear() vi.stubGlobal('crypto', { randomUUID: vi.fn().mockReturnValue('mock-uuid-1234-5678'), @@ -659,8 +665,8 @@ describe('Knowledge Search API Route', () => { describe('Optional Query Search', () => { const mockTagDefinitions = [ - { tagSlot: 'tag1', displayName: 'category' }, - { tagSlot: 'tag2', displayName: 'priority' }, + { tagSlot: 'tag1', displayName: 'category', fieldType: 'text' }, + { tagSlot: 'tag2', displayName: 'priority', fieldType: 'text' }, ] const mockTaggedResults = [ @@ -689,9 +695,7 @@ describe('Knowledge Search API Route', () => { it('should perform tag-only search without query', async () => { const tagOnlyData = { knowledgeBaseIds: 'kb-123', - filters: { - category: 'api', - }, + tagFilters: [{ tagName: 'category', value: 'api', fieldType: 'text', operator: 'eq' }], topK: 10, } @@ -706,10 +710,11 @@ describe('Knowledge Search API Route', () => { }, }) - // Mock tag definitions queries for filter mapping and display mapping - mockDbChain.limit - .mockResolvedValueOnce(mockTagDefinitions) // Tag definitions for filter mapping - .mockResolvedValueOnce(mockTagDefinitions) // Tag definitions for display mapping + // Mock tag definitions for validation + mockGetDocumentTagDefinitions.mockResolvedValue(mockTagDefinitions) + + // Mock tag definitions queries for display mapping + mockDbChain.limit.mockResolvedValueOnce(mockTagDefinitions) // Mock the tag-only search handler mockHandleTagOnlySearch.mockResolvedValue(mockTaggedResults) @@ -729,7 +734,9 @@ describe('Knowledge Search API Route', () => { expect(mockHandleTagOnlySearch).toHaveBeenCalledWith({ knowledgeBaseIds: ['kb-123'], topK: 10, - filters: { category: 'api' }, // Note: When no tag definitions are found, it uses the original filter key + structuredFilters: [ + { tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api', valueTo: undefined }, + ], }) }) @@ -737,9 +744,7 @@ describe('Knowledge Search API Route', () => { const combinedData = { knowledgeBaseIds: 'kb-123', query: 'test search', - filters: { - category: 'api', - }, + tagFilters: [{ tagName: 'category', value: 'api', fieldType: 'text', operator: 'eq' }], topK: 10, } @@ -754,10 +759,11 @@ describe('Knowledge Search API Route', () => { }, }) - // Mock tag definitions queries for filter mapping and display mapping - mockDbChain.limit - .mockResolvedValueOnce(mockTagDefinitions) // Tag definitions for filter mapping - .mockResolvedValueOnce(mockTagDefinitions) // Tag definitions for display mapping + // Mock tag definitions for validation + mockGetDocumentTagDefinitions.mockResolvedValue(mockTagDefinitions) + + // Mock tag definitions queries for display mapping + mockDbChain.limit.mockResolvedValueOnce(mockTagDefinitions) // Mock the tag + vector search handler mockHandleTagAndVectorSearch.mockResolvedValue(mockSearchResults) @@ -784,7 +790,9 @@ describe('Knowledge Search API Route', () => { expect(mockHandleTagAndVectorSearch).toHaveBeenCalledWith({ knowledgeBaseIds: ['kb-123'], topK: 10, - filters: { category: 'api' }, // Note: When no tag definitions are found, it uses the original filter key + structuredFilters: [ + { tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api', valueTo: undefined }, + ], queryVector: JSON.stringify(mockEmbedding), distanceThreshold: 1, // Single KB uses threshold of 1.0 }) @@ -928,10 +936,10 @@ describe('Knowledge Search API Route', () => { it('should handle tag-only search with multiple knowledge bases', async () => { const multiKbTagData = { knowledgeBaseIds: ['kb-123', 'kb-456'], - filters: { - category: 'docs', - priority: 'high', - }, + tagFilters: [ + { tagName: 'category', value: 'docs', fieldType: 'text', operator: 'eq' }, + { tagName: 'priority', value: 'high', fieldType: 'text', operator: 'eq' }, + ], topK: 10, } @@ -951,37 +959,14 @@ describe('Knowledge Search API Route', () => { knowledgeBase: { id: 'kb-456', userId: 'user-123', name: 'Test KB 2' }, }) - // Reset all mocks before setting up specific behavior - Object.values(mockDbChain).forEach((fn) => { - if (typeof fn === 'function') { - fn.mockClear().mockReturnThis() - } - }) + // Mock tag definitions for validation + mockGetDocumentTagDefinitions.mockResolvedValue(mockTagDefinitions) - // Create fresh mocks for multiple database calls needed for multi-KB tag search - const mockTagDefsQuery1 = { - ...mockDbChain, - limit: vi.fn().mockResolvedValue(mockTagDefinitions), - } - const mockTagSearchQuery = { - ...mockDbChain, - limit: vi.fn().mockResolvedValue(mockTaggedResults), - } - const mockTagDefsQuery2 = { - ...mockDbChain, - limit: vi.fn().mockResolvedValue(mockTagDefinitions), - } - const mockTagDefsQuery3 = { - ...mockDbChain, - limit: vi.fn().mockResolvedValue(mockTagDefinitions), - } + // Mock the tag-only search handler + mockHandleTagOnlySearch.mockResolvedValue(mockTaggedResults) - // Chain the mocks for: tag defs, search, display mapping KB1, display mapping KB2 - mockDbChain.select - .mockReturnValueOnce(mockTagDefsQuery1) - .mockReturnValueOnce(mockTagSearchQuery) - .mockReturnValueOnce(mockTagDefsQuery2) - .mockReturnValueOnce(mockTagDefsQuery3) + // Mock tag definitions queries for display mapping + mockDbChain.limit.mockResolvedValueOnce(mockTagDefinitions) const req = createMockRequest('POST', multiKbTagData) const { POST } = await import('@/app/api/knowledge/search/route') @@ -1076,6 +1061,11 @@ describe('Knowledge Search API Route', () => { }, }) + // Mock tag definitions for validation + mockGetDocumentTagDefinitions.mockResolvedValue([ + { tagSlot: 'tag1', displayName: 'tag1', fieldType: 'text' }, + ]) + mockHandleTagOnlySearch.mockResolvedValue([ { id: 'chunk-2', @@ -1108,13 +1098,15 @@ describe('Knowledge Search API Route', () => { const mockTagDefs = { select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), - where: vi.fn().mockResolvedValue([]), + where: vi + .fn() + .mockResolvedValue([{ tagSlot: 'tag1', displayName: 'tag1', fieldType: 'text' }]), } mockDbChain.select.mockReturnValueOnce(mockTagDefs) const req = createMockRequest('POST', { knowledgeBaseIds: ['kb-123'], - filters: { tag1: 'api' }, + tagFilters: [{ tagName: 'tag1', value: 'api', fieldType: 'text', operator: 'eq' }], topK: 10, }) @@ -1143,6 +1135,11 @@ describe('Knowledge Search API Route', () => { }, }) + // Mock tag definitions for validation + mockGetDocumentTagDefinitions.mockResolvedValue([ + { tagSlot: 'tag1', displayName: 'tag1', fieldType: 'text' }, + ]) + mockHandleTagAndVectorSearch.mockResolvedValue([ { id: 'chunk-3', @@ -1176,14 +1173,16 @@ describe('Knowledge Search API Route', () => { const mockTagDefs = { select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), - where: vi.fn().mockResolvedValue([]), + where: vi + .fn() + .mockResolvedValue([{ tagSlot: 'tag1', displayName: 'tag1', fieldType: 'text' }]), } mockDbChain.select.mockReturnValueOnce(mockTagDefs) const req = createMockRequest('POST', { knowledgeBaseIds: ['kb-123'], query: 'relevant content', - filters: { tag1: 'guide' }, + tagFilters: [{ tagName: 'tag1', value: 'guide', fieldType: 'text', operator: 'eq' }], topK: 10, }) diff --git a/apps/sim/app/api/knowledge/search/route.ts b/apps/sim/app/api/knowledge/search/route.ts index 1bd86c40a..4172ebc2d 100644 --- a/apps/sim/app/api/knowledge/search/route.ts +++ b/apps/sim/app/api/knowledge/search/route.ts @@ -1,8 +1,10 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { generateRequestId } from '@/lib/core/utils/request' -import { TAG_SLOTS } from '@/lib/knowledge/constants' +import { ALL_TAG_SLOTS } from '@/lib/knowledge/constants' import { getDocumentTagDefinitions } from '@/lib/knowledge/tags/service' +import { buildUndefinedTagsError, validateTagValue } from '@/lib/knowledge/tags/utils' +import type { StructuredFilter } from '@/lib/knowledge/types' import { createLogger } from '@/lib/logs/console/logger' import { estimateTokenCount } from '@/lib/tokenization/estimators' import { getUserId } from '@/app/api/auth/oauth/utils' @@ -20,6 +22,16 @@ import { calculateCost } from '@/providers/utils' const logger = createLogger('VectorSearchAPI') +/** Structured tag filter with operator support */ +const StructuredTagFilterSchema = z.object({ + tagName: z.string(), + tagSlot: z.string().optional(), + fieldType: z.enum(['text', 'number', 'date', 'boolean']).default('text'), + operator: z.string().default('eq'), + value: z.union([z.string(), z.number(), z.boolean()]), + valueTo: z.union([z.string(), z.number()]).optional(), +}) + const VectorSearchSchema = z .object({ knowledgeBaseIds: z.union([ @@ -39,18 +51,17 @@ const VectorSearchSchema = z .nullable() .default(10) .transform((val) => val ?? 10), - filters: z - .record(z.string()) + tagFilters: z + .array(StructuredTagFilterSchema) .optional() .nullable() - .transform((val) => val || undefined), // Allow dynamic filter keys (display names) + .transform((val) => val || undefined), }) .refine( (data) => { - // Ensure at least query or filters are provided const hasQuery = data.query && data.query.trim().length > 0 - const hasFilters = data.filters && Object.keys(data.filters).length > 0 - return hasQuery || hasFilters + const hasTagFilters = data.tagFilters && data.tagFilters.length > 0 + return hasQuery || hasTagFilters }, { message: 'Please provide either a search query or tag filters to search your knowledge base', @@ -88,45 +99,81 @@ 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 getDocumentTagDefinitions(kbId) + let structuredFilters: StructuredFilter[] = [] - logger.debug(`[${requestId}] Found tag definitions:`, tagDefs) - logger.debug(`[${requestId}] Original filters:`, validatedData.filters) + // Handle tag filters + if (validatedData.tagFilters && accessibleKbIds.length > 0) { + const kbId = accessibleKbIds[0] + const tagDefs = await getDocumentTagDefinitions(kbId) - // Create mapping from display name to tag slot - const displayNameToSlot: Record = {} - tagDefs.forEach((def) => { - displayNameToSlot[def.displayName] = def.tagSlot - }) + // Create mapping from display name to tag slot and fieldType + const displayNameToTagDef: Record = {} + tagDefs.forEach((def) => { + displayNameToTagDef[def.displayName] = { + tagSlot: def.tagSlot, + fieldType: def.fieldType, + } + }) - // 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 + // Validate all tag filters first + const undefinedTags: string[] = [] + const typeErrors: string[] = [] - // Check if this is an OR filter (contains |OR| separator) - if (value.includes('|OR|')) { - logger.debug( - `[${requestId}] OR filter detected: "${key}" -> "${tagSlot}" = "${value}"` - ) - } + for (const filter of validatedData.tagFilters) { + const tagDef = displayNameToTagDef[filter.tagName] - mappedFilters[tagSlot] = value - logger.debug(`[${requestId}] Mapped filter: "${key}" -> "${tagSlot}" = "${value}"`) - } - }) + // Check if tag exists + if (!tagDef) { + undefinedTags.push(filter.tagName) + continue + } - 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 + // Validate value type using shared validation + const validationError = validateTagValue( + filter.tagName, + String(filter.value), + tagDef.fieldType + ) + if (validationError) { + typeErrors.push(validationError) + } } + + // Throw combined error if there are any validation issues + if (undefinedTags.length > 0 || typeErrors.length > 0) { + const errorParts: string[] = [] + + if (undefinedTags.length > 0) { + errorParts.push(buildUndefinedTagsError(undefinedTags)) + } + + if (typeErrors.length > 0) { + errorParts.push(...typeErrors) + } + + return NextResponse.json({ error: errorParts.join('\n') }, { status: 400 }) + } + + // Build structured filters with validated data + structuredFilters = validatedData.tagFilters.map((filter) => { + const tagDef = displayNameToTagDef[filter.tagName]! + const tagSlot = filter.tagSlot || tagDef.tagSlot + const fieldType = filter.fieldType || tagDef.fieldType + + logger.debug( + `[${requestId}] Structured filter: ${filter.tagName} -> ${tagSlot} (${fieldType}) ${filter.operator} ${filter.value}` + ) + + return { + tagSlot, + fieldType, + operator: filter.operator, + value: filter.value, + valueTo: filter.valueTo, + } + }) + + logger.debug(`[${requestId}] Processed ${structuredFilters.length} structured filters`) } if (accessibleKbIds.length === 0) { @@ -155,26 +202,29 @@ export async function POST(request: NextRequest) { let results: SearchResult[] - const hasFilters = mappedFilters && Object.keys(mappedFilters).length > 0 + const hasFilters = structuredFilters && structuredFilters.length > 0 if (!hasQuery && hasFilters) { // Tag-only search without vector similarity - logger.debug(`[${requestId}] Executing tag-only search with filters:`, mappedFilters) + logger.debug(`[${requestId}] Executing tag-only search with filters:`, structuredFilters) results = await handleTagOnlySearch({ knowledgeBaseIds: accessibleKbIds, topK: validatedData.topK, - filters: mappedFilters, + structuredFilters, }) } else if (hasQuery && hasFilters) { // Tag + Vector search - logger.debug(`[${requestId}] Executing tag + vector search with filters:`, mappedFilters) + logger.debug( + `[${requestId}] Executing tag + vector search with filters:`, + structuredFilters + ) const strategy = getQueryStrategy(accessibleKbIds.length, validatedData.topK) const queryVector = JSON.stringify(await queryEmbeddingPromise) results = await handleTagAndVectorSearch({ knowledgeBaseIds: accessibleKbIds, topK: validatedData.topK, - filters: mappedFilters, + structuredFilters, queryVector, distanceThreshold: strategy.distanceThreshold, }) @@ -257,9 +307,9 @@ export async function POST(request: NextRequest) { // Create tags object with display names const tags: Record = {} - TAG_SLOTS.forEach((slot) => { + ALL_TAG_SLOTS.forEach((slot) => { const tagValue = (result as any)[slot] - if (tagValue) { + if (tagValue !== null && tagValue !== undefined) { const displayName = kbTagMap[slot] || slot logger.debug( `[${requestId}] Mapping ${slot}="${tagValue}" -> "${displayName}"="${tagValue}"` diff --git a/apps/sim/app/api/knowledge/search/utils.test.ts b/apps/sim/app/api/knowledge/search/utils.test.ts index 1834873f5..882d65853 100644 --- a/apps/sim/app/api/knowledge/search/utils.test.ts +++ b/apps/sim/app/api/knowledge/search/utils.test.ts @@ -54,7 +54,7 @@ describe('Knowledge Search Utils', () => { const params = { knowledgeBaseIds: ['kb-123'], topK: 10, - filters: {}, + structuredFilters: [], } await expect(handleTagOnlySearch(params)).rejects.toThrow( @@ -66,14 +66,14 @@ describe('Knowledge Search Utils', () => { const params = { knowledgeBaseIds: ['kb-123'], topK: 10, - filters: { tag1: 'api' }, + structuredFilters: [{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api' }], } // This test validates the function accepts the right parameters // The actual database interaction is tested via route tests expect(params.knowledgeBaseIds).toEqual(['kb-123']) expect(params.topK).toBe(10) - expect(params.filters).toEqual({ tag1: 'api' }) + expect(params.structuredFilters).toHaveLength(1) }) }) @@ -123,7 +123,7 @@ describe('Knowledge Search Utils', () => { const params = { knowledgeBaseIds: ['kb-123'], topK: 10, - filters: {}, + structuredFilters: [], queryVector: JSON.stringify([0.1, 0.2, 0.3]), distanceThreshold: 0.8, } @@ -137,7 +137,7 @@ describe('Knowledge Search Utils', () => { const params = { knowledgeBaseIds: ['kb-123'], topK: 10, - filters: { tag1: 'api' }, + structuredFilters: [{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api' }], distanceThreshold: 0.8, } @@ -150,7 +150,7 @@ describe('Knowledge Search Utils', () => { const params = { knowledgeBaseIds: ['kb-123'], topK: 10, - filters: { tag1: 'api' }, + structuredFilters: [{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api' }], queryVector: JSON.stringify([0.1, 0.2, 0.3]), } @@ -163,7 +163,7 @@ describe('Knowledge Search Utils', () => { const params = { knowledgeBaseIds: ['kb-123'], topK: 10, - filters: { tag1: 'api' }, + structuredFilters: [{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api' }], queryVector: JSON.stringify([0.1, 0.2, 0.3]), distanceThreshold: 0.8, } @@ -171,7 +171,7 @@ describe('Knowledge Search Utils', () => { // This test validates the function accepts the right parameters expect(params.knowledgeBaseIds).toEqual(['kb-123']) expect(params.topK).toBe(10) - expect(params.filters).toEqual({ tag1: 'api' }) + expect(params.structuredFilters).toHaveLength(1) expect(params.queryVector).toBe(JSON.stringify([0.1, 0.2, 0.3])) expect(params.distanceThreshold).toBe(0.8) }) diff --git a/apps/sim/app/api/knowledge/search/utils.ts b/apps/sim/app/api/knowledge/search/utils.ts index b25b07fb2..74b47664d 100644 --- a/apps/sim/app/api/knowledge/search/utils.ts +++ b/apps/sim/app/api/knowledge/search/utils.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { document, embedding } from '@sim/db/schema' import { and, eq, inArray, isNull, sql } from 'drizzle-orm' +import type { StructuredFilter } from '@/lib/knowledge/types' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('KnowledgeSearchUtils') @@ -34,6 +35,7 @@ export interface SearchResult { content: string documentId: string chunkIndex: number + // Text tags tag1: string | null tag2: string | null tag3: string | null @@ -41,6 +43,19 @@ export interface SearchResult { tag5: string | null tag6: string | null tag7: string | null + // Number tags (5 slots) + number1: number | null + number2: number | null + number3: number | null + number4: number | null + number5: number | null + // Date tags (2 slots) + date1: Date | null + date2: Date | null + // Boolean tags (3 slots) + boolean1: boolean | null + boolean2: boolean | null + boolean3: boolean | null distance: number knowledgeBaseId: string } @@ -48,7 +63,7 @@ export interface SearchResult { export interface SearchParams { knowledgeBaseIds: string[] topK: number - filters?: Record + structuredFilters?: StructuredFilter[] queryVector?: string distanceThreshold?: number } @@ -56,46 +71,230 @@ export interface SearchParams { // Use shared embedding utility export { generateSearchEmbedding } from '@/lib/knowledge/embeddings' -function getTagFilters(filters: Record, embedding: any) { - return Object.entries(filters).map(([key, value]) => { - // Handle OR logic within same tag - const values = value.includes('|OR|') ? value.split('|OR|') : [value] - logger.debug(`[getTagFilters] Processing ${key}="${value}" -> values:`, values) +/** All valid tag slot keys */ +const TAG_SLOT_KEYS = [ + // Text tags (7 slots) + 'tag1', + 'tag2', + 'tag3', + 'tag4', + 'tag5', + 'tag6', + 'tag7', + // Number tags (5 slots) + 'number1', + 'number2', + 'number3', + 'number4', + 'number5', + // Date tags (2 slots) + 'date1', + 'date2', + // Boolean tags (3 slots) + 'boolean1', + 'boolean2', + 'boolean3', +] as const - 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 - } +type TagSlotKey = (typeof TAG_SLOT_KEYS)[number] + +function isTagSlotKey(key: string): key is TagSlotKey { + return TAG_SLOT_KEYS.includes(key as TagSlotKey) +} + +/** Common fields selected for search results */ +const getSearchResultFields = (distanceExpr: any) => ({ + id: embedding.id, + content: embedding.content, + documentId: embedding.documentId, + chunkIndex: embedding.chunkIndex, + // Text tags + tag1: embedding.tag1, + tag2: embedding.tag2, + tag3: embedding.tag3, + tag4: embedding.tag4, + tag5: embedding.tag5, + tag6: embedding.tag6, + tag7: embedding.tag7, + // Number tags (5 slots) + number1: embedding.number1, + number2: embedding.number2, + number3: embedding.number3, + number4: embedding.number4, + number5: embedding.number5, + // Date tags (2 slots) + date1: embedding.date1, + date2: embedding.date2, + // Boolean tags (3 slots) + boolean1: embedding.boolean1, + boolean2: embedding.boolean2, + boolean3: embedding.boolean3, + distance: distanceExpr, + knowledgeBaseId: embedding.knowledgeBaseId, +}) + +/** + * Build a single SQL condition for a filter + */ +function buildFilterCondition(filter: StructuredFilter, embeddingTable: any) { + const { tagSlot, fieldType, operator, value, valueTo } = filter + + if (!isTagSlotKey(tagSlot)) { + logger.debug(`[getStructuredTagFilters] Unknown tag slot: ${tagSlot}`) + return null + } + + const column = embeddingTable[tagSlot] + if (!column) return null + + logger.debug( + `[getStructuredTagFilters] Processing ${tagSlot} (${fieldType}) ${operator} ${value}` + ) + + // Handle text operators + if (fieldType === 'text') { + const stringValue = String(value) + switch (operator) { + case 'eq': + return sql`LOWER(${column}) = LOWER(${stringValue})` + case 'neq': + return sql`LOWER(${column}) != LOWER(${stringValue})` + case 'contains': + return sql`LOWER(${column}) LIKE LOWER(${`%${stringValue}%`})` + case 'not_contains': + return sql`LOWER(${column}) NOT LIKE LOWER(${`%${stringValue}%`})` + case 'starts_with': + return sql`LOWER(${column}) LIKE LOWER(${`${stringValue}%`})` + case 'ends_with': + return sql`LOWER(${column}) LIKE LOWER(${`%${stringValue}`})` + default: + return sql`LOWER(${column}) = LOWER(${stringValue})` + } + } + + // Handle number operators + if (fieldType === 'number') { + const numValue = typeof value === 'number' ? value : Number.parseFloat(String(value)) + if (Number.isNaN(numValue)) return null + + switch (operator) { + case 'eq': + return sql`${column} = ${numValue}` + case 'neq': + return sql`${column} != ${numValue}` + case 'gt': + return sql`${column} > ${numValue}` + case 'gte': + return sql`${column} >= ${numValue}` + case 'lt': + return sql`${column} < ${numValue}` + case 'lte': + return sql`${column} <= ${numValue}` + case 'between': + if (valueTo !== undefined) { + const numValueTo = + typeof valueTo === 'number' ? valueTo : Number.parseFloat(String(valueTo)) + if (Number.isNaN(numValueTo)) return sql`${column} = ${numValue}` + return sql`${column} >= ${numValue} AND ${column} <= ${numValueTo}` + } + return sql`${column} = ${numValue}` + default: + return sql`${column} = ${numValue}` + } + } + + // Handle date operators - expects YYYY-MM-DD format from frontend + if (fieldType === 'date') { + const dateStr = String(value) + // Validate YYYY-MM-DD format + if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { + logger.debug(`[getStructuredTagFilters] Invalid date format: ${dateStr}, expected YYYY-MM-DD`) + 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]})` + switch (operator) { + case 'eq': + return sql`${column}::date = ${dateStr}::date` + case 'neq': + return sql`${column}::date != ${dateStr}::date` + case 'gt': + return sql`${column}::date > ${dateStr}::date` + case 'gte': + return sql`${column}::date >= ${dateStr}::date` + case 'lt': + return sql`${column}::date < ${dateStr}::date` + case 'lte': + return sql`${column}::date <= ${dateStr}::date` + case 'between': + if (valueTo !== undefined) { + const dateStrTo = String(valueTo) + if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStrTo)) { + return sql`${column}::date = ${dateStr}::date` + } + return sql`${column}::date >= ${dateStr}::date AND ${column}::date <= ${dateStrTo}::date` + } + return sql`${column}::date = ${dateStr}::date` + default: + return sql`${column}::date = ${dateStr}::date` } - // 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 `)})` - }) + } + + // Handle boolean operators + if (fieldType === 'boolean') { + const boolValue = value === true || value === 'true' + switch (operator) { + case 'eq': + return sql`${column} = ${boolValue}` + case 'neq': + return sql`${column} != ${boolValue}` + default: + return sql`${column} = ${boolValue}` + } + } + + // Fallback to equality + return sql`${column} = ${value}` +} + +/** + * Build SQL conditions from structured filters with operator support + * - Same tag multiple times: OR logic + * - Different tags: AND logic + */ +function getStructuredTagFilters(filters: StructuredFilter[], embeddingTable: any) { + // Group filters by tagSlot + const filtersBySlot = new Map() + for (const filter of filters) { + const slot = filter.tagSlot + if (!filtersBySlot.has(slot)) { + filtersBySlot.set(slot, []) + } + filtersBySlot.get(slot)!.push(filter) + } + + // Build conditions: OR within same slot, AND across different slots + const conditions: ReturnType[] = [] + + for (const [slot, slotFilters] of filtersBySlot) { + const slotConditions = slotFilters + .map((f) => buildFilterCondition(f, embeddingTable)) + .filter((c): c is ReturnType => c !== null) + + if (slotConditions.length === 0) continue + + if (slotConditions.length === 1) { + // Single condition for this slot + conditions.push(slotConditions[0]) + } else { + // Multiple conditions for same slot - OR them together + logger.debug( + `[getStructuredTagFilters] OR'ing ${slotConditions.length} conditions for ${slot}` + ) + conditions.push(sql`(${sql.join(slotConditions, sql` OR `)})`) + } + } + + return conditions } export function getQueryStrategy(kbCount: number, topK: number) { @@ -113,8 +312,10 @@ export function getQueryStrategy(kbCount: number, topK: number) { async function executeTagFilterQuery( knowledgeBaseIds: string[], - filters: Record + structuredFilters: StructuredFilter[] ): Promise<{ id: string }[]> { + const tagFilterConditions = getStructuredTagFilters(structuredFilters, embedding) + if (knowledgeBaseIds.length === 1) { return await db .select({ id: embedding.id }) @@ -125,7 +326,7 @@ async function executeTagFilterQuery( eq(embedding.knowledgeBaseId, knowledgeBaseIds[0]), eq(embedding.enabled, true), isNull(document.deletedAt), - ...getTagFilters(filters, embedding) + ...tagFilterConditions ) ) } @@ -138,7 +339,7 @@ async function executeTagFilterQuery( inArray(embedding.knowledgeBaseId, knowledgeBaseIds), eq(embedding.enabled, true), isNull(document.deletedAt), - ...getTagFilters(filters, embedding) + ...tagFilterConditions ) ) } @@ -154,21 +355,11 @@ async function executeVectorSearchOnIds( } return await db - .select({ - id: embedding.id, - content: embedding.content, - documentId: embedding.documentId, - chunkIndex: embedding.chunkIndex, - tag1: embedding.tag1, - tag2: embedding.tag2, - tag3: embedding.tag3, - tag4: embedding.tag4, - tag5: embedding.tag5, - tag6: embedding.tag6, - tag7: embedding.tag7, - distance: sql`${embedding.embedding} <=> ${queryVector}::vector`.as('distance'), - knowledgeBaseId: embedding.knowledgeBaseId, - }) + .select( + getSearchResultFields( + sql`${embedding.embedding} <=> ${queryVector}::vector`.as('distance') + ) + ) .from(embedding) .innerJoin(document, eq(embedding.documentId, document.id)) .where( @@ -183,15 +374,16 @@ async function executeVectorSearchOnIds( } export async function handleTagOnlySearch(params: SearchParams): Promise { - const { knowledgeBaseIds, topK, filters } = params + const { knowledgeBaseIds, topK, structuredFilters } = params - if (!filters || Object.keys(filters).length === 0) { + if (!structuredFilters || structuredFilters.length === 0) { throw new Error('Tag filters are required for tag-only search') } - logger.debug(`[handleTagOnlySearch] Executing tag-only search with filters:`, filters) + logger.debug(`[handleTagOnlySearch] Executing tag-only search with filters:`, structuredFilters) const strategy = getQueryStrategy(knowledgeBaseIds.length, topK) + const tagFilterConditions = getStructuredTagFilters(structuredFilters, embedding) if (strategy.useParallel) { // Parallel approach for many KBs @@ -199,21 +391,7 @@ export async function handleTagOnlySearch(params: SearchParams): Promise { return await db - .select({ - id: embedding.id, - content: embedding.content, - documentId: embedding.documentId, - chunkIndex: embedding.chunkIndex, - tag1: embedding.tag1, - tag2: embedding.tag2, - tag3: embedding.tag3, - tag4: embedding.tag4, - tag5: embedding.tag5, - tag6: embedding.tag6, - tag7: embedding.tag7, - distance: sql`0`.as('distance'), // No distance for tag-only searches - knowledgeBaseId: embedding.knowledgeBaseId, - }) + .select(getSearchResultFields(sql`0`.as('distance'))) .from(embedding) .innerJoin(document, eq(embedding.documentId, document.id)) .where( @@ -221,7 +399,7 @@ export async function handleTagOnlySearch(params: SearchParams): Promise`0`.as('distance'), // No distance for tag-only searches - knowledgeBaseId: embedding.knowledgeBaseId, - }) + .select(getSearchResultFields(sql`0`.as('distance'))) .from(embedding) .innerJoin(document, eq(embedding.documentId, document.id)) .where( @@ -254,7 +418,7 @@ export async function handleTagOnlySearch(params: SearchParams): Promise`${embedding.embedding} <=> ${queryVector}::vector`.as('distance') + if (strategy.useParallel) { // Parallel approach for many KBs const parallelLimit = Math.ceil(topK / knowledgeBaseIds.length) + 5 const queryPromises = knowledgeBaseIds.map(async (kbId) => { return await db - .select({ - id: embedding.id, - content: embedding.content, - documentId: embedding.documentId, - chunkIndex: embedding.chunkIndex, - tag1: embedding.tag1, - tag2: embedding.tag2, - tag3: embedding.tag3, - tag4: embedding.tag4, - tag5: embedding.tag5, - tag6: embedding.tag6, - tag7: embedding.tag7, - distance: sql`${embedding.embedding} <=> ${queryVector}::vector`.as('distance'), - knowledgeBaseId: embedding.knowledgeBaseId, - }) + .select(getSearchResultFields(distanceExpr)) .from(embedding) .innerJoin(document, eq(embedding.documentId, document.id)) .where( @@ -312,21 +464,7 @@ export async function handleVectorOnlySearch(params: SearchParams): Promise`${embedding.embedding} <=> ${queryVector}::vector`.as('distance'), - knowledgeBaseId: embedding.knowledgeBaseId, - }) + .select(getSearchResultFields(distanceExpr)) .from(embedding) .innerJoin(document, eq(embedding.documentId, document.id)) .where( @@ -342,19 +480,22 @@ export async function handleVectorOnlySearch(params: SearchParams): Promise { - const { knowledgeBaseIds, topK, filters, queryVector, distanceThreshold } = params + const { knowledgeBaseIds, topK, structuredFilters, queryVector, distanceThreshold } = params - if (!filters || Object.keys(filters).length === 0) { + if (!structuredFilters || structuredFilters.length === 0) { throw new Error('Tag filters are required for tag and vector search') } if (!queryVector || !distanceThreshold) { throw new Error('Query vector and distance threshold are required for tag and vector search') } - logger.debug(`[handleTagAndVectorSearch] Executing tag + vector search with filters:`, filters) + logger.debug( + `[handleTagAndVectorSearch] Executing tag + vector search with filters:`, + structuredFilters + ) // Step 1: Filter by tags first - const tagFilteredIds = await executeTagFilterQuery(knowledgeBaseIds, filters) + const tagFilteredIds = await executeTagFilterQuery(knowledgeBaseIds, structuredFilters) if (tagFilteredIds.length === 0) { logger.debug(`[handleTagAndVectorSearch] No results found after tag filtering`) diff --git a/apps/sim/app/api/knowledge/utils.ts b/apps/sim/app/api/knowledge/utils.ts index d1fc0d867..b1f796a1a 100644 --- a/apps/sim/app/api/knowledge/utils.ts +++ b/apps/sim/app/api/knowledge/utils.ts @@ -35,7 +35,7 @@ export interface DocumentData { enabled: boolean deletedAt?: Date | null uploadedAt: Date - // Document tags + // Text tags tag1?: string | null tag2?: string | null tag3?: string | null @@ -43,6 +43,19 @@ export interface DocumentData { tag5?: string | null tag6?: string | null tag7?: string | null + // Number tags (5 slots) + number1?: number | null + number2?: number | null + number3?: number | null + number4?: number | null + number5?: number | null + // Date tags (2 slots) + date1?: Date | null + date2?: Date | null + // Boolean tags (3 slots) + boolean1?: boolean | null + boolean2?: boolean | null + boolean3?: boolean | null } export interface EmbeddingData { @@ -58,7 +71,7 @@ export interface EmbeddingData { embeddingModel: string startOffset: number endOffset: number - // Tag fields for filtering + // Text tags tag1?: string | null tag2?: string | null tag3?: string | null @@ -66,6 +79,19 @@ export interface EmbeddingData { tag5?: string | null tag6?: string | null tag7?: string | null + // Number tags (5 slots) + number1?: number | null + number2?: number | null + number3?: number | null + number4?: number | null + number5?: number | null + // Date tags (2 slots) + date1?: Date | null + date2?: Date | null + // Boolean tags (3 slots) + boolean1?: boolean | null + boolean2?: boolean | null + boolean3?: boolean | null enabled: boolean createdAt: Date updatedAt: Date @@ -232,6 +258,27 @@ export async function checkDocumentWriteAccess( processingStartedAt: document.processingStartedAt, processingCompletedAt: document.processingCompletedAt, knowledgeBaseId: document.knowledgeBaseId, + // Text tags + tag1: document.tag1, + tag2: document.tag2, + tag3: document.tag3, + tag4: document.tag4, + tag5: document.tag5, + tag6: document.tag6, + tag7: document.tag7, + // Number tags (5 slots) + number1: document.number1, + number2: document.number2, + number3: document.number3, + number4: document.number4, + number5: document.number5, + // Date tags (2 slots) + date1: document.date1, + date2: document.date2, + // Boolean tags (3 slots) + boolean1: document.boolean1, + boolean2: document.boolean2, + boolean3: document.boolean3, }) .from(document) .where(and(eq(document.id, documentId), isNull(document.deletedAt))) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx index d35568d70..1f40cd20d 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx @@ -5,6 +5,7 @@ import { Loader2 } from 'lucide-react' import { Button, Combobox, + DatePicker, Input, Label, Modal, @@ -15,7 +16,7 @@ import { Trash, } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' -import { MAX_TAG_SLOTS, TAG_SLOTS, type TagSlot } from '@/lib/knowledge/constants' +import { ALL_TAG_SLOTS, type AllTagSlot, MAX_TAG_SLOTS } from '@/lib/knowledge/constants' import type { DocumentTag } from '@/lib/knowledge/tags/types' import { createLogger } from '@/lib/logs/console/logger' import { @@ -28,6 +29,54 @@ import { type DocumentData, useKnowledgeStore } from '@/stores/knowledge/store' const logger = createLogger('DocumentTagsModal') +/** Field type display labels */ +const FIELD_TYPE_LABELS: Record = { + text: 'Text', + number: 'Number', + date: 'Date', + boolean: 'Boolean', +} + +/** + * Gets the appropriate value when changing field types. + * Clears value when type changes to allow placeholder to show. + */ +function getValueForFieldType( + newFieldType: string, + currentFieldType: string, + currentValue: string +): string { + return newFieldType === currentFieldType ? currentValue : '' +} + +/** Format value for display based on field type */ +function formatValueForDisplay(value: string, fieldType: string): string { + if (!value) return '' + switch (fieldType) { + case 'boolean': + return value === 'true' ? 'True' : 'False' + case 'date': + try { + const date = new Date(value) + if (Number.isNaN(date.getTime())) return value + // For UTC dates, display the UTC date to prevent timezone shifts + // e.g., 2002-05-16T00:00:00.000Z should show as "May 16, 2002" not "May 15, 2002" + if (typeof value === 'string' && (value.endsWith('Z') || /[+-]\d{2}:\d{2}$/.test(value))) { + return new Date( + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate() + ).toLocaleDateString() + } + return date.toLocaleDateString() + } catch { + return value + } + default: + return value + } +} + interface DocumentTagsModalProps { open: boolean onOpenChange: (open: boolean) => void @@ -67,17 +116,21 @@ export function DocumentTagsModal({ const buildDocumentTags = useCallback((docData: DocumentData, definitions: TagDefinition[]) => { const tags: DocumentTag[] = [] - TAG_SLOTS.forEach((slot) => { - const value = docData[slot] as string | null | undefined + ALL_TAG_SLOTS.forEach((slot) => { + const rawValue = docData[slot] const definition = definitions.find((def) => def.tagSlot === slot) - if (value?.trim() && definition) { - tags.push({ - slot, - displayName: definition.displayName, - fieldType: definition.fieldType, - value: value.trim(), - }) + if (rawValue !== null && rawValue !== undefined && definition) { + // Convert value to string for storage + const stringValue = String(rawValue).trim() + if (stringValue) { + tags.push({ + slot, + displayName: definition.displayName, + fieldType: definition.fieldType, + value: stringValue, + }) + } } }) @@ -95,13 +148,15 @@ export function DocumentTagsModal({ try { const tagData: Record = {} - TAG_SLOTS.forEach((slot) => { - tagData[slot] = '' - }) - - tagsToSave.forEach((tag) => { - if (tag.value.trim()) { - tagData[tag.slot] = tag.value.trim() + // Only include tags that have values (omit empty ones) + // Use empty string for slots that should be cleared + ALL_TAG_SLOTS.forEach((slot) => { + const tag = tagsToSave.find((t) => t.slot === slot) + if (tag?.value.trim()) { + tagData[slot] = tag.value.trim() + } else { + // Use empty string to clear a tag (API schema expects string, not null) + tagData[slot] = '' } }) @@ -117,8 +172,8 @@ export function DocumentTagsModal({ throw new Error('Failed to update document tags') } - updateDocumentInStore(knowledgeBaseId, documentId, tagData) - onDocumentUpdate?.(tagData) + updateDocumentInStore(knowledgeBaseId, documentId, tagData as Record) + onDocumentUpdate?.(tagData as Record) await fetchTagDefinitions() } catch (error) { @@ -279,7 +334,7 @@ export function DocumentTagsModal({ const newDefinition: TagDefinitionInput = { displayName: formData.displayName, fieldType: formData.fieldType, - tagSlot: targetSlot as TagSlot, + tagSlot: targetSlot as AllTagSlot, } if (saveTagDefinitions) { @@ -359,20 +414,7 @@ export function DocumentTagsModal({
- - - {documentTags.length === 0 && !isCreatingTag && ( -
-

- No tags added yet. Add tags to help organize this document. -

-
- )} + {documentTags.map((tag, index) => (
@@ -383,9 +425,12 @@ export function DocumentTagsModal({ {tag.displayName} + + {FIELD_TYPE_LABELS[tag.fieldType] || tag.fieldType} +
- {tag.value} + {formatValueForDisplay(tag.value, tag.fieldType)}
- {/* Type selector commented out - only "text" type is currently supported -
- - -
- */} -
- - setEditTagForm({ ...editTagForm, value: e.target.value }) - } - placeholder='Enter tag value' - onKeyDown={(e) => { - if (e.key === 'Enter' && canSaveTag) { - e.preventDefault() - saveDocumentTag() + {editTagForm.fieldType === 'boolean' ? ( + setEditTagForm({ ...editTagForm, value })} + placeholder='Select value' + /> + ) : editTagForm.fieldType === 'number' ? ( + { + const val = e.target.value + // Allow empty, digits, decimal point, and negative sign + if (val === '' || /^-?\d*\.?\d*$/.test(val)) { + setEditTagForm({ ...editTagForm, value: val }) + } + }} + placeholder='Enter number' + inputMode='decimal' + onKeyDown={(e) => { + if (e.key === 'Enter' && canSaveTag) { + e.preventDefault() + saveDocumentTag() + } + if (e.key === 'Escape') { + e.preventDefault() + cancelEditingTag() + } + }} + /> + ) : editTagForm.fieldType === 'date' ? ( + setEditTagForm({ ...editTagForm, value })} + placeholder='Select date' + /> + ) : ( + + setEditTagForm({ ...editTagForm, value: e.target.value }) } - if (e.key === 'Escape') { - e.preventDefault() - cancelEditingTag() - } - }} - /> + placeholder='Enter tag value' + onKeyDown={(e) => { + if (e.key === 'Enter' && canSaveTag) { + e.preventDefault() + saveDocumentTag() + } + if (e.key === 'Escape') { + e.preventDefault() + cancelEditingTag() + } + }} + /> + )}
@@ -500,7 +588,7 @@ export function DocumentTagsModal({
))} - {!isTagEditing && ( + {documentTags.length > 0 && !isTagEditing && ( + {documentTags.length > 0 && ( + + )}
- {/* Type selector commented out - only "text" type is currently supported
- + + setCreateTagForm({ ...createTagForm, fieldType: value }) + } + placeholder='Select type' + /> + {!hasAvailableSlots(createTagForm.fieldType) && ( + + No available slots for this type. Choose a different type. + + )}
- */}
-
- )}
{renderHeader()} @@ -542,7 +403,6 @@ export function DocumentTagEntry({ {rows.map((row, rowIndex) => ( {renderTagNameCell(row, rowIndex)} - {renderTypeCell(row, rowIndex)} {renderValueCell(row, rowIndex)} {renderDeleteButton(rowIndex)} @@ -551,12 +411,11 @@ export function DocumentTagEntry({
- {/* Add Row Button and Tag slots usage indicator */} + {/* Add Row Button */} {!isPreview && !disabled && ( -
+
- - {/* Tag slots usage indicator */} -
- {tagDefinitions.length + newTagsBeingCreated} of {MAX_TAG_SLOTS} tag slots used -
)}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx index dec77c5ad..0c0cbf087 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx @@ -2,10 +2,21 @@ import { useState } from 'react' import { Plus } from 'lucide-react' -import { Trash } from '@/components/emcn/icons/trash' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' +import { + Button, + Combobox, + type ComboboxOption, + Input, + Label, + Popover, + PopoverAnchor, + PopoverContent, + PopoverItem, + PopoverScrollArea, + Trash, +} from '@/components/emcn' +import { FIELD_TYPE_LABELS, getPlaceholderForFieldType } from '@/lib/knowledge/constants' +import { type FilterFieldType, getOperatorsForFieldType } from '@/lib/knowledge/filters/types' import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text' import { checkTagTrigger, @@ -20,14 +31,22 @@ import { useSubBlockValue } from '../../hooks/use-sub-block-value' interface TagFilter { id: string tagName: string + tagSlot?: string + fieldType: FilterFieldType + operator: string tagValue: string + valueTo?: string // For 'between' operator } interface TagFilterRow { id: string cells: { tagName: string + tagSlot?: string + fieldType: FilterFieldType + operator: string value: string + valueTo?: string } } @@ -77,7 +96,13 @@ export function KnowledgeTagFilters({ const parseFilters = (filterValue: string | null): TagFilter[] => { if (!filterValue) return [] try { - return JSON.parse(filterValue) + const parsed = JSON.parse(filterValue) + // Handle legacy format (without fieldType/operator) + return parsed.map((f: TagFilter) => ({ + ...f, + fieldType: f.fieldType || 'text', + operator: f.operator || 'eq', + })) } catch { return [] } @@ -93,13 +118,17 @@ export function KnowledgeTagFilters({ id: filter.id, cells: { tagName: filter.tagName || '', + tagSlot: filter.tagSlot, + fieldType: filter.fieldType || 'text', + operator: filter.operator || 'eq', value: filter.tagValue || '', + valueTo: filter.valueTo, }, })) : [ { id: 'empty-row-0', - cells: { tagName: '', value: '' }, + cells: { tagName: '', fieldType: 'text', operator: 'eq', value: '' }, }, ] @@ -109,27 +138,75 @@ export function KnowledgeTagFilters({ setStoreValue(value) } - const handleCellChange = (rowIndex: number, column: string, value: string) => { + /** Convert rows back to TagFilter format */ + const rowsToFilters = (rowsToConvert: TagFilterRow[]): TagFilter[] => { + return rowsToConvert.map((row) => ({ + id: row.id, + tagName: row.cells.tagName || '', + tagSlot: row.cells.tagSlot, + fieldType: row.cells.fieldType || 'text', + operator: row.cells.operator || 'eq', + tagValue: row.cells.value || '', + valueTo: row.cells.valueTo, + })) + } + + const handleCellChange = (rowIndex: number, column: string, value: string | FilterFieldType) => { if (isPreview || disabled) return + const updatedRows = [...rows].map((row, idx) => { + if (idx === rowIndex) { + const newCells = { ...row.cells, [column]: value } + + // Reset operator when field type changes + if (column === 'fieldType') { + const operators = getOperatorsForFieldType(value as FilterFieldType) + newCells.operator = operators[0]?.value || 'eq' + newCells.value = '' // Reset value when type changes + newCells.valueTo = undefined + } + + // Reset valueTo if operator is not 'between' + if (column === 'operator' && value !== 'between') { + newCells.valueTo = undefined + } + + return { ...row, cells: newCells } + } + return row + }) + + updateFilters(rowsToFilters(updatedRows)) + } + + /** Handle tag name selection from dropdown */ + const handleTagNameSelection = (rowIndex: number, tagName: string) => { + if (isPreview || disabled) return + + // Find the tag definition to get fieldType and tagSlot + const tagDef = tagDefinitions.find((t) => t.displayName === tagName) + const fieldType = (tagDef?.fieldType || 'text') as FilterFieldType + const operators = getOperatorsForFieldType(fieldType) + const updatedRows = [...rows].map((row, idx) => { if (idx === rowIndex) { return { ...row, - cells: { ...row.cells, [column]: value }, + cells: { + ...row.cells, + tagName, + tagSlot: tagDef?.tagSlot, + fieldType, + operator: operators[0]?.value || 'eq', + value: '', // Reset value when tag changes + valueTo: undefined, + }, } } return row }) - // Convert back to TagFilter format - keep all rows, even empty ones - const updatedFilters = updatedRows.map((row) => ({ - id: row.id, - tagName: row.cells.tagName || '', - tagValue: row.cells.value || '', - })) - - updateFilters(updatedFilters) + updateFilters(rowsToFilters(updatedRows)) } const handleTagDropdownSelection = (rowIndex: number, column: string, value: string) => { @@ -145,36 +222,29 @@ export function KnowledgeTagFilters({ return row }) - // Convert back to TagFilter format - keep all rows, even empty ones - const updatedFilters = updatedRows.map((row) => ({ - id: row.id, - tagName: row.cells.tagName || '', - tagValue: row.cells.value || '', - })) - - const jsonValue = updatedFilters.length > 0 ? JSON.stringify(updatedFilters) : null + const jsonValue = + rowsToFilters(updatedRows).length > 0 ? JSON.stringify(rowsToFilters(updatedRows)) : null emitTagSelection(jsonValue) } const handleAddRow = () => { if (isPreview || disabled) return - const newRowId = `filter-${filters.length}-${Math.random().toString(36).substr(2, 9)}` - const newFilters = [...filters, { id: newRowId, tagName: '', tagValue: '' }] - updateFilters(newFilters) + const newRowId = `filter-${filters.length}-${Math.random().toString(36).slice(2, 11)}` + const newFilter: TagFilter = { + id: newRowId, + tagName: '', + fieldType: 'text', + operator: 'eq', + tagValue: '', + } + updateFilters([...filters, newFilter]) } const handleDeleteRow = (rowIndex: number) => { if (isPreview || disabled || rows.length <= 1) return const updatedRows = rows.filter((_, idx) => idx !== rowIndex) - - const updatedFilters = updatedRows.map((row) => ({ - id: row.id, - tagName: row.cells.tagName || '', - tagValue: row.cells.value || '', - })) - - updateFilters(updatedFilters) + updateFilters(rowsToFilters(updatedRows)) } if (isPreview) { @@ -193,106 +263,120 @@ export function KnowledgeTagFilters({ const renderHeader = () => ( - Tag Name - Value + Tag + Operator + Value + ) const renderTagNameCell = (row: TagFilterRow, rowIndex: number) => { const cellValue = row.cells.tagName || '' - const showDropdown = dropdownStates[rowIndex] || false + const isOpen = dropdownStates[rowIndex] || false - const setShowDropdown = (show: boolean) => { - setDropdownStates((prev) => ({ ...prev, [rowIndex]: show })) - } - - const handleDropdownClick = (e: React.MouseEvent) => { - e.preventDefault() - e.stopPropagation() - if (!disabled && !isLoading) { - if (!showDropdown) { - setShowDropdown(true) - } - } - } - - const handleFocus = () => { - if (!disabled && !isLoading) { - setShowDropdown(true) - } - } - - const handleBlur = () => { - // Delay closing to allow dropdown selection - setTimeout(() => setShowDropdown(false), 150) + const setIsOpen = (open: boolean) => { + setDropdownStates((prev) => ({ ...prev, [rowIndex]: open })) } return ( -
- -
-
- {formatDisplayText(cellValue || 'Select tag', { - accessiblePrefixes, - highlightAll: !accessiblePrefixes, - })} -
-
- {showDropdown && tagDefinitions.length > 0 && ( -
-
-
- {tagDefinitions.map((tag) => ( -
{ - e.preventDefault() - handleCellChange(rowIndex, 'tagName', tag.displayName) - setShowDropdown(false) - }} - > - {tag.displayName} -
- ))} -
+ + +
!disabled && !isLoading && setIsOpen(true)} + > + +
+ {cellValue || 'Select tag'}
+
+ {tagDefinitions.length > 0 && ( + + + {tagDefinitions.map((tag) => ( + { + handleTagNameSelection(rowIndex, tag.displayName) + setIsOpen(false) + }} + > + {tag.displayName} + + {FIELD_TYPE_LABELS[tag.fieldType] || 'Text'} + + + ))} + + )} -
+ + + ) + } + + /** Render operator cell */ + const renderOperatorCell = (row: TagFilterRow, rowIndex: number) => { + const fieldType = row.cells.fieldType || 'text' + const operator = row.cells.operator || 'eq' + const operators = getOperatorsForFieldType(fieldType) + + const operatorOptions: ComboboxOption[] = operators.map((op) => ({ + value: op.value, + label: op.label, + })) + + return ( + + handleCellChange(rowIndex, 'operator', value)} + disabled={disabled || !row.cells.tagName} + placeholder='Operator' + size='sm' + /> ) } const renderValueCell = (row: TagFilterRow, rowIndex: number) => { const cellValue = row.cells.value || '' + const fieldType = row.cells.fieldType || 'text' + const operator = row.cells.operator || 'eq' + const isBetween = operator === 'between' + const valueTo = row.cells.valueTo || '' + const isDisabled = disabled || !row.cells.tagName + const placeholder = getPlaceholderForFieldType(fieldType) - return ( - -
- { - const newValue = e.target.value - const cursorPosition = e.target.selectionStart ?? 0 + // Single text input for all field types with variable support + const renderInput = (value: string, column: 'value' | 'valueTo') => ( +
+ { + const newValue = e.target.value + const cursorPosition = e.target.selectionStart ?? 0 - handleCellChange(rowIndex, 'value', newValue) + handleCellChange(rowIndex, column, newValue) - // Check for tag trigger + // Check for tag trigger (only for primary value input) + if (column === 'value') { const tagTrigger = checkTagTrigger(newValue, cursorPosition) setActiveTagDropdown({ @@ -302,52 +386,69 @@ export function KnowledgeTagFilters({ activeSourceBlockId: null, element: e.target, }) - }} - onFocus={(e) => { - if (!disabled) { - setActiveTagDropdown({ - rowIndex, - showTags: false, - cursorPosition: 0, - activeSourceBlockId: null, - element: e.target, - }) - } - }} - onBlur={() => { + } + }} + onFocus={(e) => { + if (!isDisabled && column === 'value') { + setActiveTagDropdown({ + rowIndex, + showTags: false, + cursorPosition: 0, + activeSourceBlockId: null, + element: e.target, + }) + } + }} + onBlur={() => { + if (column === 'value') { setTimeout(() => setActiveTagDropdown(null), 200) - }} - onKeyDown={(e) => { - if (e.key === 'Escape') { - setActiveTagDropdown(null) - } - }} - disabled={disabled} - autoComplete='off' - className='w-full border-0 text-transparent caret-foreground placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0' - /> -
-
- {formatDisplayText(cellValue, { - accessiblePrefixes, - highlightAll: !accessiblePrefixes, - })} -
+ } + }} + onKeyDown={(e) => { + if (e.key === 'Escape') { + setActiveTagDropdown(null) + } + }} + disabled={isDisabled} + autoComplete='off' + placeholder={placeholder} + className='w-full border-0 text-transparent caret-foreground placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0' + /> +
+
+ {formatDisplayText(value || '', { + accessiblePrefixes, + highlightAll: !accessiblePrefixes, + })}
- +
) + + // Render with optional "between" second input + if (isBetween) { + return ( + +
+ {renderInput(cellValue, 'value')} + to + {renderInput(valueTo, 'valueTo')} +
+ + ) + } + + return {renderInput(cellValue, 'value')} } const renderDeleteButton = (rowIndex: number) => { const canDelete = !isPreview && !disabled return canDelete ? ( - + diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index a24d3ff9e..d548ee0be 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -134,29 +134,111 @@ const isMessagesArray = (value: unknown): value is Array<{ role: string; content ) } +/** + * Type guard for tag filter array (used in knowledge block filters) + */ +interface TagFilterItem { + id: string + tagName: string + fieldType?: string + operator?: string + tagValue: string +} + +const isTagFilterArray = (value: unknown): value is TagFilterItem[] => { + if (!Array.isArray(value) || value.length === 0) return false + const firstItem = value[0] + return ( + typeof firstItem === 'object' && + firstItem !== null && + 'tagName' in firstItem && + 'tagValue' in firstItem + ) +} + +/** + * Type guard for document tag entry array (used in knowledge block create document) + */ +interface DocumentTagItem { + id: string + tagName: string + fieldType?: string + value: string +} + +const isDocumentTagArray = (value: unknown): value is DocumentTagItem[] => { + if (!Array.isArray(value) || value.length === 0) return false + const firstItem = value[0] + return ( + typeof firstItem === 'object' && + firstItem !== null && + 'tagName' in firstItem && + 'value' in firstItem && + !('tagValue' in firstItem) // Distinguish from tag filters + ) +} + +/** + * Attempts to parse a JSON string, returns the parsed value or the original value if parsing fails + */ +const tryParseJson = (value: unknown): unknown => { + if (typeof value !== 'string') return value + try { + const trimmed = value.trim() + if ( + (trimmed.startsWith('[') && trimmed.endsWith(']')) || + (trimmed.startsWith('{') && trimmed.endsWith('}')) + ) { + return JSON.parse(trimmed) + } + } catch { + // Not valid JSON, return original + } + return value +} + /** * Formats a subblock value for display, intelligently handling nested objects and arrays. */ const getDisplayValue = (value: unknown): string => { if (value == null || value === '') return '-' - if (isMessagesArray(value)) { - const firstMessage = value[0] + // Try parsing JSON strings first + const parsedValue = tryParseJson(value) + + if (isMessagesArray(parsedValue)) { + const firstMessage = parsedValue[0] if (!firstMessage?.content || firstMessage.content.trim() === '') return '-' const content = firstMessage.content.trim() return content.length > 50 ? `${content.slice(0, 50)}...` : content } - if (isVariableAssignmentsArray(value)) { - const names = value.map((a) => a.variableName).filter((name): name is string => !!name) + if (isVariableAssignmentsArray(parsedValue)) { + const names = parsedValue.map((a) => a.variableName).filter((name): name is string => !!name) if (names.length === 0) return '-' if (names.length === 1) return names[0] if (names.length === 2) return `${names[0]}, ${names[1]}` return `${names[0]}, ${names[1]} +${names.length - 2}` } - if (isTableRowArray(value)) { - const nonEmptyRows = value.filter((row) => { + if (isTagFilterArray(parsedValue)) { + const validFilters = parsedValue.filter((f) => f.tagName?.trim()) + if (validFilters.length === 0) return '-' + if (validFilters.length === 1) return validFilters[0].tagName + if (validFilters.length === 2) return `${validFilters[0].tagName}, ${validFilters[1].tagName}` + return `${validFilters[0].tagName}, ${validFilters[1].tagName} +${validFilters.length - 2}` + } + + if (isDocumentTagArray(parsedValue)) { + const validTags = parsedValue.filter((t) => t.tagName?.trim()) + if (validTags.length === 0) return '-' + if (validTags.length === 1) return validTags[0].tagName + if (validTags.length === 2) return `${validTags[0].tagName}, ${validTags[1].tagName}` + return `${validTags[0].tagName}, ${validTags[1].tagName} +${validTags.length - 2}` + } + + if (isTableRowArray(parsedValue)) { + const nonEmptyRows = parsedValue.filter((row) => { const cellValues = Object.values(row.cells) return cellValues.some((cell) => cell && cell.trim() !== '') }) @@ -175,16 +257,16 @@ const getDisplayValue = (value: unknown): string => { return `${nonEmptyRows.length} rows` } - if (isFieldFormatArray(value)) { - const namedFields = value.filter((field) => field.name && field.name.trim() !== '') + if (isFieldFormatArray(parsedValue)) { + const namedFields = parsedValue.filter((field) => field.name && field.name.trim() !== '') if (namedFields.length === 0) return '-' if (namedFields.length === 1) return namedFields[0].name if (namedFields.length === 2) return `${namedFields[0].name}, ${namedFields[1].name}` return `${namedFields[0].name}, ${namedFields[1].name} +${namedFields.length - 2}` } - if (isPlainObject(value)) { - const entries = Object.entries(value).filter( + if (isPlainObject(parsedValue)) { + const entries = Object.entries(parsedValue).filter( ([, val]) => val !== null && val !== undefined && val !== '' ) @@ -201,8 +283,10 @@ const getDisplayValue = (value: unknown): string => { return entries.length > 2 ? `${preview} +${entries.length - 2}` : preview } - if (Array.isArray(value)) { - const nonEmptyItems = value.filter((item) => item !== null && item !== undefined && item !== '') + if (Array.isArray(parsedValue)) { + const nonEmptyItems = parsedValue.filter( + (item) => item !== null && item !== undefined && item !== '' + ) if (nonEmptyItems.length === 0) return '-' const getItemDisplayValue = (item: unknown): string => { @@ -220,10 +304,11 @@ const getDisplayValue = (value: unknown): string => { return `${getItemDisplayValue(nonEmptyItems[0])}, ${getItemDisplayValue(nonEmptyItems[1])} +${nonEmptyItems.length - 2}` } + // For non-array, non-object values, use original value for string conversion const stringValue = String(value) if (stringValue === '[object Object]') { try { - const json = JSON.stringify(value) + const json = JSON.stringify(parsedValue) if (json.length <= 40) return json return `${json.slice(0, 37)}...` } catch { diff --git a/apps/sim/components/emcn/components/date-picker/date-picker.tsx b/apps/sim/components/emcn/components/date-picker/date-picker.tsx new file mode 100644 index 000000000..3827a4a3e --- /dev/null +++ b/apps/sim/components/emcn/components/date-picker/date-picker.tsx @@ -0,0 +1,408 @@ +/** + * DatePicker component with calendar dropdown for date selection. + * Uses Radix UI Popover primitives for positioning and accessibility. + * + * @example + * ```tsx + * // Basic date picker + * setDate(dateString)} + * placeholder="Select date" + * /> + * ``` + */ + +'use client' + +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' +import { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react' +import { cn } from '@/lib/core/utils/cn' +import { Popover, PopoverAnchor, PopoverContent } from '../popover/popover' + +/** + * Variant styles for the date picker trigger button. + * Matches the combobox and input styling patterns. + */ +const datePickerVariants = cva( + 'flex w-full rounded-[4px] border border-[var(--surface-11)] bg-[var(--surface-6)] dark:bg-[var(--surface-9)] px-[8px] font-sans font-medium text-[var(--text-primary)] placeholder:text-[var(--text-muted)] dark:placeholder:text-[var(--text-muted)] outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 hover:border-[var(--surface-14)] hover:bg-[var(--surface-9)] dark:hover:border-[var(--surface-13)] dark:hover:bg-[var(--surface-11)]', + { + variants: { + variant: { + default: '', + }, + size: { + default: 'py-[6px] text-sm', + sm: 'py-[5px] text-[12px]', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +) + +export interface DatePickerProps + extends Omit, 'onChange'>, + VariantProps { + /** Current selected date value (YYYY-MM-DD string or Date) */ + value?: string | Date + /** Callback when date changes, returns YYYY-MM-DD format */ + onChange?: (value: string) => void + /** Placeholder text when no value is selected */ + placeholder?: string + /** Whether the picker is disabled */ + disabled?: boolean + /** Size variant */ + size?: 'default' | 'sm' +} + +/** + * Month names for calendar display. + */ +const MONTHS = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', +] + +/** + * Day abbreviations for calendar header. + */ +const DAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] + +/** + * Gets the number of days in a given month. + */ +function getDaysInMonth(year: number, month: number): number { + return new Date(year, month + 1, 0).getDate() +} + +/** + * Gets the day of the week (0-6) for the first day of the month. + */ +function getFirstDayOfMonth(year: number, month: number): number { + return new Date(year, month, 1).getDay() +} + +/** + * Formats a date for display in the trigger button. + */ +function formatDateForDisplay(date: Date | null): string { + if (!date) return '' + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + }) +} + +/** + * Formats a date as YYYY-MM-DD string. + */ +function formatDateAsString(year: number, month: number, day: number): string { + const m = (month + 1).toString().padStart(2, '0') + const d = day.toString().padStart(2, '0') + return `${year}-${m}-${d}` +} + +/** + * Parses a string or Date value into a Date object. + * Handles various date formats including YYYY-MM-DD and ISO strings. + */ +function parseDate(value: string | Date | undefined): Date | null { + if (!value) return null + + if (value instanceof Date) { + if (Number.isNaN(value.getTime())) return null + return value + } + + try { + // Handle YYYY-MM-DD format (treat as local date) + if (/^\d{4}-\d{2}-\d{2}$/.test(value)) { + const [year, month, day] = value.split('-').map(Number) + return new Date(year, month - 1, day) + } + + // Handle ISO strings with timezone (extract date part as local) + if (value.endsWith('Z') || /[+-]\d{2}:\d{2}$/.test(value)) { + const date = new Date(value) + if (Number.isNaN(date.getTime())) return null + // Use UTC date components to prevent timezone shift + return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) + } + + // Fallback: try parsing as-is + const date = new Date(value) + return Number.isNaN(date.getTime()) ? null : date + } catch { + return null + } +} + +/** + * DatePicker component matching emcn design patterns. + * Provides a calendar dropdown for date selection. + */ +const DatePicker = React.forwardRef( + ( + { className, variant, size, value, onChange, placeholder = 'Select date', disabled, ...props }, + ref + ) => { + const [open, setOpen] = React.useState(false) + const selectedDate = parseDate(value) + + const [viewMonth, setViewMonth] = React.useState(() => { + const d = selectedDate || new Date() + return d.getMonth() + }) + const [viewYear, setViewYear] = React.useState(() => { + const d = selectedDate || new Date() + return d.getFullYear() + }) + + // Update view when value changes externally + React.useEffect(() => { + if (selectedDate) { + setViewMonth(selectedDate.getMonth()) + setViewYear(selectedDate.getFullYear()) + } + }, [value]) + + /** + * Handles selection of a specific day in the calendar. + */ + const handleSelectDate = React.useCallback( + (day: number) => { + onChange?.(formatDateAsString(viewYear, viewMonth, day)) + setOpen(false) + }, + [viewYear, viewMonth, onChange] + ) + + /** + * Navigates to the previous month. + */ + const goToPrevMonth = React.useCallback(() => { + if (viewMonth === 0) { + setViewMonth(11) + setViewYear((prev) => prev - 1) + } else { + setViewMonth((prev) => prev - 1) + } + }, [viewMonth]) + + /** + * Navigates to the next month. + */ + const goToNextMonth = React.useCallback(() => { + if (viewMonth === 11) { + setViewMonth(0) + setViewYear((prev) => prev + 1) + } else { + setViewMonth((prev) => prev + 1) + } + }, [viewMonth]) + + /** + * Selects today's date and closes the picker. + */ + const handleSelectToday = React.useCallback(() => { + const now = new Date() + setViewMonth(now.getMonth()) + setViewYear(now.getFullYear()) + onChange?.(formatDateAsString(now.getFullYear(), now.getMonth(), now.getDate())) + setOpen(false) + }, [onChange]) + + const daysInMonth = getDaysInMonth(viewYear, viewMonth) + const firstDayOfMonth = getFirstDayOfMonth(viewYear, viewMonth) + + /** + * Checks if a day is today's date. + */ + const isToday = React.useCallback( + (day: number) => { + const today = new Date() + return ( + today.getDate() === day && + today.getMonth() === viewMonth && + today.getFullYear() === viewYear + ) + }, + [viewMonth, viewYear] + ) + + /** + * Checks if a day is the currently selected date. + */ + const isSelected = React.useCallback( + (day: number) => { + return ( + selectedDate && + selectedDate.getDate() === day && + selectedDate.getMonth() === viewMonth && + selectedDate.getFullYear() === viewYear + ) + }, + [selectedDate, viewMonth, viewYear] + ) + + // Build calendar grid + const calendarDays = React.useMemo(() => { + const days: (number | null)[] = [] + for (let i = 0; i < firstDayOfMonth; i++) { + days.push(null) + } + for (let day = 1; day <= daysInMonth; day++) { + days.push(day) + } + return days + }, [firstDayOfMonth, daysInMonth]) + + /** + * Handles keyboard events on the trigger. + */ + const handleKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (!disabled && (e.key === 'Enter' || e.key === ' ')) { + e.preventDefault() + setOpen(!open) + } + }, + [disabled, open] + ) + + /** + * Handles click on the trigger. + */ + const handleTriggerClick = React.useCallback(() => { + if (!disabled) { + setOpen(!open) + } + }, [disabled, open]) + + return ( + +
+ +
+ + {selectedDate ? formatDateForDisplay(selectedDate) : placeholder} + + +
+
+ + + {/* Calendar Header */} +
+ + + {MONTHS[viewMonth]} {viewYear} + + +
+ + {/* Day Headers */} +
+ {DAYS.map((day) => ( +
+ {day} +
+ ))} +
+ + {/* Calendar Grid */} +
+ {calendarDays.map((day, index) => ( +
+ {day !== null && ( + + )} +
+ ))} +
+ + {/* Today Button */} +
+ +
+
+
+
+ ) + } +) + +DatePicker.displayName = 'DatePicker' + +export { DatePicker, datePickerVariants } diff --git a/apps/sim/components/emcn/components/index.ts b/apps/sim/components/emcn/components/index.ts index 34ad94ee2..78d9931ca 100644 --- a/apps/sim/components/emcn/components/index.ts +++ b/apps/sim/components/emcn/components/index.ts @@ -10,7 +10,8 @@ export { languages, } from './code/code' export { Combobox, type ComboboxOption } from './combobox/combobox' -export { Input } from './input/input' +export { DatePicker, type DatePickerProps, datePickerVariants } from './date-picker/date-picker' +export { Input, type InputProps, inputVariants } from './input/input' export { Label } from './label/label' export { MODAL_SIZES, diff --git a/apps/sim/components/emcn/components/input/input.tsx b/apps/sim/components/emcn/components/input/input.tsx index 982837049..eb0e9eaff 100644 --- a/apps/sim/components/emcn/components/input/input.tsx +++ b/apps/sim/components/emcn/components/input/input.tsx @@ -1,7 +1,30 @@ +/** + * A minimal input component matching the emcn design system. + * + * @example + * ```tsx + * import { Input } from '@/components/emcn' + * + * // Basic usage + * + * + * // Controlled input + * setValue(e.target.value)} /> + * + * // Disabled state + * + * ``` + * + * @see inputVariants for available styling variants + */ import * as React from 'react' import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '@/lib/core/utils/cn' +/** + * Variant styles for the Input component. + * Currently supports a 'default' variant. + */ const inputVariants = cva( 'flex w-full rounded-[4px] border border-[var(--surface-11)] bg-[var(--surface-6)] dark:bg-[var(--surface-9)] px-[8px] py-[6px] font-medium font-sans text-sm text-foreground transition-colors placeholder:text-[var(--text-muted)] dark:placeholder:text-[var(--text-muted)] outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50', { @@ -16,6 +39,10 @@ const inputVariants = cva( } ) +/** + * Props for the Input component. + * Extends native input attributes with variant support. + */ export interface InputProps extends React.InputHTMLAttributes, VariantProps {} diff --git a/apps/sim/components/emcn/components/popover/popover.tsx b/apps/sim/components/emcn/components/popover/popover.tsx index dd8a06e4a..71f6f0225 100644 --- a/apps/sim/components/emcn/components/popover/popover.tsx +++ b/apps/sim/components/emcn/components/popover/popover.tsx @@ -247,6 +247,11 @@ export interface PopoverContentProps * @default false */ border?: boolean + /** + * When true, the popover will flip to avoid collisions with viewport edges + * @default true + */ + avoidCollisions?: boolean } /** @@ -279,6 +284,7 @@ const PopoverContent = React.forwardRef< sideOffset, collisionPadding = 8, border = false, + avoidCollisions = true, ...restProps }, ref @@ -328,7 +334,7 @@ const PopoverContent = React.forwardRef< align={align} sideOffset={effectiveSideOffset} collisionPadding={collisionPadding} - avoidCollisions={true} + avoidCollisions={avoidCollisions} sticky='partial' onWheel={handleWheel} {...restProps} diff --git a/apps/sim/hooks/use-knowledge-base-tag-definitions.ts b/apps/sim/hooks/use-knowledge-base-tag-definitions.ts index 57c0cac36..b753307a7 100644 --- a/apps/sim/hooks/use-knowledge-base-tag-definitions.ts +++ b/apps/sim/hooks/use-knowledge-base-tag-definitions.ts @@ -1,14 +1,14 @@ 'use client' import { useCallback, useEffect, useState } from 'react' -import type { TagSlot } from '@/lib/knowledge/constants' +import type { AllTagSlot } from '@/lib/knowledge/constants' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('useKnowledgeBaseTagDefinitions') export interface TagDefinition { id: string - tagSlot: TagSlot + tagSlot: AllTagSlot displayName: string fieldType: string createdAt: string diff --git a/apps/sim/hooks/use-tag-definitions.ts b/apps/sim/hooks/use-tag-definitions.ts index 5e15b2786..46ac68b86 100644 --- a/apps/sim/hooks/use-tag-definitions.ts +++ b/apps/sim/hooks/use-tag-definitions.ts @@ -1,14 +1,14 @@ 'use client' import { useCallback, useEffect, useState } from 'react' -import type { TagSlot } from '@/lib/knowledge/constants' +import type { AllTagSlot } from '@/lib/knowledge/constants' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('useTagDefinitions') export interface TagDefinition { id: string - tagSlot: TagSlot + tagSlot: AllTagSlot displayName: string fieldType: string createdAt: string @@ -16,7 +16,7 @@ export interface TagDefinition { } export interface TagDefinitionInput { - tagSlot: TagSlot + tagSlot: AllTagSlot displayName: string fieldType: string // Optional: for editing existing definitions diff --git a/apps/sim/lib/knowledge/chunks/service.ts b/apps/sim/lib/knowledge/chunks/service.ts index 50cd0bc59..c66a39fae 100644 --- a/apps/sim/lib/knowledge/chunks/service.ts +++ b/apps/sim/lib/knowledge/chunks/service.ts @@ -92,7 +92,7 @@ export async function queryChunks( export async function createChunk( knowledgeBaseId: string, documentId: string, - docTags: Record, + docTags: Record, chunkData: CreateChunkData, requestId: string ): Promise { @@ -131,14 +131,27 @@ export async function createChunk( embeddingModel: 'text-embedding-3-small', startOffset: 0, // Manual chunks don't have document offsets endOffset: chunkData.content.length, - // Inherit tags from parent document - tag1: docTags.tag1, - tag2: docTags.tag2, - tag3: docTags.tag3, - tag4: docTags.tag4, - tag5: docTags.tag5, - tag6: docTags.tag6, - tag7: docTags.tag7, + // Inherit text tags from parent document + tag1: docTags.tag1 as string | null, + tag2: docTags.tag2 as string | null, + tag3: docTags.tag3 as string | null, + tag4: docTags.tag4 as string | null, + tag5: docTags.tag5 as string | null, + tag6: docTags.tag6 as string | null, + tag7: docTags.tag7 as string | null, + // Inherit number tags from parent document (5 slots) + number1: docTags.number1 as number | null, + number2: docTags.number2 as number | null, + number3: docTags.number3 as number | null, + number4: docTags.number4 as number | null, + number5: docTags.number5 as number | null, + // Inherit date tags from parent document (2 slots) + date1: docTags.date1 as Date | null, + date2: docTags.date2 as Date | null, + // Inherit boolean tags from parent document (3 slots) + boolean1: docTags.boolean1 as boolean | null, + boolean2: docTags.boolean2 as boolean | null, + boolean3: docTags.boolean3 as boolean | null, enabled: chunkData.enabled ?? true, createdAt: now, updatedAt: now, diff --git a/apps/sim/lib/knowledge/constants.ts b/apps/sim/lib/knowledge/constants.ts index c63ba68d1..3ed4b5e4e 100644 --- a/apps/sim/lib/knowledge/constants.ts +++ b/apps/sim/lib/knowledge/constants.ts @@ -3,18 +3,55 @@ export const TAG_SLOT_CONFIG = { slots: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7'] as const, maxSlots: 7, }, + number: { + slots: ['number1', 'number2', 'number3', 'number4', 'number5'] as const, + maxSlots: 5, + }, + date: { + slots: ['date1', 'date2'] as const, + maxSlots: 2, + }, + boolean: { + slots: ['boolean1', 'boolean2', 'boolean3'] as const, + maxSlots: 3, + }, } as const export const SUPPORTED_FIELD_TYPES = Object.keys(TAG_SLOT_CONFIG) as Array< keyof typeof TAG_SLOT_CONFIG > +/** Text tag slots (for backwards compatibility) */ export const TAG_SLOTS = TAG_SLOT_CONFIG.text.slots +/** All tag slots across all field types */ +export const ALL_TAG_SLOTS = [ + ...TAG_SLOT_CONFIG.text.slots, + ...TAG_SLOT_CONFIG.number.slots, + ...TAG_SLOT_CONFIG.date.slots, + ...TAG_SLOT_CONFIG.boolean.slots, +] as const + export const MAX_TAG_SLOTS = TAG_SLOT_CONFIG.text.maxSlots +/** Type for text tag slots (for backwards compatibility) */ export type TagSlot = (typeof TAG_SLOTS)[number] +/** Type for all tag slots */ +export type AllTagSlot = (typeof ALL_TAG_SLOTS)[number] + +/** Type for number tag slots */ +export type NumberTagSlot = (typeof TAG_SLOT_CONFIG.number.slots)[number] + +/** Type for date tag slots */ +export type DateTagSlot = (typeof TAG_SLOT_CONFIG.date.slots)[number] + +/** Type for boolean tag slots */ +export type BooleanTagSlot = (typeof TAG_SLOT_CONFIG.boolean.slots)[number] + +/** + * Get the available slots for a field type + */ export function getSlotsForFieldType(fieldType: string): readonly string[] { const config = TAG_SLOT_CONFIG[fieldType as keyof typeof TAG_SLOT_CONFIG] if (!config) { @@ -22,3 +59,52 @@ export function getSlotsForFieldType(fieldType: string): readonly string[] { } return config.slots } + +/** + * Get the field type for a tag slot + */ +export function getFieldTypeForSlot(tagSlot: string): keyof typeof TAG_SLOT_CONFIG | null { + for (const [fieldType, config] of Object.entries(TAG_SLOT_CONFIG)) { + if ((config.slots as readonly string[]).includes(tagSlot)) { + return fieldType as keyof typeof TAG_SLOT_CONFIG + } + } + return null +} + +/** + * Check if a slot is valid for a given field type + */ +export function isValidSlotForFieldType(tagSlot: string, fieldType: string): boolean { + const config = TAG_SLOT_CONFIG[fieldType as keyof typeof TAG_SLOT_CONFIG] + if (!config) { + return false + } + return (config.slots as readonly string[]).includes(tagSlot) +} + +/** + * Display labels for field types + */ +export const FIELD_TYPE_LABELS: Record = { + text: 'Text', + number: 'Number', + date: 'Date', + boolean: 'Boolean', +} + +/** + * Get placeholder text for value input based on field type + */ +export function getPlaceholderForFieldType(fieldType: string): string { + switch (fieldType) { + case 'boolean': + return 'true or false' + case 'number': + return 'Enter number' + case 'date': + return 'YYYY-MM-DD' + default: + return 'Enter value' + } +} diff --git a/apps/sim/lib/knowledge/documents/service.ts b/apps/sim/lib/knowledge/documents/service.ts index c9ff61282..8a3be03e0 100644 --- a/apps/sim/lib/knowledge/documents/service.ts +++ b/apps/sim/lib/knowledge/documents/service.ts @@ -5,12 +5,18 @@ import { tasks } from '@trigger.dev/sdk' import { and, asc, desc, eq, inArray, isNull, sql } from 'drizzle-orm' import { env } from '@/lib/core/config/env' import { getStorageMethod, isRedisStorage } from '@/lib/core/storage' -import { getSlotsForFieldType, type TAG_SLOT_CONFIG } from '@/lib/knowledge/constants' import { processDocument } from '@/lib/knowledge/documents/document-processor' import { DocumentProcessingQueue } from '@/lib/knowledge/documents/queue' import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types' import { generateEmbeddings } from '@/lib/knowledge/embeddings' -import { getNextAvailableSlot } from '@/lib/knowledge/tags/service' +import { + buildUndefinedTagsError, + parseBooleanValue, + parseDateValue, + parseNumberValue, + validateTagValue, +} from '@/lib/knowledge/tags/utils' +import type { ProcessedDocumentTags } from '@/lib/knowledge/types' import { createLogger } from '@/lib/logs/console/logger' import type { DocumentProcessingPayload } from '@/background/knowledge-processing' @@ -113,80 +119,194 @@ export interface DocumentTagData { } /** - * Process structured document tags and create tag definitions + * Process structured document tags and validate them against existing definitions + * Throws an error if a tag doesn't exist or if the value doesn't match the expected type */ export async function processDocumentTags( knowledgeBaseId: string, tagData: DocumentTagData[], requestId: string -): Promise> { - const result: Record = {} +): Promise { + // Helper to set a tag value with proper typing + const setTagValue = ( + tags: ProcessedDocumentTags, + slot: string, + value: string | number | Date | boolean | null + ): void => { + switch (slot) { + case 'tag1': + tags.tag1 = value as string | null + break + case 'tag2': + tags.tag2 = value as string | null + break + case 'tag3': + tags.tag3 = value as string | null + break + case 'tag4': + tags.tag4 = value as string | null + break + case 'tag5': + tags.tag5 = value as string | null + break + case 'tag6': + tags.tag6 = value as string | null + break + case 'tag7': + tags.tag7 = value as string | null + break + case 'number1': + tags.number1 = value as number | null + break + case 'number2': + tags.number2 = value as number | null + break + case 'number3': + tags.number3 = value as number | null + break + case 'number4': + tags.number4 = value as number | null + break + case 'number5': + tags.number5 = value as number | null + break + case 'date1': + tags.date1 = value as Date | null + break + case 'date2': + tags.date2 = value as Date | null + break + case 'boolean1': + tags.boolean1 = value as boolean | null + break + case 'boolean2': + tags.boolean2 = value as boolean | null + break + case 'boolean3': + tags.boolean3 = value as boolean | null + break + } + } - const textSlots = getSlotsForFieldType('text') - textSlots.forEach((slot) => { - result[slot] = null - }) + const result: ProcessedDocumentTags = { + tag1: null, + tag2: null, + tag3: null, + tag4: null, + tag5: null, + tag6: null, + tag7: null, + number1: null, + number2: null, + number3: null, + number4: null, + number5: null, + date1: null, + date2: null, + boolean1: null, + boolean2: null, + boolean3: null, + } if (!Array.isArray(tagData) || tagData.length === 0) { return result } - try { - const existingDefinitions = await db - .select() - .from(knowledgeBaseTagDefinitions) - .where(eq(knowledgeBaseTagDefinitions.knowledgeBaseId, knowledgeBaseId)) + // Fetch 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 as string, def])) + const existingByName = new Map(existingDefinitions.map((def) => [def.displayName, def])) - for (const tag of tagData) { - if (!tag.tagName?.trim() || !tag.value?.trim()) continue + // First pass: collect all validation errors + const undefinedTags: string[] = [] + const typeErrors: string[] = [] - const tagName = tag.tagName.trim() - const fieldType = tag.fieldType - const value = tag.value.trim() + for (const tag of tagData) { + // Skip if no tag name + if (!tag.tagName?.trim()) continue - let targetSlot: string | null = null + const tagName = tag.tagName.trim() + const fieldType = tag.fieldType || 'text' - // Check if tag definition already exists - const existingDef = existingByName.get(tagName) - if (existingDef) { - targetSlot = existingDef.tagSlot - } else { - // Find next available slot using the tags service function - targetSlot = await getNextAvailableSlot(knowledgeBaseId, fieldType, existingBySlot) + // For boolean, check if value is defined; for others, check if value is non-empty + const hasValue = + fieldType === 'boolean' + ? tag.value !== undefined && tag.value !== null && tag.value !== '' + : tag.value?.trim && tag.value.trim().length > 0 - // Create new tag definition if we have a slot - if (targetSlot) { - const newDefinition = { - id: randomUUID(), - knowledgeBaseId, - tagSlot: targetSlot as (typeof TAG_SLOT_CONFIG.text.slots)[number], - displayName: tagName, - fieldType, - createdAt: new Date(), - updatedAt: new Date(), - } + if (!hasValue) continue - await db.insert(knowledgeBaseTagDefinitions).values(newDefinition) - existingBySlot.set(targetSlot, newDefinition) - - logger.info(`[${requestId}] Created tag definition: ${tagName} -> ${targetSlot}`) - } - } - - // Assign value to the slot - if (targetSlot) { - result[targetSlot] = value - } + // Check if tag exists + const existingDef = existingByName.get(tagName) + if (!existingDef) { + undefinedTags.push(tagName) + continue } - return result - } catch (error) { - logger.error(`[${requestId}] Error processing document tags:`, error) - return result + // Validate value type using shared validation + const rawValue = typeof tag.value === 'string' ? tag.value.trim() : tag.value + const actualFieldType = existingDef.fieldType || fieldType + const validationError = validateTagValue(tagName, String(rawValue), actualFieldType) + if (validationError) { + typeErrors.push(validationError) + } } + + // Throw combined error if there are any validation issues + if (undefinedTags.length > 0 || typeErrors.length > 0) { + const errorParts: string[] = [] + + if (undefinedTags.length > 0) { + errorParts.push(buildUndefinedTagsError(undefinedTags)) + } + + if (typeErrors.length > 0) { + errorParts.push(...typeErrors) + } + + throw new Error(errorParts.join('\n')) + } + + // Second pass: process valid tags + for (const tag of tagData) { + if (!tag.tagName?.trim()) continue + + const tagName = tag.tagName.trim() + const fieldType = tag.fieldType || 'text' + + const hasValue = + fieldType === 'boolean' + ? tag.value !== undefined && tag.value !== null && tag.value !== '' + : tag.value?.trim && tag.value.trim().length > 0 + + if (!hasValue) continue + + const existingDef = existingByName.get(tagName) + if (!existingDef) continue // Already validated above + + const targetSlot = existingDef.tagSlot + const actualFieldType = existingDef.fieldType || fieldType + const rawValue = typeof tag.value === 'string' ? tag.value.trim() : tag.value + const stringValue = String(rawValue).trim() + + // Assign value to the slot with proper type conversion (values already validated) + if (actualFieldType === 'boolean') { + setTagValue(result, targetSlot, parseBooleanValue(stringValue) ?? false) + } else if (actualFieldType === 'number') { + setTagValue(result, targetSlot, parseNumberValue(stringValue)) + } else if (actualFieldType === 'date') { + setTagValue(result, targetSlot, parseDateValue(stringValue)) + } else { + setTagValue(result, targetSlot, stringValue) + } + + logger.info(`[${requestId}] Set tag ${tagName} (${targetSlot}) = ${stringValue}`) + } + + return result } /** @@ -375,6 +495,7 @@ export async function processDocumentAsync( const documentRecord = await db .select({ + // Text tags (7 slots) tag1: document.tag1, tag2: document.tag2, tag3: document.tag3, @@ -382,6 +503,19 @@ export async function processDocumentAsync( tag5: document.tag5, tag6: document.tag6, tag7: document.tag7, + // Number tags (5 slots) + number1: document.number1, + number2: document.number2, + number3: document.number3, + number4: document.number4, + number5: document.number5, + // Date tags (2 slots) + date1: document.date1, + date2: document.date2, + // Boolean tags (3 slots) + boolean1: document.boolean1, + boolean2: document.boolean2, + boolean3: document.boolean3, }) .from(document) .where(eq(document.id, documentId)) @@ -404,7 +538,7 @@ export async function processDocumentAsync( embeddingModel: 'text-embedding-3-small', startOffset: chunk.metadata.startIndex, endOffset: chunk.metadata.endIndex, - // Copy tags from document + // Copy text tags from document (7 slots) tag1: documentTags.tag1, tag2: documentTags.tag2, tag3: documentTags.tag3, @@ -412,6 +546,19 @@ export async function processDocumentAsync( tag5: documentTags.tag5, tag6: documentTags.tag6, tag7: documentTags.tag7, + // Copy number tags from document (5 slots) + number1: documentTags.number1, + number2: documentTags.number2, + number3: documentTags.number3, + number4: documentTags.number4, + number5: documentTags.number5, + // Copy date tags from document (2 slots) + date1: documentTags.date1, + date2: documentTags.date2, + // Copy boolean tags from document (3 slots) + boolean1: documentTags.boolean1, + boolean2: documentTags.boolean2, + boolean3: documentTags.boolean3, createdAt: now, updatedAt: now, })) @@ -568,15 +715,7 @@ export async function createDocumentRecords( for (const docData of documents) { const documentId = randomUUID() - let processedTags: Record = { - tag1: null, - tag2: null, - tag3: null, - tag4: null, - tag5: null, - tag6: null, - tag7: null, - } + let processedTags: Record = {} if (docData.documentTagsData) { try { @@ -585,7 +724,12 @@ export async function createDocumentRecords( processedTags = await processDocumentTags(knowledgeBaseId, tagData, requestId) } } catch (error) { - logger.warn(`[${requestId}] Failed to parse documentTagsData for bulk document:`, error) + // Re-throw validation errors, only catch JSON parse errors + if (error instanceof SyntaxError) { + logger.warn(`[${requestId}] Failed to parse documentTagsData for bulk document:`, error) + } else { + throw error + } } } @@ -602,14 +746,27 @@ export async function createDocumentRecords( processingStatus: 'pending' as const, enabled: true, uploadedAt: now, - // 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, + // Text tags - 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, + // Number tags (5 slots) + number1: processedTags.number1 ?? null, + number2: processedTags.number2 ?? null, + number3: processedTags.number3 ?? null, + number4: processedTags.number4 ?? null, + number5: processedTags.number5 ?? null, + // Date tags (2 slots) + date1: processedTags.date1 ?? null, + date2: processedTags.date2 ?? null, + // Boolean tags (3 slots) + boolean1: processedTags.boolean1 ?? null, + boolean2: processedTags.boolean2 ?? null, + boolean3: processedTags.boolean3 ?? null, } documentRecords.push(newDocument) @@ -679,6 +836,7 @@ export async function getDocuments( processingError: string | null enabled: boolean uploadedAt: Date + // Text tags tag1: string | null tag2: string | null tag3: string | null @@ -686,6 +844,19 @@ export async function getDocuments( tag5: string | null tag6: string | null tag7: string | null + // Number tags + number1: number | null + number2: number | null + number3: number | null + number4: number | null + number5: number | null + // Date tags + date1: Date | null + date2: Date | null + // Boolean tags + boolean1: boolean | null + boolean2: boolean | null + boolean3: boolean | null }> pagination: { total: number @@ -772,7 +943,7 @@ export async function getDocuments( processingError: document.processingError, enabled: document.enabled, uploadedAt: document.uploadedAt, - // Include tags in response + // Text tags (7 slots) tag1: document.tag1, tag2: document.tag2, tag3: document.tag3, @@ -780,6 +951,19 @@ export async function getDocuments( tag5: document.tag5, tag6: document.tag6, tag7: document.tag7, + // Number tags (5 slots) + number1: document.number1, + number2: document.number2, + number3: document.number3, + number4: document.number4, + number5: document.number5, + // Date tags (2 slots) + date1: document.date1, + date2: document.date2, + // Boolean tags (3 slots) + boolean1: document.boolean1, + boolean2: document.boolean2, + boolean3: document.boolean3, }) .from(document) .where(and(...whereConditions)) @@ -807,6 +991,7 @@ export async function getDocuments( processingError: doc.processingError, enabled: doc.enabled, uploadedAt: doc.uploadedAt, + // Text tags tag1: doc.tag1, tag2: doc.tag2, tag3: doc.tag3, @@ -814,6 +999,19 @@ export async function getDocuments( tag5: doc.tag5, tag6: doc.tag6, tag7: doc.tag7, + // Number tags + number1: doc.number1, + number2: doc.number2, + number3: doc.number3, + number4: doc.number4, + number5: doc.number5, + // Date tags + date1: doc.date1, + date2: doc.date2, + // Boolean tags + boolean1: doc.boolean1, + boolean2: doc.boolean2, + boolean3: doc.boolean3, })), pagination: { total, @@ -883,14 +1081,28 @@ export async function createSingleDocument( const now = new Date() // Process structured tag data if provided - let processedTags: Record = { - tag1: documentData.tag1 || null, - tag2: documentData.tag2 || null, - tag3: documentData.tag3 || null, - tag4: documentData.tag4 || null, - tag5: documentData.tag5 || null, - tag6: documentData.tag6 || null, - tag7: documentData.tag7 || null, + let processedTags: Record = { + // Text tags (7 slots) + tag1: documentData.tag1 ?? null, + tag2: documentData.tag2 ?? null, + tag3: documentData.tag3 ?? null, + tag4: documentData.tag4 ?? null, + tag5: documentData.tag5 ?? null, + tag6: documentData.tag6 ?? null, + tag7: documentData.tag7 ?? null, + // Number tags (5 slots) + number1: null, + number2: null, + number3: null, + number4: null, + number5: null, + // Date tags (2 slots) + date1: null, + date2: null, + // Boolean tags (3 slots) + boolean1: null, + boolean2: null, + boolean3: null, } if (documentData.documentTagsData) { @@ -901,7 +1113,12 @@ export async function createSingleDocument( processedTags = await processDocumentTags(knowledgeBaseId, tagData, requestId) } } catch (error) { - logger.warn(`[${requestId}] Failed to parse documentTagsData:`, error) + // Re-throw validation errors, only catch JSON parse errors + if (error instanceof SyntaxError) { + logger.warn(`[${requestId}] Failed to parse documentTagsData:`, error) + } else { + throw error + } } } @@ -1183,6 +1400,7 @@ export async function updateDocument( characterCount?: number processingStatus?: 'pending' | 'processing' | 'completed' | 'failed' processingError?: string + // Text tags tag1?: string tag2?: string tag3?: string @@ -1190,6 +1408,19 @@ export async function updateDocument( tag5?: string tag6?: string tag7?: string + // Number tags + number1?: string + number2?: string + number3?: string + number4?: string + number5?: string + // Date tags + date1?: string + date2?: string + // Boolean tags + boolean1?: string + boolean2?: string + boolean3?: string }, requestId: string ): Promise<{ @@ -1215,6 +1446,16 @@ export async function updateDocument( tag5: string | null tag6: string | null tag7: string | null + number1: number | null + number2: number | null + number3: number | null + number4: number | null + number5: number | null + date1: Date | null + date2: Date | null + boolean1: boolean | null + boolean2: boolean | null + boolean3: boolean | null deletedAt: Date | null }> { const dbUpdateData: Partial<{ @@ -1234,9 +1475,38 @@ export async function updateDocument( tag5: string | null tag6: string | null tag7: string | null + number1: number | null + number2: number | null + number3: number | null + number4: number | null + number5: number | null + date1: Date | null + date2: Date | null + boolean1: boolean | null + boolean2: boolean | null + boolean3: boolean | null }> = {} - const TAG_SLOTS = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7'] as const - type TagSlot = (typeof TAG_SLOTS)[number] + // All tag slots across all field types + const ALL_TAG_SLOTS = [ + 'tag1', + 'tag2', + 'tag3', + 'tag4', + 'tag5', + 'tag6', + 'tag7', + 'number1', + 'number2', + 'number3', + 'number4', + 'number5', + 'date1', + 'date2', + 'boolean1', + 'boolean2', + 'boolean3', + ] as const + type TagSlot = (typeof ALL_TAG_SLOTS)[number] // Regular field updates if (updateData.filename !== undefined) dbUpdateData.filename = updateData.filename @@ -1250,23 +1520,49 @@ export async function updateDocument( if (updateData.processingError !== undefined) dbUpdateData.processingError = updateData.processingError - TAG_SLOTS.forEach((slot: TagSlot) => { + // Helper to convert string values to proper types for the database + const convertTagValue = ( + slot: string, + value: string | undefined + ): string | number | Date | boolean | null => { + if (value === undefined || value === '') return null + + // Number slots + if (slot.startsWith('number')) { + return parseNumberValue(value) + } + + // Date slots + if (slot.startsWith('date')) { + return parseDateValue(value) + } + + // Boolean slots + if (slot.startsWith('boolean')) { + return parseBooleanValue(value) ?? false + } + + // Text slots: keep as string + return value || null + } + + ALL_TAG_SLOTS.forEach((slot: TagSlot) => { const updateValue = (updateData as any)[slot] if (updateValue !== undefined) { - ;(dbUpdateData as any)[slot] = updateValue + ;(dbUpdateData as any)[slot] = convertTagValue(slot, updateValue) } }) await db.transaction(async (tx) => { await tx.update(document).set(dbUpdateData).where(eq(document.id, documentId)) - const hasTagUpdates = TAG_SLOTS.some((field) => (updateData as any)[field] !== undefined) + const hasTagUpdates = ALL_TAG_SLOTS.some((field) => (updateData as any)[field] !== undefined) if (hasTagUpdates) { - const embeddingUpdateData: Record = {} - TAG_SLOTS.forEach((field) => { + const embeddingUpdateData: Record = {} + ALL_TAG_SLOTS.forEach((field) => { if ((updateData as any)[field] !== undefined) { - embeddingUpdateData[field] = (updateData as any)[field] || null + embeddingUpdateData[field] = convertTagValue(field, (updateData as any)[field]) } }) @@ -1313,6 +1609,16 @@ export async function updateDocument( tag5: doc.tag5, tag6: doc.tag6, tag7: doc.tag7, + number1: doc.number1, + number2: doc.number2, + number3: doc.number3, + number4: doc.number4, + number5: doc.number5, + date1: doc.date1, + date2: doc.date2, + boolean1: doc.boolean1, + boolean2: doc.boolean2, + boolean3: doc.boolean3, deletedAt: doc.deletedAt, } } diff --git a/apps/sim/lib/knowledge/filters/index.ts b/apps/sim/lib/knowledge/filters/index.ts new file mode 100644 index 000000000..26bee6598 --- /dev/null +++ b/apps/sim/lib/knowledge/filters/index.ts @@ -0,0 +1,2 @@ +export * from './query-builder' +export * from './types' diff --git a/apps/sim/lib/knowledge/filters/query-builder.ts b/apps/sim/lib/knowledge/filters/query-builder.ts new file mode 100644 index 000000000..83bad1175 --- /dev/null +++ b/apps/sim/lib/knowledge/filters/query-builder.ts @@ -0,0 +1,393 @@ +import { document, embedding } from '@sim/db/schema' +import { and, eq, gt, gte, ilike, lt, lte, ne, not, or, type SQL } from 'drizzle-orm' +import type { + BooleanFilterCondition, + DateFilterCondition, + FilterCondition, + FilterGroup, + NumberFilterCondition, + SimpleTagFilter, + TagFilter, + TextFilterCondition, +} from './types' + +/** + * Valid tag slots that can be used in filters + */ +const VALID_TEXT_SLOTS = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7'] as const +const VALID_NUMBER_SLOTS = ['number1', 'number2', 'number3', 'number4', 'number5'] as const +const VALID_DATE_SLOTS = ['date1', 'date2'] as const +const VALID_BOOLEAN_SLOTS = ['boolean1', 'boolean2', 'boolean3'] as const + +type TextSlot = (typeof VALID_TEXT_SLOTS)[number] +type NumberSlot = (typeof VALID_NUMBER_SLOTS)[number] +type DateSlot = (typeof VALID_DATE_SLOTS)[number] +type BooleanSlot = (typeof VALID_BOOLEAN_SLOTS)[number] + +/** + * Validates that a tag slot is valid for the given field type + */ +function isValidSlotForType( + slot: string, + fieldType: string +): slot is TextSlot | NumberSlot | DateSlot | BooleanSlot { + switch (fieldType) { + case 'text': + return (VALID_TEXT_SLOTS as readonly string[]).includes(slot) + case 'number': + return (VALID_NUMBER_SLOTS as readonly string[]).includes(slot) + case 'date': + return (VALID_DATE_SLOTS as readonly string[]).includes(slot) + case 'boolean': + return (VALID_BOOLEAN_SLOTS as readonly string[]).includes(slot) + default: + return false + } +} + +/** + * Build SQL condition for a text filter + */ +function buildTextCondition( + condition: TextFilterCondition, + table: typeof document | typeof embedding +): SQL | null { + const { tagSlot, operator, value } = condition + + if (!isValidSlotForType(tagSlot, 'text')) { + return null + } + + const column = table[tagSlot as TextSlot] + if (!column) return null + + switch (operator) { + case 'eq': + return eq(column, value) + case 'neq': + return ne(column, value) + case 'contains': + return ilike(column, `%${value}%`) + case 'not_contains': + return not(ilike(column, `%${value}%`)) + case 'starts_with': + return ilike(column, `${value}%`) + case 'ends_with': + return ilike(column, `%${value}`) + default: + return null + } +} + +/** + * Build SQL condition for a number filter + */ +function buildNumberCondition( + condition: NumberFilterCondition, + table: typeof document | typeof embedding +): SQL | null { + const { tagSlot, operator, value, valueTo } = condition + + if (!isValidSlotForType(tagSlot, 'number')) { + return null + } + + const column = table[tagSlot as NumberSlot] + if (!column) return null + + switch (operator) { + case 'eq': + return eq(column, value) + case 'neq': + return ne(column, value) + case 'gt': + return gt(column, value) + case 'gte': + return gte(column, value) + case 'lt': + return lt(column, value) + case 'lte': + return lte(column, value) + case 'between': + if (valueTo !== undefined) { + return and(gte(column, value), lte(column, valueTo)) ?? null + } + return null + default: + return null + } +} + +/** + * Parse a YYYY-MM-DD date string into a UTC Date object. + */ +function parseDateValue(value: string): Date | null { + if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null + const [year, month, day] = value.split('-').map(Number) + return new Date(Date.UTC(year, month - 1, day)) +} + +/** + * Build SQL condition for a date filter. + * Expects date values in YYYY-MM-DD format. + */ +function buildDateCondition( + condition: DateFilterCondition, + table: typeof document | typeof embedding +): SQL | null { + const { tagSlot, operator, value, valueTo } = condition + + if (!isValidSlotForType(tagSlot, 'date')) { + return null + } + + const column = table[tagSlot as DateSlot] + if (!column) return null + + const dateValue = parseDateValue(value) + if (!dateValue) return null + + switch (operator) { + case 'eq': + return eq(column, dateValue) + case 'neq': + return ne(column, dateValue) + case 'gt': + return gt(column, dateValue) + case 'gte': + return gte(column, dateValue) + case 'lt': + return lt(column, dateValue) + case 'lte': + return lte(column, dateValue) + case 'between': + if (valueTo !== undefined) { + const dateValueTo = parseDateValue(valueTo) + if (!dateValueTo) return null + return and(gte(column, dateValue), lte(column, dateValueTo)) ?? null + } + return null + default: + return null + } +} + +/** + * Build SQL condition for a boolean filter + */ +function buildBooleanCondition( + condition: BooleanFilterCondition, + table: typeof document | typeof embedding +): SQL | null { + const { tagSlot, operator, value } = condition + + if (!isValidSlotForType(tagSlot, 'boolean')) { + return null + } + + const column = table[tagSlot as BooleanSlot] + if (!column) return null + + switch (operator) { + case 'eq': + return eq(column, value) + case 'neq': + return ne(column, value) + default: + return null + } +} + +/** + * Build SQL condition for a single filter condition + */ +function buildCondition( + condition: FilterCondition, + table: typeof document | typeof embedding +): SQL | null { + switch (condition.fieldType) { + case 'text': + return buildTextCondition(condition, table) + case 'number': + return buildNumberCondition(condition, table) + case 'date': + return buildDateCondition(condition, table) + case 'boolean': + return buildBooleanCondition(condition, table) + default: + return null + } +} + +/** + * Build SQL condition for a filter group + */ +function buildGroupCondition( + group: FilterGroup, + table: typeof document | typeof embedding +): SQL | null { + const conditions = group.conditions + .map((condition) => buildCondition(condition, table)) + .filter((c): c is SQL => c !== null) + + if (conditions.length === 0) { + return null + } + + if (conditions.length === 1) { + return conditions[0] + } + + return (group.operator === 'AND' ? and(...conditions) : or(...conditions)) ?? null +} + +/** + * Build SQL WHERE clause from a TagFilter + * Supports nested groups with AND/OR logic + */ +export function buildTagFilterQuery( + filter: TagFilter, + table: typeof document | typeof embedding +): SQL | null { + const groupConditions = filter.groups + .map((group) => buildGroupCondition(group, table)) + .filter((c): c is SQL => c !== null) + + if (groupConditions.length === 0) { + return null + } + + if (groupConditions.length === 1) { + return groupConditions[0] + } + + return (filter.rootOperator === 'AND' ? and(...groupConditions) : or(...groupConditions)) ?? null +} + +/** + * Build SQL WHERE clause from a SimpleTagFilter + * For flat filter structures without nested groups + */ +export function buildSimpleTagFilterQuery( + filter: SimpleTagFilter, + table: typeof document | typeof embedding +): SQL | null { + const conditions = filter.conditions + .map((condition) => buildCondition(condition, table)) + .filter((c): c is SQL => c !== null) + + if (conditions.length === 0) { + return null + } + + if (conditions.length === 1) { + return conditions[0] + } + + return (filter.operator === 'AND' ? and(...conditions) : or(...conditions)) ?? null +} + +/** + * Build SQL WHERE clause from an array of filter conditions + * Combines all conditions with AND by default + */ +export function buildFilterConditionsQuery( + conditions: FilterCondition[], + table: typeof document | typeof embedding, + operator: 'AND' | 'OR' = 'AND' +): SQL | null { + return buildSimpleTagFilterQuery({ operator, conditions }, table) +} + +/** + * Convenience function to build filter for document table + */ +export function buildDocumentFilterQuery(filter: TagFilter | SimpleTagFilter): SQL | null { + if ('rootOperator' in filter) { + return buildTagFilterQuery(filter, document) + } + return buildSimpleTagFilterQuery(filter, document) +} + +/** + * Convenience function to build filter for embedding table + */ +export function buildEmbeddingFilterQuery(filter: TagFilter | SimpleTagFilter): SQL | null { + if ('rootOperator' in filter) { + return buildTagFilterQuery(filter, embedding) + } + return buildSimpleTagFilterQuery(filter, embedding) +} + +/** + * Validate a filter condition + * Returns an array of validation errors, empty if valid + */ +export function validateFilterCondition(condition: FilterCondition): string[] { + const errors: string[] = [] + + if (!isValidSlotForType(condition.tagSlot, condition.fieldType)) { + errors.push(`Invalid tag slot "${condition.tagSlot}" for field type "${condition.fieldType}"`) + } + + switch (condition.fieldType) { + case 'text': + if (typeof condition.value !== 'string') { + errors.push('Text filter value must be a string') + } + break + case 'number': + if (typeof condition.value !== 'number' || Number.isNaN(condition.value)) { + errors.push('Number filter value must be a valid number') + } + if (condition.operator === 'between' && condition.valueTo === undefined) { + errors.push('Between operator requires a second value') + } + if (condition.valueTo !== undefined && typeof condition.valueTo !== 'number') { + errors.push('Number filter second value must be a valid number') + } + break + case 'date': + if (typeof condition.value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(condition.value)) { + errors.push('Date filter value must be in YYYY-MM-DD format') + } + if (condition.operator === 'between' && condition.valueTo === undefined) { + errors.push('Between operator requires a second value') + } + if ( + condition.valueTo !== undefined && + (typeof condition.valueTo !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(condition.valueTo)) + ) { + errors.push('Date filter second value must be in YYYY-MM-DD format') + } + break + case 'boolean': + if (typeof condition.value !== 'boolean') { + errors.push('Boolean filter value must be true or false') + } + break + } + + return errors +} + +/** + * Validate all conditions in a filter + */ +export function validateFilter(filter: TagFilter | SimpleTagFilter): string[] { + const errors: string[] = [] + + if ('rootOperator' in filter) { + for (const group of filter.groups) { + for (const condition of group.conditions) { + errors.push(...validateFilterCondition(condition)) + } + } + } else { + for (const condition of filter.conditions) { + errors.push(...validateFilterCondition(condition)) + } + } + + return errors +} diff --git a/apps/sim/lib/knowledge/filters/types.ts b/apps/sim/lib/knowledge/filters/types.ts new file mode 100644 index 000000000..af8f5406c --- /dev/null +++ b/apps/sim/lib/knowledge/filters/types.ts @@ -0,0 +1,191 @@ +/** + * Filter operators for different field types + */ + +/** + * Text filter operators + */ +export type TextOperator = 'eq' | 'neq' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' + +/** + * Number filter operators + */ +export type NumberOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'between' + +/** + * Date filter operators + */ +export type DateOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'between' + +/** + * Boolean filter operators + */ +export type BooleanOperator = 'eq' | 'neq' + +/** + * All filter operators union + */ +export type FilterOperator = TextOperator | NumberOperator | DateOperator | BooleanOperator + +/** + * Field types supported for filtering + */ +export type FilterFieldType = 'text' | 'number' | 'date' | 'boolean' + +/** + * Logical operators for combining filters + */ +export type LogicalOperator = 'AND' | 'OR' + +/** + * Base filter condition interface + */ +interface BaseFilterCondition { + tagSlot: string + fieldType: FilterFieldType +} + +/** + * Text filter condition + */ +export interface TextFilterCondition extends BaseFilterCondition { + fieldType: 'text' + operator: TextOperator + value: string +} + +/** + * Number filter condition + */ +export interface NumberFilterCondition extends BaseFilterCondition { + fieldType: 'number' + operator: NumberOperator + value: number + valueTo?: number // For 'between' operator +} + +/** + * Date filter condition + */ +export interface DateFilterCondition extends BaseFilterCondition { + fieldType: 'date' + operator: DateOperator + value: string // ISO date string + valueTo?: string // For 'between' operator (ISO date string) +} + +/** + * Boolean filter condition + */ +export interface BooleanFilterCondition extends BaseFilterCondition { + fieldType: 'boolean' + operator: BooleanOperator + value: boolean +} + +/** + * Union of all filter conditions + */ +export type FilterCondition = + | TextFilterCondition + | NumberFilterCondition + | DateFilterCondition + | BooleanFilterCondition + +/** + * Filter group with logical operator + */ +export interface FilterGroup { + operator: LogicalOperator + conditions: FilterCondition[] +} + +/** + * Complete filter query structure + * Supports nested groups with AND/OR logic + */ +export interface TagFilter { + rootOperator: LogicalOperator + groups: FilterGroup[] +} + +/** + * Simplified flat filter structure for simple use cases + */ +export interface SimpleTagFilter { + operator: LogicalOperator + conditions: FilterCondition[] +} + +/** + * Operator metadata for UI display + */ +export interface OperatorInfo { + value: string + label: string + requiresSecondValue?: boolean +} + +/** + * Text operators metadata + */ +export const TEXT_OPERATORS: OperatorInfo[] = [ + { value: 'eq', label: 'equals' }, + { value: 'neq', label: 'not equals' }, + { value: 'contains', label: 'contains' }, + { value: 'not_contains', label: 'does not contain' }, + { value: 'starts_with', label: 'starts with' }, + { value: 'ends_with', label: 'ends with' }, +] + +/** + * Number operators metadata + */ +export const NUMBER_OPERATORS: OperatorInfo[] = [ + { value: 'eq', label: 'equals' }, + { value: 'neq', label: 'not equals' }, + { value: 'gt', label: 'greater than' }, + { value: 'gte', label: 'greater than or equal' }, + { value: 'lt', label: 'less than' }, + { value: 'lte', label: 'less than or equal' }, + { value: 'between', label: 'between', requiresSecondValue: true }, +] + +/** + * Date operators metadata + */ +export const DATE_OPERATORS: OperatorInfo[] = [ + { value: 'eq', label: 'equals' }, + { value: 'neq', label: 'not equals' }, + { value: 'gt', label: 'after' }, + { value: 'gte', label: 'on or after' }, + { value: 'lt', label: 'before' }, + { value: 'lte', label: 'on or before' }, + { value: 'between', label: 'between', requiresSecondValue: true }, +] + +/** + * Boolean operators metadata + */ +export const BOOLEAN_OPERATORS: OperatorInfo[] = [ + { value: 'eq', label: 'is' }, + { value: 'neq', label: 'is not' }, +] + +/** + * Get operators for a field type + */ +export function getOperatorsForFieldType(fieldType: FilterFieldType): OperatorInfo[] { + switch (fieldType) { + case 'text': + return TEXT_OPERATORS + case 'number': + return NUMBER_OPERATORS + case 'date': + return DATE_OPERATORS + case 'boolean': + return BOOLEAN_OPERATORS + default: + return [] + } +} diff --git a/apps/sim/lib/knowledge/tags/service.ts b/apps/sim/lib/knowledge/tags/service.ts index 54061883c..66d663f58 100644 --- a/apps/sim/lib/knowledge/tags/service.ts +++ b/apps/sim/lib/knowledge/tags/service.ts @@ -2,11 +2,7 @@ import { randomUUID } from 'crypto' import { db } from '@sim/db' import { document, embedding, knowledgeBaseTagDefinitions } from '@sim/db/schema' import { and, eq, isNotNull, isNull, sql } from 'drizzle-orm' -import { - getSlotsForFieldType, - SUPPORTED_FIELD_TYPES, - type TAG_SLOT_CONFIG, -} from '@/lib/knowledge/constants' +import { getSlotsForFieldType, SUPPORTED_FIELD_TYPES } from '@/lib/knowledge/constants' import type { BulkTagDefinitionsData, DocumentTagDefinition } from '@/lib/knowledge/tags/types' import type { CreateTagDefinitionData, @@ -17,14 +13,45 @@ import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('TagsService') -const VALID_TAG_SLOTS = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7'] as const +/** Text tag slots */ +const VALID_TEXT_SLOTS = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7'] as const -function validateTagSlot(tagSlot: string): asserts tagSlot is (typeof VALID_TAG_SLOTS)[number] { - if (!VALID_TAG_SLOTS.includes(tagSlot as (typeof VALID_TAG_SLOTS)[number])) { +const VALID_NUMBER_SLOTS = ['number1', 'number2', 'number3', 'number4', 'number5'] as const +/** Date tag slots (reduced to 2 for write performance) */ +const VALID_DATE_SLOTS = ['date1', 'date2'] as const +/** Boolean tag slots */ +const VALID_BOOLEAN_SLOTS = ['boolean1', 'boolean2', 'boolean3'] as const + +/** All valid tag slots combined */ +const VALID_TAG_SLOTS = [ + ...VALID_TEXT_SLOTS, + ...VALID_NUMBER_SLOTS, + ...VALID_DATE_SLOTS, + ...VALID_BOOLEAN_SLOTS, +] as const + +type ValidTagSlot = (typeof VALID_TAG_SLOTS)[number] + +/** + * Validates that a tag slot is a valid slot name + */ +function validateTagSlot(tagSlot: string): asserts tagSlot is ValidTagSlot { + if (!VALID_TAG_SLOTS.includes(tagSlot as ValidTagSlot)) { throw new Error(`Invalid tag slot: ${tagSlot}. Must be one of: ${VALID_TAG_SLOTS.join(', ')}`) } } +/** + * Get the field type for a tag slot + */ +function getFieldTypeForSlot(tagSlot: string): string | null { + if ((VALID_TEXT_SLOTS as readonly string[]).includes(tagSlot)) return 'text' + if ((VALID_NUMBER_SLOTS as readonly string[]).includes(tagSlot)) return 'number' + if ((VALID_DATE_SLOTS as readonly string[]).includes(tagSlot)) return 'date' + if ((VALID_BOOLEAN_SLOTS as readonly string[]).includes(tagSlot)) return 'boolean' + return null +} + /** * Get the next available slot for a knowledge base and field type */ @@ -215,7 +242,7 @@ export async function createOrUpdateTagDefinitionsBulk( const newDefinition = { id, knowledgeBaseId, - tagSlot: finalTagSlot as (typeof TAG_SLOT_CONFIG.text.slots)[number], + tagSlot: finalTagSlot as ValidTagSlot, displayName, fieldType, createdAt: now, @@ -466,7 +493,7 @@ export async function createTagDefinition( const newDefinition = { id: tagDefinitionId, knowledgeBaseId: data.knowledgeBaseId, - tagSlot: data.tagSlot as (typeof TAG_SLOT_CONFIG.text.slots)[number], + tagSlot: data.tagSlot as ValidTagSlot, displayName: data.displayName, fieldType: data.fieldType, createdAt: now, @@ -562,21 +589,31 @@ export async function getTagUsage( const tagSlot = def.tagSlot validateTagSlot(tagSlot) + // Build WHERE conditions based on field type + // Text columns need both IS NOT NULL and != '' checks + // Numeric/date/boolean columns only need IS NOT NULL + const fieldType = getFieldTypeForSlot(tagSlot) + const isTextColumn = fieldType === 'text' + + const whereConditions = [ + eq(document.knowledgeBaseId, knowledgeBaseId), + isNull(document.deletedAt), + isNotNull(sql`${sql.raw(tagSlot)}`), + ] + + // Only add empty string check for text columns + if (isTextColumn) { + whereConditions.push(sql`${sql.raw(tagSlot)} != ''`) + } + const documentsWithTag = await db .select({ id: document.id, filename: document.filename, - tagValue: sql`${sql.raw(tagSlot)}`, + tagValue: sql`${sql.raw(tagSlot)}::text`, }) .from(document) - .where( - and( - eq(document.knowledgeBaseId, knowledgeBaseId), - isNull(document.deletedAt), - isNotNull(sql`${sql.raw(tagSlot)}`), - sql`${sql.raw(tagSlot)} != ''` - ) - ) + .where(and(...whereConditions)) usage.push({ tagName: def.displayName, diff --git a/apps/sim/lib/knowledge/tags/utils.ts b/apps/sim/lib/knowledge/tags/utils.ts new file mode 100644 index 000000000..713a04cd4 --- /dev/null +++ b/apps/sim/lib/knowledge/tags/utils.ts @@ -0,0 +1,89 @@ +/** + * Validate a tag value against its expected field type + * Returns an error message if invalid, or null if valid + */ +export function validateTagValue(tagName: string, value: string, fieldType: string): string | null { + const stringValue = String(value).trim() + + switch (fieldType) { + case 'boolean': { + const lowerValue = stringValue.toLowerCase() + if (lowerValue !== 'true' && lowerValue !== 'false') { + return `Tag "${tagName}" expects a boolean value (true/false), but received "${value}"` + } + return null + } + case 'number': { + const numValue = Number(stringValue) + if (Number.isNaN(numValue)) { + return `Tag "${tagName}" expects a number value, but received "${value}"` + } + return null + } + case 'date': { + // Check format first + if (!/^\d{4}-\d{2}-\d{2}$/.test(stringValue)) { + return `Tag "${tagName}" expects a date in YYYY-MM-DD format, but received "${value}"` + } + // Validate the date is actually valid (e.g., reject 2024-02-31) + const [year, month, day] = stringValue.split('-').map(Number) + const date = new Date(year, month - 1, day) + if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) { + return `Tag "${tagName}" has an invalid date: "${value}"` + } + return null + } + default: + return null + } +} + +/** + * Build error message for undefined tags + */ +export function buildUndefinedTagsError(undefinedTags: string[]): string { + const tagList = undefinedTags.map((t) => `"${t}"`).join(', ') + return `The following tags are not defined in this knowledge base: ${tagList}. Please define them at the knowledge base level first.` +} + +/** + * Parse a string to number with strict validation + * Returns null if invalid + */ +export function parseNumberValue(value: string): number | null { + const num = Number(value) + return Number.isNaN(num) ? null : num +} + +/** + * Parse a string to Date with strict YYYY-MM-DD validation + * Returns null if invalid format or invalid date + */ +export function parseDateValue(value: string): Date | null { + const stringValue = String(value).trim() + + // Must be YYYY-MM-DD format + if (!/^\d{4}-\d{2}-\d{2}$/.test(stringValue)) { + return null + } + + // Validate the date is actually valid (e.g., reject 2024-02-31) + const [year, month, day] = stringValue.split('-').map(Number) + const date = new Date(year, month - 1, day) + if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) { + return null + } + + return date +} + +/** + * Parse a string to boolean with strict validation + * Returns null if not 'true' or 'false' + */ +export function parseBooleanValue(value: string): boolean | null { + const lowerValue = String(value).trim().toLowerCase() + if (lowerValue === 'true') return true + if (lowerValue === 'false') return false + return null +} diff --git a/apps/sim/lib/knowledge/types.ts b/apps/sim/lib/knowledge/types.ts index 7ec36fc01..932845a61 100644 --- a/apps/sim/lib/knowledge/types.ts +++ b/apps/sim/lib/knowledge/types.ts @@ -48,3 +48,40 @@ export interface UpdateTagDefinitionData { displayName?: string fieldType?: string } + +/** Tag filter for knowledge base search */ +export interface StructuredFilter { + tagName?: string // Human-readable name (input from frontend) + tagSlot: string // Database column (resolved from tagName) + fieldType: string + operator: string + value: string | number | boolean + valueTo?: string | number +} + +/** Processed document tags ready for database storage */ +export interface ProcessedDocumentTags { + // Text tags + tag1: string | null + tag2: string | null + tag3: string | null + tag4: string | null + tag5: string | null + tag6: string | null + tag7: string | null + // Number tags + number1: number | null + number2: number | null + number3: number | null + number4: number | null + number5: number | null + // Date tags + date1: Date | null + date2: Date | null + // Boolean tags + boolean1: boolean | null + boolean2: boolean | null + boolean3: boolean | null + // Index signature for dynamic access + [key: string]: string | number | Date | boolean | null +} diff --git a/apps/sim/stores/knowledge/store.ts b/apps/sim/stores/knowledge/store.ts index 3bc0492e4..528b33142 100644 --- a/apps/sim/stores/knowledge/store.ts +++ b/apps/sim/stores/knowledge/store.ts @@ -44,7 +44,7 @@ export interface DocumentData { processingError?: string | null enabled: boolean uploadedAt: string - // Document tags + // Text tags tag1?: string | null tag2?: string | null tag3?: string | null @@ -52,6 +52,19 @@ export interface DocumentData { tag5?: string | null tag6?: string | null tag7?: string | null + // Number tags (5 slots) + number1?: number | null + number2?: number | null + number3?: number | null + number4?: number | null + number5?: number | null + // Date tags (2 slots) + date1?: string | null + date2?: string | null + // Boolean tags (3 slots) + boolean1?: boolean | null + boolean2?: boolean | null + boolean3?: boolean | null } export interface ChunkData { @@ -63,6 +76,7 @@ export interface ChunkData { enabled: boolean startOffset: number endOffset: number + // Text tags tag1?: string | null tag2?: string | null tag3?: string | null @@ -70,6 +84,19 @@ export interface ChunkData { tag5?: string | null tag6?: string | null tag7?: string | null + // Number tags (5 slots) + number1?: number | null + number2?: number | null + number3?: number | null + number4?: number | null + number5?: number | null + // Date tags (2 slots) + date1?: string | null + date2?: string | null + // Boolean tags (3 slots) + boolean1?: boolean | null + boolean2?: boolean | null + boolean3?: boolean | null createdAt: string updatedAt: string } diff --git a/apps/sim/tools/knowledge/search.ts b/apps/sim/tools/knowledge/search.ts index a4bd4d892..aee78f764 100644 --- a/apps/sim/tools/knowledge/search.ts +++ b/apps/sim/tools/knowledge/search.ts @@ -1,3 +1,4 @@ +import type { StructuredFilter } from '@/lib/knowledge/types' import type { KnowledgeSearchResponse } from '@/tools/knowledge/types' import type { ToolConfig } from '@/tools/types' @@ -53,8 +54,8 @@ export const knowledgeSearchTool: ToolConfig = { // Use single knowledge base ID const knowledgeBaseIds = [params.knowledgeBaseId] - // Parse dynamic tag filters and send display names to API - const filters: Record = {} + // Parse dynamic tag filters + let structuredFilters: StructuredFilter[] = [] if (params.tagFilters) { let tagFilters = params.tagFilters @@ -62,27 +63,29 @@ export const knowledgeSearchTool: ToolConfig = { if (typeof tagFilters === 'string') { try { tagFilters = JSON.parse(tagFilters) - } catch (error) { + } catch { 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 && filter.tagValue.trim().length > 0) { - if (!groupedFilters[filter.tagName]) { - groupedFilters[filter.tagName] = [] + // Send full filter objects with operator support + structuredFilters = tagFilters + .filter((filter: Record) => { + // For boolean, any value is valid; for others, check for non-empty string + if (filter.fieldType === 'boolean') { + return filter.tagName && filter.tagValue !== undefined } - 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 - }) + return filter.tagName && filter.tagValue && String(filter.tagValue).trim().length > 0 + }) + .map((filter: Record) => ({ + tagName: filter.tagName as string, + tagSlot: (filter.tagSlot as string) || '', // Will be resolved by API from tagName + fieldType: (filter.fieldType as string) || 'text', + operator: (filter.operator as string) || 'eq', + value: filter.tagValue as string | number | boolean, + valueTo: filter.valueTo as string | number | undefined, + })) } } @@ -90,7 +93,7 @@ export const knowledgeSearchTool: ToolConfig = { knowledgeBaseIds, query: params.query, topK: params.topK ? Math.max(1, Math.min(100, Number(params.topK))) : 10, - ...(Object.keys(filters).length > 0 && { filters }), + ...(structuredFilters.length > 0 && { tagFilters: structuredFilters }), ...(workflowId && { workflowId }), } diff --git a/packages/db/constants.ts b/packages/db/constants.ts index 39bd76a57..536ae1d71 100644 --- a/packages/db/constants.ts +++ b/packages/db/constants.ts @@ -18,11 +18,56 @@ export const DEFAULT_TEAM_STORAGE_LIMIT_GB = 500 export const DEFAULT_ENTERPRISE_STORAGE_LIMIT_GB = 500 /** - * Tag slots available for knowledge base documents and embeddings + * Text tag slots for knowledge base documents and embeddings */ -export const TAG_SLOTS = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7'] as const +export const TEXT_TAG_SLOTS = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7'] as const /** - * Type for tag slot names + * Number tag slots for knowledge base documents and embeddings (5 slots) + */ +export const NUMBER_TAG_SLOTS = ['number1', 'number2', 'number3', 'number4', 'number5'] as const + +/** + * Date tag slots for knowledge base documents and embeddings (2 slots) + */ +export const DATE_TAG_SLOTS = ['date1', 'date2'] as const + +/** + * Boolean tag slots for knowledge base documents and embeddings (3 slots) + */ +export const BOOLEAN_TAG_SLOTS = ['boolean1', 'boolean2', 'boolean3'] as const + +/** + * All tag slots combined (for backwards compatibility) + */ +export const TAG_SLOTS = [ + ...TEXT_TAG_SLOTS, + ...NUMBER_TAG_SLOTS, + ...DATE_TAG_SLOTS, + ...BOOLEAN_TAG_SLOTS, +] as const + +/** + * Type for all tag slot names */ export type TagSlot = (typeof TAG_SLOTS)[number] + +/** + * Type for text tag slot names + */ +export type TextTagSlot = (typeof TEXT_TAG_SLOTS)[number] + +/** + * Type for number tag slot names + */ +export type NumberTagSlot = (typeof NUMBER_TAG_SLOTS)[number] + +/** + * Type for date tag slot names + */ +export type DateTagSlot = (typeof DATE_TAG_SLOTS)[number] + +/** + * Type for boolean tag slot names + */ +export type BooleanTagSlot = (typeof BOOLEAN_TAG_SLOTS)[number] diff --git a/packages/db/migrations/0126_dapper_midnight.sql b/packages/db/migrations/0126_dapper_midnight.sql new file mode 100644 index 000000000..64a38c8b4 --- /dev/null +++ b/packages/db/migrations/0126_dapper_midnight.sql @@ -0,0 +1,40 @@ +ALTER TABLE "document" ADD COLUMN "number1" double precision;--> statement-breakpoint +ALTER TABLE "document" ADD COLUMN "number2" double precision;--> statement-breakpoint +ALTER TABLE "document" ADD COLUMN "number3" double precision;--> statement-breakpoint +ALTER TABLE "document" ADD COLUMN "number4" double precision;--> statement-breakpoint +ALTER TABLE "document" ADD COLUMN "number5" double precision;--> statement-breakpoint +ALTER TABLE "document" ADD COLUMN "date1" timestamp;--> statement-breakpoint +ALTER TABLE "document" ADD COLUMN "date2" timestamp;--> statement-breakpoint +ALTER TABLE "document" ADD COLUMN "boolean1" boolean;--> statement-breakpoint +ALTER TABLE "document" ADD COLUMN "boolean2" boolean;--> statement-breakpoint +ALTER TABLE "document" ADD COLUMN "boolean3" boolean;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "number1" double precision;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "number2" double precision;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "number3" double precision;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "number4" double precision;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "number5" double precision;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "date1" timestamp;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "date2" timestamp;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "boolean1" boolean;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "boolean2" boolean;--> statement-breakpoint +ALTER TABLE "embedding" ADD COLUMN "boolean3" boolean;--> statement-breakpoint +CREATE INDEX "doc_number1_idx" ON "document" USING btree ("number1");--> statement-breakpoint +CREATE INDEX "doc_number2_idx" ON "document" USING btree ("number2");--> statement-breakpoint +CREATE INDEX "doc_number3_idx" ON "document" USING btree ("number3");--> statement-breakpoint +CREATE INDEX "doc_number4_idx" ON "document" USING btree ("number4");--> statement-breakpoint +CREATE INDEX "doc_number5_idx" ON "document" USING btree ("number5");--> statement-breakpoint +CREATE INDEX "doc_date1_idx" ON "document" USING btree ("date1");--> statement-breakpoint +CREATE INDEX "doc_date2_idx" ON "document" USING btree ("date2");--> statement-breakpoint +CREATE INDEX "doc_boolean1_idx" ON "document" USING btree ("boolean1");--> statement-breakpoint +CREATE INDEX "doc_boolean2_idx" ON "document" USING btree ("boolean2");--> statement-breakpoint +CREATE INDEX "doc_boolean3_idx" ON "document" USING btree ("boolean3");--> statement-breakpoint +CREATE INDEX "emb_number1_idx" ON "embedding" USING btree ("number1");--> statement-breakpoint +CREATE INDEX "emb_number2_idx" ON "embedding" USING btree ("number2");--> statement-breakpoint +CREATE INDEX "emb_number3_idx" ON "embedding" USING btree ("number3");--> statement-breakpoint +CREATE INDEX "emb_number4_idx" ON "embedding" USING btree ("number4");--> statement-breakpoint +CREATE INDEX "emb_number5_idx" ON "embedding" USING btree ("number5");--> statement-breakpoint +CREATE INDEX "emb_date1_idx" ON "embedding" USING btree ("date1");--> statement-breakpoint +CREATE INDEX "emb_date2_idx" ON "embedding" USING btree ("date2");--> statement-breakpoint +CREATE INDEX "emb_boolean1_idx" ON "embedding" USING btree ("boolean1");--> statement-breakpoint +CREATE INDEX "emb_boolean2_idx" ON "embedding" USING btree ("boolean2");--> statement-breakpoint +CREATE INDEX "emb_boolean3_idx" ON "embedding" USING btree ("boolean3"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0126_snapshot.json b/packages/db/migrations/meta/0126_snapshot.json new file mode 100644 index 000000000..27102ff8b --- /dev/null +++ b/packages/db/migrations/meta/0126_snapshot.json @@ -0,0 +1,8221 @@ +{ + "id": "28e1455e-42b7-4d72-b40c-ce264247bc67", + "prevId": "3764bc2d-e560-47d6-a827-c07936980298", + "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": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_user_provider_account_unique": { + "name": "account_user_provider_account_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "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": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "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 + }, + "identifier": { + "name": "identifier", + "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": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "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'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "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": { + "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_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "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": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "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 + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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": "set null", + "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 + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "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_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": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "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 + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "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_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "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.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_namespace_unique": { + "name": "idempotency_key_namespace_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "namespace", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idempotency_key_namespace_idx": { + "name": "idempotency_key_namespace_idx", + "columns": [ + { + "expression": "namespace", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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\": 1, \"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_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "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.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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": { + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_idx": { + "name": "mcp_servers_workspace_deleted_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "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": { + "member_user_id_idx": { + "name": "member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "data": { + "name": "data", + "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()" + }, + "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 + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "departed_member_usage": { + "name": "departed_member_usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "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 + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_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": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_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": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_token_idx": { + "name": "session_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "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.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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" + } + }, + "isRLSEnabled": false + }, + "public.template_creators": { + "name": "template_creators", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "reference_type": { + "name": "reference_type", + "type": "template_creator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile_image_url": { + "name": "profile_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "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": { + "template_creators_reference_idx": { + "name": "template_creators_reference_idx", + "columns": [ + { + "expression": "reference_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_reference_id_idx": { + "name": "template_creators_reference_id_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_created_by_idx": { + "name": "template_creators_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_creators_created_by_user_id_fk": { + "name": "template_creators_created_by_user_id_fk", + "tableFrom": "template_creators", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "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": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "template_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "required_credentials": { + "name": "required_credentials", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "og_image_url": { + "name": "og_image_url", + "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": { + "templates_status_idx": { + "name": "templates_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_creator_id_idx": { + "name": "templates_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "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_status_views_idx": { + "name": "templates_status_views_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_status_stars_idx": { + "name": "templates_status_stars_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stars", + "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": "set null", + "onUpdate": "no action" + }, + "templates_creator_id_template_creators_id_fk": { + "name": "templates_creator_id_template_creators_id_fk", + "tableFrom": "templates", + "tableTo": "template_creators", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "is_super_user": { + "name": "is_super_user", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "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": false, + "default": "'10'" + }, + "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'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + } + }, + "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": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "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": { + "path_idx": { + "name": "path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id": { + "name": "idx_webhook_on_workflow_id_block_id", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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 + }, + "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_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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": "'{}'" + } + }, + "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 + }, + "trigger_mode": { + "name": "trigger_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": "'{}'" + }, + "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_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "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_checkpoints": { + "name": "workflow_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 + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "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()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_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": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_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": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_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": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "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_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 + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "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 + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_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_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": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "last_queued_at": { + "name": "last_queued_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 + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "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": {}, + "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" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "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": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_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 + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "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": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_files_key_unique": { + "name": "workspace_files_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "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": "workspace_invitation_status", + "typeSchema": "public", + "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'" + }, + "org_invitation_id": { + "name": "org_invitation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "public.workspace_notification_delivery": { + "name": "workspace_notification_delivery", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "notification_delivery_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "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": { + "workspace_notification_delivery_subscription_id_idx": { + "name": "workspace_notification_delivery_subscription_id_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_execution_id_idx": { + "name": "workspace_notification_delivery_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_status_idx": { + "name": "workspace_notification_delivery_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_next_attempt_idx": { + "name": "workspace_notification_delivery_next_attempt_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk": { + "name": "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workspace_notification_subscription", + "columnsFrom": ["subscription_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_delivery_workflow_id_workflow_id_fk": { + "name": "workspace_notification_delivery_workflow_id_workflow_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_notification_subscription": { + "name": "workspace_notification_subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workflow_ids": { + "name": "workflow_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "all_workflows": { + "name": "all_workflows", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "level_filter": { + "name": "level_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['info', 'error']::text[]" + }, + "trigger_filter": { + "name": "trigger_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['api', 'webhook', 'schedule', 'manual', 'chat']::text[]" + }, + "include_final_output": { + "name": "include_final_output", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_trace_spans": { + "name": "include_trace_spans", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_rate_limits": { + "name": "include_rate_limits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_usage_data": { + "name": "include_usage_data", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "webhook_config": { + "name": "webhook_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "email_recipients": { + "name": "email_recipients", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "slack_config": { + "name": "slack_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "alert_config": { + "name": "alert_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_alert_at": { + "name": "last_alert_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "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": { + "workspace_notification_workspace_id_idx": { + "name": "workspace_notification_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_active_idx": { + "name": "workspace_notification_active_idx", + "columns": [ + { + "expression": "active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_type_idx": { + "name": "workspace_notification_type_idx", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_subscription_workspace_id_workspace_id_fk": { + "name": "workspace_notification_subscription_workspace_id_workspace_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_subscription_created_by_user_id_fk": { + "name": "workspace_notification_subscription_created_by_user_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.notification_delivery_status": { + "name": "notification_delivery_status", + "schema": "public", + "values": ["pending", "in_progress", "success", "failed"] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": ["webhook", "email", "slack"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.template_creator_type": { + "name": "template_creator_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.template_status": { + "name": "template_status", + "schema": "public", + "values": ["pending", "approved", "rejected"] + }, + "public.workspace_invitation_status": { + "name": "workspace_invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 798e59204..b3dd13337 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -876,6 +876,13 @@ "when": 1766133598113, "tag": "0125_eager_lily_hollister", "breakpoints": true + }, + { + "idx": 126, + "version": "7", + "when": 1766203036010, + "tag": "0126_dapper_midnight", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 4c41490ed..724269fca 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -5,6 +5,7 @@ import { check, customType, decimal, + doublePrecision, index, integer, json, @@ -1047,6 +1048,7 @@ export const document = pgTable( deletedAt: timestamp('deleted_at'), // Soft delete // Document tags for filtering (inherited by all chunks) + // Text tags (7 slots) tag1: text('tag1'), tag2: text('tag2'), tag3: text('tag3'), @@ -1054,6 +1056,19 @@ export const document = pgTable( tag5: text('tag5'), tag6: text('tag6'), tag7: text('tag7'), + // Number tags (5 slots) + number1: doublePrecision('number1'), + number2: doublePrecision('number2'), + number3: doublePrecision('number3'), + number4: doublePrecision('number4'), + number5: doublePrecision('number5'), + // Date tags (2 slots) + date1: timestamp('date1'), + date2: timestamp('date2'), + // Boolean tags (3 slots) + boolean1: boolean('boolean1'), + boolean2: boolean('boolean2'), + boolean3: boolean('boolean3'), // Timestamps uploadedAt: timestamp('uploaded_at').notNull().defaultNow(), @@ -1068,7 +1083,7 @@ export const document = pgTable( table.knowledgeBaseId, table.processingStatus ), - // Tag indexes for filtering + // Text tag indexes tag1Idx: index('doc_tag1_idx').on(table.tag1), tag2Idx: index('doc_tag2_idx').on(table.tag2), tag3Idx: index('doc_tag3_idx').on(table.tag3), @@ -1076,6 +1091,19 @@ export const document = pgTable( tag5Idx: index('doc_tag5_idx').on(table.tag5), tag6Idx: index('doc_tag6_idx').on(table.tag6), tag7Idx: index('doc_tag7_idx').on(table.tag7), + // Number tag indexes (5 slots) + number1Idx: index('doc_number1_idx').on(table.number1), + number2Idx: index('doc_number2_idx').on(table.number2), + number3Idx: index('doc_number3_idx').on(table.number3), + number4Idx: index('doc_number4_idx').on(table.number4), + number5Idx: index('doc_number5_idx').on(table.number5), + // Date tag indexes (2 slots) + date1Idx: index('doc_date1_idx').on(table.date1), + date2Idx: index('doc_date2_idx').on(table.date2), + // Boolean tag indexes (3 slots) + boolean1Idx: index('doc_boolean1_idx').on(table.boolean1), + boolean2Idx: index('doc_boolean2_idx').on(table.boolean2), + boolean3Idx: index('doc_boolean3_idx').on(table.boolean3), }) ) @@ -1137,6 +1165,7 @@ export const embedding = pgTable( endOffset: integer('end_offset').notNull(), // Tag columns inherited from document for efficient filtering + // Text tags (7 slots) tag1: text('tag1'), tag2: text('tag2'), tag3: text('tag3'), @@ -1144,6 +1173,19 @@ export const embedding = pgTable( tag5: text('tag5'), tag6: text('tag6'), tag7: text('tag7'), + // Number tags (5 slots) + number1: doublePrecision('number1'), + number2: doublePrecision('number2'), + number3: doublePrecision('number3'), + number4: doublePrecision('number4'), + number5: doublePrecision('number5'), + // Date tags (2 slots) + date1: timestamp('date1'), + date2: timestamp('date2'), + // Boolean tags (3 slots) + boolean1: boolean('boolean1'), + boolean2: boolean('boolean2'), + boolean3: boolean('boolean3'), // Chunk state - enable/disable from knowledge base enabled: boolean('enabled').notNull().default(true), @@ -1182,7 +1224,7 @@ export const embedding = pgTable( ef_construction: 64, }), - // Tag indexes for efficient filtering + // Text tag indexes tag1Idx: index('emb_tag1_idx').on(table.tag1), tag2Idx: index('emb_tag2_idx').on(table.tag2), tag3Idx: index('emb_tag3_idx').on(table.tag3), @@ -1190,6 +1232,19 @@ export const embedding = pgTable( tag5Idx: index('emb_tag5_idx').on(table.tag5), tag6Idx: index('emb_tag6_idx').on(table.tag6), tag7Idx: index('emb_tag7_idx').on(table.tag7), + // Number tag indexes (5 slots) + number1Idx: index('emb_number1_idx').on(table.number1), + number2Idx: index('emb_number2_idx').on(table.number2), + number3Idx: index('emb_number3_idx').on(table.number3), + number4Idx: index('emb_number4_idx').on(table.number4), + number5Idx: index('emb_number5_idx').on(table.number5), + // Date tag indexes (2 slots) + date1Idx: index('emb_date1_idx').on(table.date1), + date2Idx: index('emb_date2_idx').on(table.date2), + // Boolean tag indexes (3 slots) + boolean1Idx: index('emb_boolean1_idx').on(table.boolean1), + boolean2Idx: index('emb_boolean2_idx').on(table.boolean2), + boolean3Idx: index('emb_boolean3_idx').on(table.boolean3), // Full-text search index contentFtsIdx: index('emb_content_fts_idx').using('gin', table.contentTsv),