mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
feat(knowledge) (#449)
* feat(knowledge): setup knowledge and base UI * improvement(knowledge): empty state UI * feat(knowledge): created schema * added s3 bucket for kbs * fix: remove dummy values * feat(knowledge): embedding view; schema adjustments; migration history; navigation * feat(knowledge): block/tool for vector search --------- Co-authored-by: Waleed Latif <walif6@gmail.com>
This commit is contained in:
3
apps/sim/.gitignore
vendored
3
apps/sim/.gitignore
vendored
@@ -47,3 +47,6 @@ next-env.d.ts
|
||||
|
||||
# Sentry Config File
|
||||
.env.sentry-build-plugin
|
||||
|
||||
# Uploads
|
||||
/uploads
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { document, embedding, knowledgeBase } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('ChunkByIdAPI')
|
||||
|
||||
// Schema for chunk updates
|
||||
const UpdateChunkSchema = z.object({
|
||||
content: z.string().min(1, 'Content is required').optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
searchRank: z.number().min(0).optional(),
|
||||
qualityScore: z.number().min(0).max(1).optional(),
|
||||
})
|
||||
|
||||
async function checkChunkAccess(
|
||||
knowledgeBaseId: string,
|
||||
documentId: string,
|
||||
chunkId: string,
|
||||
userId: string
|
||||
) {
|
||||
// First check knowledge base access
|
||||
const kb = await db
|
||||
.select({
|
||||
id: knowledgeBase.id,
|
||||
userId: knowledgeBase.userId,
|
||||
})
|
||||
.from(knowledgeBase)
|
||||
.where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt)))
|
||||
.limit(1)
|
||||
|
||||
if (kb.length === 0) {
|
||||
return { hasAccess: false, notFound: true, reason: 'Knowledge base not found' }
|
||||
}
|
||||
|
||||
const kbData = kb[0]
|
||||
|
||||
// Check if user owns the knowledge base
|
||||
if (kbData.userId !== userId) {
|
||||
return { hasAccess: false, reason: 'Unauthorized knowledge base access' }
|
||||
}
|
||||
|
||||
// Check if document exists and belongs to the knowledge base
|
||||
const doc = await db
|
||||
.select()
|
||||
.from(document)
|
||||
.where(
|
||||
and(
|
||||
eq(document.id, documentId),
|
||||
eq(document.knowledgeBaseId, knowledgeBaseId),
|
||||
isNull(document.deletedAt)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (doc.length === 0) {
|
||||
return { hasAccess: false, notFound: true, reason: 'Document not found' }
|
||||
}
|
||||
|
||||
// Check if chunk exists and belongs to the document
|
||||
const chunk = await db
|
||||
.select()
|
||||
.from(embedding)
|
||||
.where(and(eq(embedding.id, chunkId), eq(embedding.documentId, documentId)))
|
||||
.limit(1)
|
||||
|
||||
if (chunk.length === 0) {
|
||||
return { hasAccess: false, notFound: true, reason: 'Chunk not found' }
|
||||
}
|
||||
|
||||
return { hasAccess: true, chunk: chunk[0], document: doc[0], knowledgeBase: kbData }
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; documentId: string; chunkId: string }> }
|
||||
) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId, documentId, chunkId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized chunk access attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkChunkAccess(
|
||||
knowledgeBaseId,
|
||||
documentId,
|
||||
chunkId,
|
||||
session.user.id
|
||||
)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(
|
||||
`[${requestId}] ${accessCheck.reason}: KB=${knowledgeBaseId}, Doc=${documentId}, Chunk=${chunkId}`
|
||||
)
|
||||
return NextResponse.json({ error: accessCheck.reason }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted unauthorized chunk access: ${accessCheck.reason}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Retrieved chunk: ${chunkId} from document ${documentId} in knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: accessCheck.chunk,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error fetching chunk`, error)
|
||||
return NextResponse.json({ error: 'Failed to fetch chunk' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; documentId: string; chunkId: string }> }
|
||||
) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId, documentId, chunkId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized chunk update attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkChunkAccess(
|
||||
knowledgeBaseId,
|
||||
documentId,
|
||||
chunkId,
|
||||
session.user.id
|
||||
)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(
|
||||
`[${requestId}] ${accessCheck.reason}: KB=${knowledgeBaseId}, Doc=${documentId}, Chunk=${chunkId}`
|
||||
)
|
||||
return NextResponse.json({ error: accessCheck.reason }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted unauthorized chunk update: ${accessCheck.reason}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
|
||||
try {
|
||||
const validatedData = UpdateChunkSchema.parse(body)
|
||||
|
||||
const updateData: any = {
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
|
||||
if (validatedData.content !== undefined) {
|
||||
updateData.content = validatedData.content
|
||||
updateData.contentLength = validatedData.content.length
|
||||
// Update token count estimation (rough approximation: 4 chars per token)
|
||||
updateData.tokenCount = Math.ceil(validatedData.content.length / 4)
|
||||
}
|
||||
if (validatedData.enabled !== undefined) updateData.enabled = validatedData.enabled
|
||||
if (validatedData.searchRank !== undefined)
|
||||
updateData.searchRank = validatedData.searchRank.toString()
|
||||
if (validatedData.qualityScore !== undefined)
|
||||
updateData.qualityScore = validatedData.qualityScore.toString()
|
||||
|
||||
await db.update(embedding).set(updateData).where(eq(embedding.id, chunkId))
|
||||
|
||||
// Fetch the updated chunk
|
||||
const updatedChunk = await db
|
||||
.select()
|
||||
.from(embedding)
|
||||
.where(eq(embedding.id, chunkId))
|
||||
.limit(1)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Chunk updated: ${chunkId} in document ${documentId} in knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: updatedChunk[0],
|
||||
})
|
||||
} catch (validationError) {
|
||||
if (validationError instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid chunk update data`, {
|
||||
errors: validationError.errors,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: validationError.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
throw validationError
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error updating chunk`, error)
|
||||
return NextResponse.json({ error: 'Failed to update chunk' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; documentId: string; chunkId: string }> }
|
||||
) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId, documentId, chunkId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized chunk delete attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkChunkAccess(
|
||||
knowledgeBaseId,
|
||||
documentId,
|
||||
chunkId,
|
||||
session.user.id
|
||||
)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(
|
||||
`[${requestId}] ${accessCheck.reason}: KB=${knowledgeBaseId}, Doc=${documentId}, Chunk=${chunkId}`
|
||||
)
|
||||
return NextResponse.json({ error: accessCheck.reason }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted unauthorized chunk deletion: ${accessCheck.reason}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Delete the chunk
|
||||
await db.delete(embedding).where(eq(embedding.id, chunkId))
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Chunk deleted: ${chunkId} from document ${documentId} in knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { message: 'Chunk deleted successfully' },
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error deleting chunk`, error)
|
||||
return NextResponse.json({ error: 'Failed to delete chunk' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import { and, asc, eq, ilike, isNull, sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { document, embedding, knowledgeBase } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('DocumentChunksAPI')
|
||||
|
||||
// Schema for query parameters
|
||||
const GetChunksQuerySchema = z.object({
|
||||
search: z.string().optional(),
|
||||
enabled: z.enum(['true', 'false', 'all']).optional().default('all'),
|
||||
limit: z.coerce.number().min(1).max(100).optional().default(50),
|
||||
offset: z.coerce.number().min(0).optional().default(0),
|
||||
})
|
||||
|
||||
async function checkDocumentAccess(knowledgeBaseId: string, documentId: string, userId: string) {
|
||||
// First check knowledge base access
|
||||
const kb = await db
|
||||
.select({
|
||||
id: knowledgeBase.id,
|
||||
userId: knowledgeBase.userId,
|
||||
})
|
||||
.from(knowledgeBase)
|
||||
.where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt)))
|
||||
.limit(1)
|
||||
|
||||
if (kb.length === 0) {
|
||||
return { hasAccess: false, notFound: true, reason: 'Knowledge base not found' }
|
||||
}
|
||||
|
||||
const kbData = kb[0]
|
||||
|
||||
// Check if user owns the knowledge base
|
||||
if (kbData.userId !== userId) {
|
||||
return { hasAccess: false, reason: 'Unauthorized knowledge base access' }
|
||||
}
|
||||
|
||||
// Now check if document exists and belongs to the knowledge base
|
||||
const doc = await db
|
||||
.select()
|
||||
.from(document)
|
||||
.where(
|
||||
and(
|
||||
eq(document.id, documentId),
|
||||
eq(document.knowledgeBaseId, knowledgeBaseId),
|
||||
isNull(document.deletedAt)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (doc.length === 0) {
|
||||
return { hasAccess: false, notFound: true, reason: 'Document not found' }
|
||||
}
|
||||
|
||||
return { hasAccess: true, document: doc[0], knowledgeBase: kbData }
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; documentId: string }> }
|
||||
) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId, documentId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized chunks access attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkDocumentAccess(knowledgeBaseId, documentId, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] ${accessCheck.reason}: KB=${knowledgeBaseId}, Doc=${documentId}`)
|
||||
return NextResponse.json({ error: accessCheck.reason }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted unauthorized chunks access: ${accessCheck.reason}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Parse query parameters
|
||||
const { searchParams } = new URL(req.url)
|
||||
const queryParams = GetChunksQuerySchema.parse({
|
||||
search: searchParams.get('search') || undefined,
|
||||
enabled: searchParams.get('enabled') || undefined,
|
||||
limit: searchParams.get('limit') || undefined,
|
||||
offset: searchParams.get('offset') || undefined,
|
||||
})
|
||||
|
||||
// Build query conditions
|
||||
const conditions = [eq(embedding.documentId, documentId)]
|
||||
|
||||
// Add enabled filter
|
||||
if (queryParams.enabled === 'true') {
|
||||
conditions.push(eq(embedding.enabled, true))
|
||||
} else if (queryParams.enabled === 'false') {
|
||||
conditions.push(eq(embedding.enabled, false))
|
||||
}
|
||||
|
||||
// Add search filter
|
||||
if (queryParams.search) {
|
||||
conditions.push(ilike(embedding.content, `%${queryParams.search}%`))
|
||||
}
|
||||
|
||||
// Fetch chunks
|
||||
const chunks = await db
|
||||
.select({
|
||||
id: embedding.id,
|
||||
chunkIndex: embedding.chunkIndex,
|
||||
content: embedding.content,
|
||||
contentLength: embedding.contentLength,
|
||||
tokenCount: embedding.tokenCount,
|
||||
enabled: embedding.enabled,
|
||||
startOffset: embedding.startOffset,
|
||||
endOffset: embedding.endOffset,
|
||||
overlapTokens: embedding.overlapTokens,
|
||||
metadata: embedding.metadata,
|
||||
searchRank: embedding.searchRank,
|
||||
qualityScore: embedding.qualityScore,
|
||||
createdAt: embedding.createdAt,
|
||||
updatedAt: embedding.updatedAt,
|
||||
})
|
||||
.from(embedding)
|
||||
.where(and(...conditions))
|
||||
.orderBy(asc(embedding.chunkIndex))
|
||||
.limit(queryParams.limit)
|
||||
.offset(queryParams.offset)
|
||||
|
||||
// Get total count for pagination
|
||||
const totalCount = await db
|
||||
.select({ count: sql`count(*)` })
|
||||
.from(embedding)
|
||||
.where(and(...conditions))
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Retrieved ${chunks.length} chunks for document ${documentId} in knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: chunks,
|
||||
pagination: {
|
||||
total: Number(totalCount[0]?.count || 0),
|
||||
limit: queryParams.limit,
|
||||
offset: queryParams.offset,
|
||||
hasMore: chunks.length === queryParams.limit,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error fetching chunks`, error)
|
||||
return NextResponse.json({ error: 'Failed to fetch chunks' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
229
apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts
Normal file
229
apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { document, knowledgeBase } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('DocumentByIdAPI')
|
||||
|
||||
// Schema for document updates
|
||||
const UpdateDocumentSchema = z.object({
|
||||
filename: z.string().min(1, 'Filename is required').optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
chunkCount: z.number().min(0).optional(),
|
||||
tokenCount: z.number().min(0).optional(),
|
||||
characterCount: z.number().min(0).optional(),
|
||||
})
|
||||
|
||||
async function checkDocumentAccess(knowledgeBaseId: string, documentId: string, userId: string) {
|
||||
// First check knowledge base access
|
||||
const kb = await db
|
||||
.select({
|
||||
id: knowledgeBase.id,
|
||||
userId: knowledgeBase.userId,
|
||||
})
|
||||
.from(knowledgeBase)
|
||||
.where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt)))
|
||||
.limit(1)
|
||||
|
||||
if (kb.length === 0) {
|
||||
return { hasAccess: false, notFound: true, reason: 'Knowledge base not found' }
|
||||
}
|
||||
|
||||
const kbData = kb[0]
|
||||
|
||||
// Check if user owns the knowledge base
|
||||
if (kbData.userId !== userId) {
|
||||
return { hasAccess: false, reason: 'Unauthorized knowledge base access' }
|
||||
}
|
||||
|
||||
// Now check if document exists and belongs to the knowledge base
|
||||
const doc = await db
|
||||
.select()
|
||||
.from(document)
|
||||
.where(
|
||||
and(
|
||||
eq(document.id, documentId),
|
||||
eq(document.knowledgeBaseId, knowledgeBaseId),
|
||||
isNull(document.deletedAt)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (doc.length === 0) {
|
||||
return { hasAccess: false, notFound: true, reason: 'Document not found' }
|
||||
}
|
||||
|
||||
return { hasAccess: true, document: doc[0], knowledgeBase: kbData }
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; documentId: string }> }
|
||||
) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId, documentId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized document access attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkDocumentAccess(knowledgeBaseId, documentId, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] ${accessCheck.reason}: KB=${knowledgeBaseId}, Doc=${documentId}`)
|
||||
return NextResponse.json({ error: accessCheck.reason }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted unauthorized document access: ${accessCheck.reason}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Retrieved document: ${documentId} from knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: accessCheck.document,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error fetching document`, error)
|
||||
return NextResponse.json({ error: 'Failed to fetch document' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; documentId: string }> }
|
||||
) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId, documentId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized document update attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkDocumentAccess(knowledgeBaseId, documentId, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] ${accessCheck.reason}: KB=${knowledgeBaseId}, Doc=${documentId}`)
|
||||
return NextResponse.json({ error: accessCheck.reason }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted unauthorized document update: ${accessCheck.reason}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
|
||||
try {
|
||||
const validatedData = UpdateDocumentSchema.parse(body)
|
||||
|
||||
const updateData: any = {}
|
||||
|
||||
if (validatedData.filename !== undefined) updateData.filename = validatedData.filename
|
||||
if (validatedData.enabled !== undefined) updateData.enabled = validatedData.enabled
|
||||
if (validatedData.chunkCount !== undefined) updateData.chunkCount = validatedData.chunkCount
|
||||
if (validatedData.tokenCount !== undefined) updateData.tokenCount = validatedData.tokenCount
|
||||
if (validatedData.characterCount !== undefined)
|
||||
updateData.characterCount = validatedData.characterCount
|
||||
|
||||
await db.update(document).set(updateData).where(eq(document.id, documentId))
|
||||
|
||||
// Fetch the updated document
|
||||
const updatedDocument = await db
|
||||
.select()
|
||||
.from(document)
|
||||
.where(eq(document.id, documentId))
|
||||
.limit(1)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Document updated: ${documentId} in knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: updatedDocument[0],
|
||||
})
|
||||
} catch (validationError) {
|
||||
if (validationError instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid document update data`, {
|
||||
errors: validationError.errors,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: validationError.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
throw validationError
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error updating document`, error)
|
||||
return NextResponse.json({ error: 'Failed to update document' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; documentId: string }> }
|
||||
) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId, documentId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized document delete attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkDocumentAccess(knowledgeBaseId, documentId, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] ${accessCheck.reason}: KB=${knowledgeBaseId}, Doc=${documentId}`)
|
||||
return NextResponse.json({ error: accessCheck.reason }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted unauthorized document deletion: ${accessCheck.reason}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Soft delete by setting deletedAt timestamp
|
||||
await db
|
||||
.update(document)
|
||||
.set({
|
||||
deletedAt: new Date(),
|
||||
})
|
||||
.where(eq(document.id, documentId))
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Document deleted: ${documentId} from knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { message: 'Document deleted successfully' },
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error deleting document`, error)
|
||||
return NextResponse.json({ error: 'Failed to delete document' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
213
apps/sim/app/api/knowledge/[id]/documents/route.ts
Normal file
213
apps/sim/app/api/knowledge/[id]/documents/route.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { document, knowledgeBase } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('DocumentsAPI')
|
||||
|
||||
// Schema for document creation
|
||||
const CreateDocumentSchema = z.object({
|
||||
filename: z.string().min(1, 'Filename is required'),
|
||||
fileUrl: z.string().url('File URL must be valid'),
|
||||
fileSize: z.number().min(1, 'File size must be greater than 0'),
|
||||
mimeType: z.string().min(1, 'MIME type is required'),
|
||||
fileHash: z.string().optional(),
|
||||
})
|
||||
|
||||
async function checkKnowledgeBaseAccess(knowledgeBaseId: string, userId: string) {
|
||||
const kb = await db
|
||||
.select({
|
||||
id: knowledgeBase.id,
|
||||
userId: knowledgeBase.userId,
|
||||
})
|
||||
.from(knowledgeBase)
|
||||
.where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt)))
|
||||
.limit(1)
|
||||
|
||||
if (kb.length === 0) {
|
||||
return { hasAccess: false, notFound: true }
|
||||
}
|
||||
|
||||
const kbData = kb[0]
|
||||
|
||||
// Check if user owns the knowledge base
|
||||
if (kbData.userId === userId) {
|
||||
return { hasAccess: true, knowledgeBase: kbData }
|
||||
}
|
||||
|
||||
return { hasAccess: false, knowledgeBase: kbData }
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized documents access attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] Knowledge base not found: ${knowledgeBaseId}`)
|
||||
return NextResponse.json({ error: 'Knowledge base not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted to access unauthorized knowledge base documents ${knowledgeBaseId}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const url = new URL(req.url)
|
||||
const includeDisabled = url.searchParams.get('includeDisabled') === 'true'
|
||||
|
||||
// Build where conditions
|
||||
const whereConditions = [
|
||||
eq(document.knowledgeBaseId, knowledgeBaseId),
|
||||
isNull(document.deletedAt),
|
||||
]
|
||||
|
||||
// Filter out disabled documents unless specifically requested
|
||||
if (!includeDisabled) {
|
||||
whereConditions.push(eq(document.enabled, true))
|
||||
}
|
||||
|
||||
const documents = await db
|
||||
.select({
|
||||
id: document.id,
|
||||
knowledgeBaseId: document.knowledgeBaseId,
|
||||
filename: document.filename,
|
||||
fileUrl: document.fileUrl,
|
||||
fileSize: document.fileSize,
|
||||
mimeType: document.mimeType,
|
||||
fileHash: document.fileHash,
|
||||
chunkCount: document.chunkCount,
|
||||
tokenCount: document.tokenCount,
|
||||
characterCount: document.characterCount,
|
||||
enabled: document.enabled,
|
||||
uploadedAt: document.uploadedAt,
|
||||
})
|
||||
.from(document)
|
||||
.where(and(...whereConditions))
|
||||
.orderBy(document.uploadedAt)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Retrieved ${documents.length} documents for knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: documents,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error fetching documents`, error)
|
||||
return NextResponse.json({ error: 'Failed to fetch documents' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized document creation attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] Knowledge base not found: ${knowledgeBaseId}`)
|
||||
return NextResponse.json({ error: 'Knowledge base not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted to create document in unauthorized knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
|
||||
try {
|
||||
const validatedData = CreateDocumentSchema.parse(body)
|
||||
|
||||
// Check for duplicate file hash if provided
|
||||
if (validatedData.fileHash) {
|
||||
const existingDocument = await db
|
||||
.select({ id: document.id })
|
||||
.from(document)
|
||||
.where(
|
||||
and(
|
||||
eq(document.knowledgeBaseId, knowledgeBaseId),
|
||||
eq(document.fileHash, validatedData.fileHash),
|
||||
isNull(document.deletedAt)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (existingDocument.length > 0) {
|
||||
logger.warn(`[${requestId}] Duplicate file hash detected: ${validatedData.fileHash}`)
|
||||
return NextResponse.json(
|
||||
{ error: 'Document with this file hash already exists' },
|
||||
{ status: 409 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const documentId = crypto.randomUUID()
|
||||
const now = new Date()
|
||||
|
||||
const newDocument = {
|
||||
id: documentId,
|
||||
knowledgeBaseId,
|
||||
filename: validatedData.filename,
|
||||
fileUrl: validatedData.fileUrl,
|
||||
fileSize: validatedData.fileSize,
|
||||
mimeType: validatedData.mimeType,
|
||||
fileHash: validatedData.fileHash || null,
|
||||
chunkCount: 0,
|
||||
tokenCount: 0,
|
||||
characterCount: 0,
|
||||
enabled: true,
|
||||
uploadedAt: now,
|
||||
}
|
||||
|
||||
await db.insert(document).values(newDocument)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Document created: ${documentId} in knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: newDocument,
|
||||
})
|
||||
} catch (validationError) {
|
||||
if (validationError instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid document data`, {
|
||||
errors: validationError.errors,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: validationError.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
throw validationError
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error creating document`, error)
|
||||
return NextResponse.json({ error: 'Failed to create document' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
392
apps/sim/app/api/knowledge/[id]/process-documents/route.ts
Normal file
392
apps/sim/app/api/knowledge/[id]/process-documents/route.ts
Normal file
@@ -0,0 +1,392 @@
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { type ProcessedDocument, processDocuments } from '@/lib/document-processor'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { document, embedding, knowledgeBase } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('ProcessDocumentsAPI')
|
||||
|
||||
// Schema for document processing request
|
||||
const ProcessDocumentsSchema = z.object({
|
||||
documents: z
|
||||
.array(
|
||||
z.object({
|
||||
filename: z.string().min(1, 'Filename is required'),
|
||||
fileUrl: z.string().url('File URL must be valid'),
|
||||
fileSize: z.number().min(1, 'File size must be greater than 0'),
|
||||
mimeType: z.string().min(1, 'MIME type is required'),
|
||||
fileHash: z.string().optional(),
|
||||
})
|
||||
)
|
||||
.min(1, 'At least one document is required'),
|
||||
processingOptions: z
|
||||
.object({
|
||||
chunkSize: z.number().min(100).max(2048).default(512),
|
||||
minCharactersPerChunk: z.number().min(10).max(1000).default(24),
|
||||
recipe: z.string().default('default'),
|
||||
lang: z.string().default('en'),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
async function checkKnowledgeBaseAccess(knowledgeBaseId: string, userId: string) {
|
||||
const kb = await db
|
||||
.select({
|
||||
id: knowledgeBase.id,
|
||||
userId: knowledgeBase.userId,
|
||||
chunkingConfig: knowledgeBase.chunkingConfig,
|
||||
})
|
||||
.from(knowledgeBase)
|
||||
.where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt)))
|
||||
.limit(1)
|
||||
|
||||
if (kb.length === 0) {
|
||||
return { hasAccess: false, notFound: true }
|
||||
}
|
||||
|
||||
const kbData = kb[0]
|
||||
|
||||
// Check if user owns the knowledge base
|
||||
if (kbData.userId === userId) {
|
||||
return { hasAccess: true, knowledgeBase: kbData }
|
||||
}
|
||||
|
||||
return { hasAccess: false, knowledgeBase: kbData }
|
||||
}
|
||||
|
||||
async function generateEmbeddings(
|
||||
texts: string[],
|
||||
embeddingModel = 'text-embedding-3-small'
|
||||
): Promise<number[][]> {
|
||||
const openaiApiKey = env.OPENAI_API_KEY
|
||||
if (!openaiApiKey) {
|
||||
throw new Error('OPENAI_API_KEY not configured')
|
||||
}
|
||||
|
||||
try {
|
||||
// Batch process embeddings for efficiency
|
||||
const batchSize = 100 // OpenAI allows up to 2048 inputs per request
|
||||
const allEmbeddings: number[][] = []
|
||||
|
||||
for (let i = 0; i < texts.length; i += batchSize) {
|
||||
const batch = texts.slice(i, i + batchSize)
|
||||
|
||||
logger.info(
|
||||
`Generating embeddings for batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(texts.length / batchSize)} (${batch.length} texts)`
|
||||
)
|
||||
|
||||
// Make direct API call to OpenAI embeddings
|
||||
const response = await fetch('https://api.openai.com/v1/embeddings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${openaiApiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
input: batch,
|
||||
model: embeddingModel,
|
||||
encoding_format: 'float',
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(
|
||||
`OpenAI API error: ${response.status} ${response.statusText} - ${errorText}`
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
throw new Error('Invalid response format from OpenAI embeddings API')
|
||||
}
|
||||
|
||||
// Extract embeddings from response
|
||||
const batchEmbeddings = data.data.map((item: any) => item.embedding)
|
||||
allEmbeddings.push(...batchEmbeddings)
|
||||
}
|
||||
|
||||
logger.info(`Successfully generated ${allEmbeddings.length} embeddings`)
|
||||
return allEmbeddings
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate embeddings:', error)
|
||||
throw new Error(
|
||||
`Embedding generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProcessedDocuments(
|
||||
knowledgeBaseId: string,
|
||||
processedDocuments: ProcessedDocument[],
|
||||
requestedDocuments: Array<{
|
||||
filename: string
|
||||
fileUrl: string
|
||||
fileSize: number
|
||||
mimeType: string
|
||||
fileHash?: string
|
||||
}>
|
||||
) {
|
||||
const now = new Date()
|
||||
const results: Array<{
|
||||
documentId: string
|
||||
chunkCount: number
|
||||
success: boolean
|
||||
error?: string
|
||||
}> = []
|
||||
|
||||
// Collect all chunk texts for batch embedding generation
|
||||
const allChunkTexts: string[] = []
|
||||
const chunkMapping: Array<{ docIndex: number; chunkIndex: number }> = []
|
||||
|
||||
processedDocuments.forEach((processed, docIndex) => {
|
||||
processed.chunks.forEach((chunk, chunkIndex) => {
|
||||
allChunkTexts.push(chunk.text)
|
||||
chunkMapping.push({ docIndex, chunkIndex })
|
||||
})
|
||||
})
|
||||
|
||||
// Generate embeddings for all chunks at once
|
||||
let allEmbeddings: number[][] = []
|
||||
if (allChunkTexts.length > 0) {
|
||||
try {
|
||||
logger.info(
|
||||
`Generating embeddings for ${allChunkTexts.length} chunks across ${processedDocuments.length} documents`
|
||||
)
|
||||
allEmbeddings = await generateEmbeddings(allChunkTexts, 'text-embedding-3-small')
|
||||
logger.info(`Successfully generated ${allEmbeddings.length} embeddings`)
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate embeddings for chunks:', error)
|
||||
// Continue without embeddings rather than failing completely
|
||||
allEmbeddings = []
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < processedDocuments.length; i++) {
|
||||
const processed = processedDocuments[i]
|
||||
const original = requestedDocuments.find((doc) => doc.filename === processed.metadata.filename)
|
||||
|
||||
if (!original) {
|
||||
results.push({
|
||||
documentId: '',
|
||||
chunkCount: 0,
|
||||
success: false,
|
||||
error: `Original document data not found for ${processed.metadata.filename}`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
// Check for duplicate file hash if provided
|
||||
if (original.fileHash) {
|
||||
const existingDocument = await db
|
||||
.select({ id: document.id })
|
||||
.from(document)
|
||||
.where(
|
||||
and(
|
||||
eq(document.knowledgeBaseId, knowledgeBaseId),
|
||||
eq(document.fileHash, original.fileHash),
|
||||
isNull(document.deletedAt)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (existingDocument.length > 0) {
|
||||
results.push({
|
||||
documentId: existingDocument[0].id,
|
||||
chunkCount: 0,
|
||||
success: false,
|
||||
error: 'Document with this file hash already exists',
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Insert document record
|
||||
const documentId = crypto.randomUUID()
|
||||
const newDocument = {
|
||||
id: documentId,
|
||||
knowledgeBaseId,
|
||||
filename: original.filename,
|
||||
fileUrl: processed.metadata.s3Url || original.fileUrl,
|
||||
fileSize: original.fileSize,
|
||||
mimeType: original.mimeType,
|
||||
fileHash: original.fileHash || null,
|
||||
chunkCount: processed.metadata.chunkCount,
|
||||
tokenCount: processed.metadata.tokenCount,
|
||||
characterCount: processed.metadata.characterCount,
|
||||
enabled: true,
|
||||
uploadedAt: now,
|
||||
}
|
||||
|
||||
await db.insert(document).values(newDocument)
|
||||
|
||||
// Insert embedding records for chunks with generated embeddings
|
||||
const embeddingRecords = processed.chunks.map((chunk, chunkIndex) => {
|
||||
// Find the corresponding embedding for this chunk
|
||||
const globalChunkIndex = chunkMapping.findIndex(
|
||||
(mapping) => mapping.docIndex === i && mapping.chunkIndex === chunkIndex
|
||||
)
|
||||
const embedding =
|
||||
globalChunkIndex >= 0 && globalChunkIndex < allEmbeddings.length
|
||||
? allEmbeddings[globalChunkIndex]
|
||||
: null
|
||||
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
knowledgeBaseId,
|
||||
documentId,
|
||||
chunkIndex: chunkIndex,
|
||||
chunkHash: crypto.randomUUID(), // Generate a hash for the chunk
|
||||
content: chunk.text,
|
||||
contentLength: chunk.text.length,
|
||||
tokenCount: Math.ceil(chunk.text.length / 4), // Rough token estimation
|
||||
embedding: embedding, // Store the generated OpenAI embedding
|
||||
embeddingModel: 'text-embedding-3-small',
|
||||
startOffset: chunk.startIndex || 0,
|
||||
endOffset: chunk.endIndex || chunk.text.length,
|
||||
overlapTokens: 0,
|
||||
metadata: {},
|
||||
searchRank: '1.0',
|
||||
accessCount: 0,
|
||||
lastAccessedAt: null,
|
||||
qualityScore: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
})
|
||||
|
||||
if (embeddingRecords.length > 0) {
|
||||
await db.insert(embedding).values(embeddingRecords)
|
||||
}
|
||||
|
||||
results.push({
|
||||
documentId,
|
||||
chunkCount: processed.metadata.chunkCount,
|
||||
success: true,
|
||||
})
|
||||
|
||||
logger.info(
|
||||
`Document processed and saved: ${documentId} with ${processed.metadata.chunkCount} chunks and ${embeddingRecords.filter((r) => r.embedding).length} embeddings`
|
||||
)
|
||||
} catch (error) {
|
||||
logger.error(`Failed to save processed document ${processed.metadata.filename}:`, error)
|
||||
results.push({
|
||||
documentId: '',
|
||||
chunkCount: 0,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error during save',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id: knowledgeBaseId } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized document processing attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] Knowledge base not found: ${knowledgeBaseId}`)
|
||||
return NextResponse.json({ error: 'Knowledge base not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted to process documents in unauthorized knowledge base ${knowledgeBaseId}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
|
||||
try {
|
||||
const validatedData = ProcessDocumentsSchema.parse(body)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Starting processing of ${validatedData.documents.length} documents`
|
||||
)
|
||||
|
||||
// Get chunking config from knowledge base or use defaults
|
||||
const kbChunkingConfig = accessCheck.knowledgeBase?.chunkingConfig as any
|
||||
const processingOptions = {
|
||||
knowledgeBaseId,
|
||||
chunkSize: validatedData.processingOptions?.chunkSize || kbChunkingConfig?.maxSize || 512,
|
||||
minCharactersPerChunk:
|
||||
validatedData.processingOptions?.minCharactersPerChunk || kbChunkingConfig?.minSize || 24,
|
||||
recipe: validatedData.processingOptions?.recipe || 'default',
|
||||
lang: validatedData.processingOptions?.lang || 'en',
|
||||
}
|
||||
|
||||
// Process documents (parsing + chunking)
|
||||
const processedDocuments = await processDocuments(
|
||||
validatedData.documents.map((doc) => ({
|
||||
fileUrl: doc.fileUrl,
|
||||
filename: doc.filename,
|
||||
mimeType: doc.mimeType,
|
||||
fileSize: doc.fileSize,
|
||||
})),
|
||||
processingOptions
|
||||
)
|
||||
|
||||
// Save processed documents and chunks to database
|
||||
const saveResults = await saveProcessedDocuments(
|
||||
knowledgeBaseId,
|
||||
processedDocuments,
|
||||
validatedData.documents
|
||||
)
|
||||
|
||||
const successfulCount = saveResults.filter((r) => r.success).length
|
||||
const totalChunks = saveResults.reduce((sum, r) => sum + r.chunkCount, 0)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Document processing completed: ${successfulCount}/${validatedData.documents.length} documents, ${totalChunks} total chunks`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
processed: successfulCount,
|
||||
total: validatedData.documents.length,
|
||||
totalChunks,
|
||||
results: saveResults,
|
||||
},
|
||||
})
|
||||
} catch (validationError) {
|
||||
if (validationError instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid document processing data`, {
|
||||
errors: validationError.errors,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: validationError.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
throw validationError
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error processing documents`, error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to process documents',
|
||||
details: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
224
apps/sim/app/api/knowledge/[id]/route.ts
Normal file
224
apps/sim/app/api/knowledge/[id]/route.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { knowledgeBase } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('KnowledgeBaseByIdAPI')
|
||||
|
||||
// Schema for knowledge base updates
|
||||
const UpdateKnowledgeBaseSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required').optional(),
|
||||
description: z.string().optional(),
|
||||
embeddingModel: z.literal('text-embedding-3-small').optional(),
|
||||
embeddingDimension: z.literal(1536).optional(),
|
||||
chunkingConfig: z
|
||||
.object({
|
||||
maxSize: z.number(),
|
||||
minSize: z.number(),
|
||||
overlap: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
async function checkKnowledgeBaseAccess(knowledgeBaseId: string, userId: string) {
|
||||
const kb = await db
|
||||
.select({
|
||||
id: knowledgeBase.id,
|
||||
userId: knowledgeBase.userId,
|
||||
})
|
||||
.from(knowledgeBase)
|
||||
.where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt)))
|
||||
.limit(1)
|
||||
|
||||
if (kb.length === 0) {
|
||||
return { hasAccess: false, notFound: true }
|
||||
}
|
||||
|
||||
const kbData = kb[0]
|
||||
|
||||
// Check if user owns the knowledge base
|
||||
if (kbData.userId === userId) {
|
||||
return { hasAccess: true, knowledgeBase: kbData }
|
||||
}
|
||||
|
||||
return { hasAccess: false, knowledgeBase: kbData }
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized knowledge base access attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkKnowledgeBaseAccess(id, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] Knowledge base not found: ${id}`)
|
||||
return NextResponse.json({ error: 'Knowledge base not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted to access unauthorized knowledge base ${id}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const knowledgeBases = await db
|
||||
.select()
|
||||
.from(knowledgeBase)
|
||||
.where(and(eq(knowledgeBase.id, id), isNull(knowledgeBase.deletedAt)))
|
||||
.limit(1)
|
||||
|
||||
if (knowledgeBases.length === 0) {
|
||||
return NextResponse.json({ error: 'Knowledge base not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Retrieved knowledge base: ${id} for user ${session.user.id}`)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: knowledgeBases[0],
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error fetching knowledge base`, error)
|
||||
return NextResponse.json({ error: 'Failed to fetch knowledge base' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized knowledge base update attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkKnowledgeBaseAccess(id, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] Knowledge base not found: ${id}`)
|
||||
return NextResponse.json({ error: 'Knowledge base not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted to update unauthorized knowledge base ${id}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
|
||||
try {
|
||||
const validatedData = UpdateKnowledgeBaseSchema.parse(body)
|
||||
|
||||
const updateData: any = {
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
|
||||
if (validatedData.name !== undefined) updateData.name = validatedData.name
|
||||
if (validatedData.description !== undefined)
|
||||
updateData.description = validatedData.description
|
||||
|
||||
// Handle embedding model and dimension together to ensure consistency
|
||||
if (
|
||||
validatedData.embeddingModel !== undefined ||
|
||||
validatedData.embeddingDimension !== undefined
|
||||
) {
|
||||
updateData.embeddingModel = 'text-embedding-3-small'
|
||||
updateData.embeddingDimension = 1536
|
||||
}
|
||||
|
||||
if (validatedData.chunkingConfig !== undefined)
|
||||
updateData.chunkingConfig = validatedData.chunkingConfig
|
||||
|
||||
await db.update(knowledgeBase).set(updateData).where(eq(knowledgeBase.id, id))
|
||||
|
||||
// Fetch the updated knowledge base
|
||||
const updatedKnowledgeBase = await db
|
||||
.select()
|
||||
.from(knowledgeBase)
|
||||
.where(eq(knowledgeBase.id, id))
|
||||
.limit(1)
|
||||
|
||||
logger.info(`[${requestId}] Knowledge base updated: ${id} for user ${session.user.id}`)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: updatedKnowledgeBase[0],
|
||||
})
|
||||
} catch (validationError) {
|
||||
if (validationError instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid knowledge base update data`, {
|
||||
errors: validationError.errors,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: validationError.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
throw validationError
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error updating knowledge base`, error)
|
||||
return NextResponse.json({ error: 'Failed to update knowledge base' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id } = await params
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized knowledge base delete attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessCheck = await checkKnowledgeBaseAccess(id, session.user.id)
|
||||
|
||||
if (accessCheck.notFound) {
|
||||
logger.warn(`[${requestId}] Knowledge base not found: ${id}`)
|
||||
return NextResponse.json({ error: 'Knowledge base not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!accessCheck.hasAccess) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} attempted to delete unauthorized knowledge base ${id}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Soft delete by setting deletedAt timestamp
|
||||
await db
|
||||
.update(knowledgeBase)
|
||||
.set({
|
||||
deletedAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(knowledgeBase.id, id))
|
||||
|
||||
logger.info(`[${requestId}] Knowledge base deleted: ${id} for user ${session.user.id}`)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { message: 'Knowledge base deleted successfully' },
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error deleting knowledge base`, error)
|
||||
return NextResponse.json({ error: 'Failed to delete knowledge base' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
137
apps/sim/app/api/knowledge/route.ts
Normal file
137
apps/sim/app/api/knowledge/route.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { and, count, eq, isNull } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { document, knowledgeBase } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('KnowledgeBaseAPI')
|
||||
|
||||
// Schema for knowledge base creation
|
||||
const CreateKnowledgeBaseSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
description: z.string().optional(),
|
||||
workspaceId: z.string().optional(),
|
||||
embeddingModel: z.literal('text-embedding-3-small').default('text-embedding-3-small'),
|
||||
embeddingDimension: z.literal(1536).default(1536),
|
||||
chunkingConfig: z
|
||||
.object({
|
||||
maxSize: z.number().default(1024),
|
||||
minSize: z.number().default(100),
|
||||
overlap: z.number().default(200),
|
||||
})
|
||||
.default({}),
|
||||
})
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized knowledge base access attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Build where conditions
|
||||
const whereConditions = [
|
||||
eq(knowledgeBase.userId, session.user.id),
|
||||
isNull(knowledgeBase.deletedAt),
|
||||
]
|
||||
|
||||
// Get knowledge bases with document counts
|
||||
const knowledgeBasesWithCounts = await db
|
||||
.select({
|
||||
id: knowledgeBase.id,
|
||||
name: knowledgeBase.name,
|
||||
description: knowledgeBase.description,
|
||||
tokenCount: knowledgeBase.tokenCount,
|
||||
embeddingModel: knowledgeBase.embeddingModel,
|
||||
embeddingDimension: knowledgeBase.embeddingDimension,
|
||||
chunkingConfig: knowledgeBase.chunkingConfig,
|
||||
createdAt: knowledgeBase.createdAt,
|
||||
updatedAt: knowledgeBase.updatedAt,
|
||||
workspaceId: knowledgeBase.workspaceId,
|
||||
docCount: count(document.id),
|
||||
})
|
||||
.from(knowledgeBase)
|
||||
.leftJoin(
|
||||
document,
|
||||
and(eq(document.knowledgeBaseId, knowledgeBase.id), isNull(document.deletedAt))
|
||||
)
|
||||
.where(and(...whereConditions))
|
||||
.groupBy(knowledgeBase.id)
|
||||
.orderBy(knowledgeBase.createdAt)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Retrieved ${knowledgeBasesWithCounts.length} knowledge bases for user ${session.user.id}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: knowledgeBasesWithCounts,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error fetching knowledge bases`, error)
|
||||
return NextResponse.json({ error: 'Failed to fetch knowledge bases' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized knowledge base creation attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
|
||||
try {
|
||||
const validatedData = CreateKnowledgeBaseSchema.parse(body)
|
||||
|
||||
const id = crypto.randomUUID()
|
||||
const now = new Date()
|
||||
|
||||
const newKnowledgeBase = {
|
||||
id,
|
||||
userId: session.user.id,
|
||||
workspaceId: validatedData.workspaceId || null,
|
||||
name: validatedData.name,
|
||||
description: validatedData.description || null,
|
||||
tokenCount: 0,
|
||||
embeddingModel: validatedData.embeddingModel,
|
||||
embeddingDimension: validatedData.embeddingDimension,
|
||||
chunkingConfig: validatedData.chunkingConfig,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
|
||||
await db.insert(knowledgeBase).values(newKnowledgeBase)
|
||||
|
||||
logger.info(`[${requestId}] Knowledge base created: ${id} for user ${session.user.id}`)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: newKnowledgeBase,
|
||||
})
|
||||
} catch (validationError) {
|
||||
if (validationError instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid knowledge base data`, {
|
||||
errors: validationError.errors,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: validationError.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
throw validationError
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error creating knowledge base`, error)
|
||||
return NextResponse.json({ error: 'Failed to create knowledge base' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
166
apps/sim/app/api/knowledge/search/route.ts
Normal file
166
apps/sim/app/api/knowledge/search/route.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { and, eq, isNull, sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { embedding, knowledgeBase } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('VectorSearchAPI')
|
||||
|
||||
// Schema for vector search request
|
||||
const VectorSearchSchema = z.object({
|
||||
knowledgeBaseId: z.string().min(1, 'Knowledge base ID is required'),
|
||||
query: z.string().min(1, 'Search query is required'),
|
||||
topK: z.number().min(1).max(100).default(10),
|
||||
})
|
||||
|
||||
async function generateSearchEmbedding(query: string): Promise<number[]> {
|
||||
const openaiApiKey = env.OPENAI_API_KEY
|
||||
if (!openaiApiKey) {
|
||||
throw new Error('OPENAI_API_KEY not configured')
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.openai.com/v1/embeddings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${openaiApiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
input: query,
|
||||
model: 'text-embedding-3-small',
|
||||
encoding_format: 'float',
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`OpenAI API error: ${response.status} ${response.statusText} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data) || data.data.length === 0) {
|
||||
throw new Error('Invalid response format from OpenAI embeddings API')
|
||||
}
|
||||
|
||||
return data.data[0].embedding
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate search embedding:', error)
|
||||
throw new Error(
|
||||
`Embedding generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
logger.info(`[${requestId}] Processing vector search request`)
|
||||
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized vector search attempt`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
|
||||
try {
|
||||
const validatedData = VectorSearchSchema.parse(body)
|
||||
|
||||
// Verify the knowledge base exists and user has access
|
||||
const kb = await db
|
||||
.select()
|
||||
.from(knowledgeBase)
|
||||
.where(
|
||||
and(
|
||||
eq(knowledgeBase.id, validatedData.knowledgeBaseId),
|
||||
eq(knowledgeBase.userId, session.user.id),
|
||||
isNull(knowledgeBase.deletedAt)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (kb.length === 0) {
|
||||
logger.warn(
|
||||
`[${requestId}] Knowledge base not found or access denied: ${validatedData.knowledgeBaseId}`
|
||||
)
|
||||
return NextResponse.json(
|
||||
{ error: 'Knowledge base not found or access denied' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Generate embedding for the search query
|
||||
logger.info(`[${requestId}] Generating embedding for search query`)
|
||||
const queryEmbedding = await generateSearchEmbedding(validatedData.query)
|
||||
|
||||
// Perform vector similarity search using pgvector cosine similarity
|
||||
logger.info(`[${requestId}] Performing vector search with topK=${validatedData.topK}`)
|
||||
|
||||
const results = await db
|
||||
.select({
|
||||
id: embedding.id,
|
||||
content: embedding.content,
|
||||
documentId: embedding.documentId,
|
||||
chunkIndex: embedding.chunkIndex,
|
||||
metadata: embedding.metadata,
|
||||
similarity: sql<number>`1 - (${embedding.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`,
|
||||
})
|
||||
.from(embedding)
|
||||
.where(
|
||||
and(
|
||||
eq(embedding.knowledgeBaseId, validatedData.knowledgeBaseId),
|
||||
eq(embedding.enabled, true)
|
||||
)
|
||||
)
|
||||
.orderBy(sql`${embedding.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`)
|
||||
.limit(validatedData.topK)
|
||||
|
||||
logger.info(`[${requestId}] Vector search completed. Found ${results.length} results`)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
results: results.map((result) => ({
|
||||
id: result.id,
|
||||
content: result.content,
|
||||
documentId: result.documentId,
|
||||
chunkIndex: result.chunkIndex,
|
||||
metadata: result.metadata,
|
||||
similarity: result.similarity,
|
||||
})),
|
||||
query: validatedData.query,
|
||||
knowledgeBaseId: validatedData.knowledgeBaseId,
|
||||
topK: validatedData.topK,
|
||||
totalResults: results.length,
|
||||
},
|
||||
})
|
||||
} catch (validationError) {
|
||||
if (validationError instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid vector search data`, {
|
||||
errors: validationError.errors,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: validationError.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
throw validationError
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error performing vector search`, error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to perform vector search',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -236,7 +236,9 @@ export function ControlBar() {
|
||||
|
||||
// Use cache if available and not expired
|
||||
if (!forceRefresh && usageDataCache.data && cacheAge < usageDataCache.expirationMs) {
|
||||
logger.info('Using cached usage data', { cacheAge: `${Math.round(cacheAge / 1000)}s` })
|
||||
logger.info('Using cached usage data', {
|
||||
cacheAge: `${Math.round(cacheAge / 1000)}s`,
|
||||
})
|
||||
return usageDataCache.data
|
||||
}
|
||||
|
||||
@@ -658,7 +660,7 @@ export function ControlBar() {
|
||||
// size="icon"
|
||||
// onClick={handlePublishWorkflow}
|
||||
// disabled={isPublishing}
|
||||
// className={cn('hover:text-[#802FFF]', isPublished && 'text-[#802FFF]')}
|
||||
// className={cn('hover:text-[#701FFC]', isPublished && 'text-[#701FFC]')}
|
||||
// >
|
||||
// {isPublishing ? (
|
||||
// <Loader2 className="h-5 w-5 animate-spin" />
|
||||
@@ -869,13 +871,13 @@ export function ControlBar() {
|
||||
<Button
|
||||
className={cn(
|
||||
'gap-2 font-medium',
|
||||
'bg-[#802FFF] hover:bg-[#7028E6]',
|
||||
'shadow-[0_0_0_0_#802FFF] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]',
|
||||
'bg-[#701FFC] hover:bg-[#6518E6]',
|
||||
'shadow-[0_0_0_0_#701FFC] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]',
|
||||
'text-white transition-all duration-200',
|
||||
(isExecuting || isMultiRunning) &&
|
||||
!isCancelling &&
|
||||
'relative after:absolute after:inset-0 after:animate-pulse after:bg-white/20',
|
||||
'disabled:opacity-50 disabled:hover:bg-[#802FFF] disabled:hover:shadow-none',
|
||||
'disabled:opacity-50 disabled:hover:bg-[#701FFC] disabled:hover:shadow-none',
|
||||
isDebugModeEnabled || isMultiRunning
|
||||
? 'h-10 rounded px-4 py-2'
|
||||
: 'h-10 rounded-r-none border-r border-r-[#6420cc] px-4 py-2'
|
||||
@@ -939,13 +941,13 @@ export function ControlBar() {
|
||||
<Button
|
||||
className={cn(
|
||||
'px-2 font-medium',
|
||||
'bg-[#802FFF] hover:bg-[#7028E6]',
|
||||
'shadow-[0_0_0_0_#802FFF] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]',
|
||||
'bg-[#701FFC] hover:bg-[#6518E6]',
|
||||
'shadow-[0_0_0_0_#701FFC] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]',
|
||||
'text-white transition-all duration-200',
|
||||
(isExecuting || isMultiRunning) &&
|
||||
!isCancelling &&
|
||||
'relative after:absolute after:inset-0 after:animate-pulse after:bg-white/20',
|
||||
'disabled:opacity-50 disabled:hover:bg-[#802FFF] disabled:hover:shadow-none',
|
||||
'disabled:opacity-50 disabled:hover:bg-[#701FFC] disabled:hover:shadow-none',
|
||||
'h-10 rounded-l-none'
|
||||
)}
|
||||
disabled={isExecuting || isMultiRunning || isCancelling}
|
||||
|
||||
@@ -6,7 +6,7 @@ export const ParallelTool = {
|
||||
name: 'Parallel',
|
||||
description: 'Parallel Execution',
|
||||
icon: SplitIcon,
|
||||
bgColor: '#FEE12B', // Yellow color for parallel execution
|
||||
bgColor: '#8BC34A',
|
||||
data: {
|
||||
label: 'Parallel',
|
||||
parallelType: 'collection' as 'collection' | 'count',
|
||||
|
||||
@@ -13,16 +13,29 @@ const ParallelNodeStyles: React.FC = () => {
|
||||
return (
|
||||
<style jsx global>{`
|
||||
@keyframes parallel-node-pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(254, 225, 43, 0.3); }
|
||||
70% { box-shadow: 0 0 0 6px rgba(254, 225, 43, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(254, 225, 43, 0); }
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(139, 195, 74, 0.3);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 6px rgba(139, 195, 74, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(139, 195, 74, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.parallel-node-drag-over {
|
||||
animation: parallel-node-pulse 1.2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
animation: parallel-node-pulse 1.2s cubic-bezier(0.4, 0, 0.6, 1)
|
||||
infinite;
|
||||
border-style: solid !important;
|
||||
background-color: rgba(254, 225, 43, 0.08) !important;
|
||||
box-shadow: 0 0 0 8px rgba(254, 225, 43, 0.1);
|
||||
background-color: rgba(139, 195, 74, 0.08) !important;
|
||||
box-shadow: 0 0 0 8px rgba(139, 195, 74, 0.1);
|
||||
}
|
||||
|
||||
/* Make resizer handles more visible */
|
||||
.react-flow__resize-control {
|
||||
z-index: 10;
|
||||
pointer-events: all !important;
|
||||
}
|
||||
|
||||
/* Ensure parent borders are visible when hovering over resize controls */
|
||||
@@ -37,6 +50,11 @@ const ParallelNodeStyles: React.FC = () => {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
/* React Flow position transitions within parallel blocks */
|
||||
.react-flow__node[data-parent-node-id] {
|
||||
transition: transform 0.05s ease;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
/* Prevent jumpy drag behavior */
|
||||
.parallel-drop-container .react-flow__node {
|
||||
@@ -59,7 +77,7 @@ const ParallelNodeStyles: React.FC = () => {
|
||||
|
||||
/* Enhanced drag detection */
|
||||
.react-flow__node-group.dragging-over {
|
||||
background-color: rgba(254,225,43,0.05);
|
||||
background-color: rgba(139, 195, 74, 0.05);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
`}</style>
|
||||
@@ -90,7 +108,7 @@ export const ParallelNodeComponent = memo(({ data, selected, id }: NodeProps) =>
|
||||
const getNestedStyles = () => {
|
||||
// Base styles
|
||||
const styles: Record<string, string> = {
|
||||
backgroundColor: data?.state === 'valid' ? 'rgba(254, 225, 43, 0.05)' : 'transparent',
|
||||
backgroundColor: data?.state === 'valid' ? 'rgba(139, 195, 74, 0.05)' : 'transparent',
|
||||
}
|
||||
|
||||
// Apply nested styles
|
||||
@@ -117,7 +135,7 @@ export const ParallelNodeComponent = memo(({ data, selected, id }: NodeProps) =>
|
||||
'relative cursor-default select-none',
|
||||
'transition-block-bg transition-ring',
|
||||
'z-[20]',
|
||||
data?.state === 'valid' && 'bg-[rgba(254,225,43,0.05)] ring-2 ring-[#FEE12B]',
|
||||
data?.state === 'valid' && 'bg-[rgba(139,195,74,0.05)] ring-2 ring-[#8BC34A]',
|
||||
nestingLevel > 0 &&
|
||||
`border border-[0.5px] ${nestingLevel % 2 === 0 ? 'border-slate-300/60' : 'border-slate-400/60'}`
|
||||
)}
|
||||
@@ -171,7 +189,7 @@ export const ParallelNodeComponent = memo(({ data, selected, id }: NodeProps) =>
|
||||
|
||||
{/* Parallel Start Block */}
|
||||
<div
|
||||
className='-translate-y-1/2 absolute top-1/2 left-8 flex h-10 w-10 transform items-center justify-center rounded-md bg-[#FEE12B] p-2'
|
||||
className='-translate-y-1/2 absolute top-1/2 left-8 flex h-10 w-10 transform items-center justify-center rounded-md bg-[#8BC34A] p-2'
|
||||
style={{ pointerEvents: 'auto' }}
|
||||
data-parent-id={id}
|
||||
data-node-role='parallel-start'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { HelpCircle, ScrollText, Send, Settings } from 'lucide-react'
|
||||
import { HelpCircle, LibraryBig, ScrollText, Send, Settings } from 'lucide-react'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
@@ -249,7 +249,7 @@ export function Sidebar() {
|
||||
|
||||
{/* Logs and Settings Navigation - Follows workflows */}
|
||||
<div className='mt-6 flex-shrink-0'>
|
||||
<NavSection isLoading={isLoading} itemCount={2} isCollapsed={isCollapsed}>
|
||||
<NavSection isLoading={isLoading} itemCount={3} isCollapsed={isCollapsed}>
|
||||
<NavSection.Item
|
||||
icon={<ScrollText className='h-[18px] w-[18px]' />}
|
||||
href='/w/logs'
|
||||
@@ -259,6 +259,15 @@ export function Sidebar() {
|
||||
shortcutCommand={getKeyboardShortcutText('L', true, true)}
|
||||
shortcutCommandPosition='below'
|
||||
/>
|
||||
<NavSection.Item
|
||||
icon={<LibraryBig className='h-[18px] w-[18px]' />}
|
||||
href='/w/knowledge'
|
||||
label='Knowledge'
|
||||
active={pathname === '/w/knowledge'}
|
||||
isCollapsed={isCollapsed}
|
||||
shortcutCommand={getKeyboardShortcutText('K', true, true)}
|
||||
shortcutCommandPosition='below'
|
||||
/>
|
||||
<NavSection.Item
|
||||
icon={<Settings className='h-[18px] w-[18px]' />}
|
||||
onClick={() => setShowSettings(true)}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
'use client'
|
||||
|
||||
import { LibraryBig, Search } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
import { ChunkTableSkeleton } from '../../../components/skeletons/table-skeleton'
|
||||
|
||||
interface DocumentLoadingProps {
|
||||
knowledgeBaseId: string
|
||||
knowledgeBaseName: string
|
||||
documentName: string
|
||||
}
|
||||
|
||||
export function DocumentLoading({
|
||||
knowledgeBaseId,
|
||||
knowledgeBaseName,
|
||||
documentName,
|
||||
}: DocumentLoadingProps) {
|
||||
const { mode, isExpanded } = useSidebarStore()
|
||||
const isSidebarCollapsed =
|
||||
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex h-[100vh] flex-col transition-padding duration-200 ${isSidebarCollapsed ? 'pl-14' : 'pl-60'}`}
|
||||
>
|
||||
{/* Fixed Header with Breadcrumbs */}
|
||||
<div className='flex items-center gap-2 px-6 pt-[14px] pb-6'>
|
||||
<Link
|
||||
href='/w/knowledge'
|
||||
prefetch={true}
|
||||
className='group flex items-center gap-2 font-medium text-sm transition-colors hover:text-muted-foreground'
|
||||
>
|
||||
<LibraryBig className='h-[18px] w-[18px] text-muted-foreground transition-colors group-hover:text-muted-foreground/70' />
|
||||
<span>Knowledge</span>
|
||||
</Link>
|
||||
<span className='text-muted-foreground'>/</span>
|
||||
<Link
|
||||
href={`/w/knowledge/${knowledgeBaseId}`}
|
||||
className='font-medium text-sm transition-colors hover:text-muted-foreground'
|
||||
>
|
||||
{knowledgeBaseName}
|
||||
</Link>
|
||||
<span className='text-muted-foreground'>/</span>
|
||||
<span className='font-medium text-sm'>{documentName}</span>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-1 overflow-hidden'>
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Main Content */}
|
||||
<div className='flex-1 overflow-auto pt-[4px]'>
|
||||
<div className='px-6 pb-6'>
|
||||
{/* Search Section */}
|
||||
<div className='mb-4'>
|
||||
<div className='relative max-w-md'>
|
||||
<div className='relative flex items-center'>
|
||||
<Search className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-[18px] w-[18px] transform text-muted-foreground' />
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Search chunks...'
|
||||
disabled
|
||||
className='h-10 w-full rounded-md border bg-background px-9 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table container */}
|
||||
<ChunkTableSkeleton isSidebarCollapsed={isSidebarCollapsed} rowCount={8} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
interface ChunkData {
|
||||
id: string
|
||||
chunkIndex: number
|
||||
content: string
|
||||
contentLength: number
|
||||
tokenCount: number
|
||||
enabled: boolean
|
||||
startOffset: number
|
||||
endOffset: number
|
||||
overlapTokens: number
|
||||
metadata: any
|
||||
searchRank: string
|
||||
qualityScore: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
interface DocumentData {
|
||||
id: string
|
||||
knowledgeBaseId: string
|
||||
filename: string
|
||||
fileUrl: string
|
||||
fileSize: number
|
||||
mimeType: string
|
||||
fileHash?: string
|
||||
chunkCount: number
|
||||
tokenCount: number
|
||||
characterCount: number
|
||||
enabled: boolean
|
||||
uploadedAt: string
|
||||
}
|
||||
|
||||
interface EditChunkModalProps {
|
||||
chunk: ChunkData | null
|
||||
document: DocumentData | null
|
||||
knowledgeBaseId: string
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onChunkUpdate?: (updatedChunk: ChunkData) => void
|
||||
}
|
||||
|
||||
export function EditChunkModal({
|
||||
chunk,
|
||||
document,
|
||||
knowledgeBaseId,
|
||||
isOpen,
|
||||
onClose,
|
||||
onChunkUpdate,
|
||||
}: EditChunkModalProps) {
|
||||
const [editedContent, setEditedContent] = useState('')
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
// Update edited content when chunk changes
|
||||
useEffect(() => {
|
||||
if (chunk?.content) {
|
||||
setEditedContent(chunk.content)
|
||||
}
|
||||
}, [chunk?.id, chunk?.content])
|
||||
|
||||
const handleSaveContent = async () => {
|
||||
if (!chunk || !document) return
|
||||
|
||||
try {
|
||||
setIsSaving(true)
|
||||
|
||||
const response = await fetch(
|
||||
`/api/knowledge/${knowledgeBaseId}/documents/${document.id}/chunks/${chunk.id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: editedContent,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update chunk')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success && onChunkUpdate) {
|
||||
onChunkUpdate(result.data)
|
||||
onClose()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating chunk:', error)
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setEditedContent(chunk?.content || '')
|
||||
onClose()
|
||||
}
|
||||
|
||||
if (!chunk || !document) return null
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent
|
||||
className='flex h-[74vh] flex-col gap-0 overflow-hidden p-0 sm:max-w-[600px]'
|
||||
hideCloseButton
|
||||
>
|
||||
<DialogHeader className='flex-shrink-0 border-b px-6 py-4'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<DialogTitle className='font-medium text-lg'>Edit Chunk Content</DialogTitle>
|
||||
<Button variant='ghost' size='icon' className='h-8 w-8 p-0' onClick={onClose}>
|
||||
<X className='h-4 w-4' />
|
||||
<span className='sr-only'>Close</span>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
<form className='flex h-full flex-col'>
|
||||
{/* Scrollable Content */}
|
||||
<div className='scrollbar-thin scrollbar-thumb-muted-foreground/20 hover:scrollbar-thumb-muted-foreground/25 scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto px-6'>
|
||||
<div className='py-4'>
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor='content'>Content</Label>
|
||||
<Textarea
|
||||
id='content'
|
||||
value={editedContent}
|
||||
onChange={(e) => setEditedContent(e.target.value)}
|
||||
placeholder='Enter chunk content...'
|
||||
className='min-h-[400px] resize-none'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fixed Footer */}
|
||||
<div className='mt-auto border-t px-6 pt-4 pb-6'>
|
||||
<div className='flex justify-between'>
|
||||
<Button variant='outline' onClick={handleCancel} type='button' disabled={isSaving}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
onClick={handleSaveContent}
|
||||
disabled={isSaving || editedContent === chunk.content}
|
||||
className='bg-[#701FFC] font-[480] text-primary-foreground shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]'
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
602
apps/sim/app/w/knowledge/[id]/[documentId]/document.tsx
Normal file
602
apps/sim/app/w/knowledge/[id]/[documentId]/document.tsx
Normal file
@@ -0,0 +1,602 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Circle, CircleOff, FileText, LibraryBig, Search, Trash2, X } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
import { DocumentLoading } from './components/document-loading'
|
||||
import { EditChunkModal } from './components/edit-chunk-modal'
|
||||
|
||||
interface DocumentProps {
|
||||
knowledgeBaseId: string
|
||||
documentId: string
|
||||
knowledgeBaseName: string
|
||||
documentName: string
|
||||
}
|
||||
|
||||
interface DocumentData {
|
||||
id: string
|
||||
knowledgeBaseId: string
|
||||
filename: string
|
||||
fileUrl: string
|
||||
fileSize: number
|
||||
mimeType: string
|
||||
fileHash?: string
|
||||
chunkCount: number
|
||||
tokenCount: number
|
||||
characterCount: number
|
||||
enabled: boolean
|
||||
uploadedAt: string
|
||||
}
|
||||
|
||||
interface ChunkData {
|
||||
id: string
|
||||
chunkIndex: number
|
||||
content: string
|
||||
contentLength: number
|
||||
tokenCount: number
|
||||
enabled: boolean
|
||||
startOffset: number
|
||||
endOffset: number
|
||||
overlapTokens: number
|
||||
metadata: any
|
||||
searchRank: string
|
||||
qualityScore: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
interface ChunksResponse {
|
||||
success: boolean
|
||||
data: ChunkData[]
|
||||
error?: string
|
||||
pagination: {
|
||||
total: number
|
||||
limit: number
|
||||
offset: number
|
||||
hasMore: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get status badge styles
|
||||
function getStatusBadgeStyles(enabled: boolean) {
|
||||
return enabled
|
||||
? 'bg-green-100 dark:bg-green-950/40 text-green-700 dark:text-green-400'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-400'
|
||||
}
|
||||
|
||||
// Helper function to truncate content for display
|
||||
function truncateContent(content: string, maxLength = 150): string {
|
||||
if (content.length <= maxLength) return content
|
||||
return `${content.substring(0, maxLength)}...`
|
||||
}
|
||||
|
||||
export function Document({
|
||||
knowledgeBaseId,
|
||||
documentId,
|
||||
knowledgeBaseName,
|
||||
documentName,
|
||||
}: DocumentProps) {
|
||||
const { mode, isExpanded } = useSidebarStore()
|
||||
const isSidebarCollapsed =
|
||||
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [selectedChunks, setSelectedChunks] = useState<Set<string>>(new Set())
|
||||
const [selectedChunk, setSelectedChunk] = useState<ChunkData | null>(null)
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
|
||||
const [document, setDocument] = useState<DocumentData | null>(null)
|
||||
const [chunks, setChunks] = useState<ChunkData[]>([])
|
||||
const [isLoadingDocument, setIsLoadingDocument] = useState(true)
|
||||
const [isLoadingChunks, setIsLoadingChunks] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [pagination, setPagination] = useState({
|
||||
total: 0,
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
hasMore: false,
|
||||
})
|
||||
|
||||
// Fetch document data
|
||||
useEffect(() => {
|
||||
const fetchDocument = async () => {
|
||||
try {
|
||||
setIsLoadingDocument(true)
|
||||
setError(null)
|
||||
|
||||
const response = await fetch(`/api/knowledge/${knowledgeBaseId}/documents/${documentId}`)
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error('Document not found')
|
||||
}
|
||||
throw new Error(`Failed to fetch document: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
setDocument(result.data)
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to fetch document')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching document:', err)
|
||||
setError(err instanceof Error ? err.message : 'An error occurred')
|
||||
} finally {
|
||||
setIsLoadingDocument(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (knowledgeBaseId && documentId) {
|
||||
fetchDocument()
|
||||
}
|
||||
}, [knowledgeBaseId, documentId])
|
||||
|
||||
// Fetch chunks data
|
||||
const fetchChunks = useCallback(
|
||||
async (search?: string, offset = 0) => {
|
||||
try {
|
||||
setIsLoadingChunks(true)
|
||||
|
||||
const params = new URLSearchParams({
|
||||
limit: pagination.limit.toString(),
|
||||
offset: offset.toString(),
|
||||
})
|
||||
|
||||
if (search) params.append('search', search)
|
||||
|
||||
const response = await fetch(
|
||||
`/api/knowledge/${knowledgeBaseId}/documents/${documentId}/chunks?${params}`
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch chunks: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const result: ChunksResponse = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
if (offset === 0) {
|
||||
setChunks(result.data)
|
||||
} else {
|
||||
setChunks((prev) => [...prev, ...result.data])
|
||||
}
|
||||
setPagination(result.pagination)
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to fetch chunks')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching chunks:', err)
|
||||
setError(err instanceof Error ? err.message : 'An error occurred')
|
||||
} finally {
|
||||
setIsLoadingChunks(false)
|
||||
}
|
||||
},
|
||||
[knowledgeBaseId, documentId, pagination.limit]
|
||||
)
|
||||
|
||||
// Initial fetch and refetch on filter changes
|
||||
useEffect(() => {
|
||||
if (document) {
|
||||
fetchChunks(searchQuery, 0)
|
||||
}
|
||||
}, [document, searchQuery, fetchChunks])
|
||||
|
||||
const handleChunkClick = (chunk: ChunkData) => {
|
||||
setSelectedChunk(chunk)
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setIsModalOpen(false)
|
||||
setSelectedChunk(null)
|
||||
}
|
||||
|
||||
const handleToggleEnabled = async (chunkId: string) => {
|
||||
const chunk = chunks.find((c) => c.id === chunkId)
|
||||
if (!chunk) return
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/knowledge/${knowledgeBaseId}/documents/${documentId}/chunks/${chunkId}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
enabled: !chunk.enabled,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update chunk')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
setChunks((prev) => prev.map((c) => (c.id === chunkId ? { ...c, enabled: !c.enabled } : c)))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error updating chunk:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteChunk = async (chunkId: string) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/knowledge/${knowledgeBaseId}/documents/${documentId}/chunks/${chunkId}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete chunk')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
setChunks((prev) => prev.filter((c) => c.id !== chunkId))
|
||||
setSelectedChunks((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.delete(chunkId)
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error deleting chunk:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectChunk = (chunkId: string, checked: boolean) => {
|
||||
setSelectedChunks((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
if (checked) {
|
||||
newSet.add(chunkId)
|
||||
} else {
|
||||
newSet.delete(chunkId)
|
||||
}
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
if (checked) {
|
||||
setSelectedChunks(new Set(chunks.map((chunk) => chunk.id)))
|
||||
} else {
|
||||
setSelectedChunks(new Set())
|
||||
}
|
||||
}
|
||||
|
||||
const isAllSelected = chunks.length > 0 && selectedChunks.size === chunks.length
|
||||
const isIndeterminate = selectedChunks.size > 0 && selectedChunks.size < chunks.length
|
||||
|
||||
// Show loading component while data is being fetched
|
||||
if (isLoadingDocument || isLoadingChunks) {
|
||||
return (
|
||||
<DocumentLoading
|
||||
knowledgeBaseId={knowledgeBaseId}
|
||||
knowledgeBaseName={knowledgeBaseName}
|
||||
documentName={documentName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Show error state for document fetch
|
||||
if (error && isLoadingDocument) {
|
||||
return (
|
||||
<div
|
||||
className={`flex h-[100vh] flex-col transition-padding duration-200 ${isSidebarCollapsed ? 'pl-14' : 'pl-60'}`}
|
||||
>
|
||||
<div className='flex items-center gap-2 px-6 pt-[14px] pb-6'>
|
||||
<Link
|
||||
href='/w/knowledge'
|
||||
prefetch={true}
|
||||
className='group flex items-center gap-2 font-medium text-sm transition-colors hover:text-muted-foreground'
|
||||
>
|
||||
<LibraryBig className='h-[18px] w-[18px] text-muted-foreground transition-colors group-hover:text-muted-foreground/70' />
|
||||
<span>Knowledge</span>
|
||||
</Link>
|
||||
<span className='text-muted-foreground'>/</span>
|
||||
<Link
|
||||
href={`/w/knowledge/${knowledgeBaseId}`}
|
||||
className='font-medium text-sm transition-colors hover:text-muted-foreground'
|
||||
>
|
||||
{knowledgeBaseName}
|
||||
</Link>
|
||||
<span className='text-muted-foreground'>/</span>
|
||||
<span className='font-medium text-sm'>Error</span>
|
||||
</div>
|
||||
<div className='flex flex-1 items-center justify-center'>
|
||||
<div className='text-center'>
|
||||
<p className='mb-2 text-red-600 text-sm'>Error: {error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className='text-blue-600 text-sm underline hover:text-blue-800'
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex h-[100vh] flex-col transition-padding duration-200 ${isSidebarCollapsed ? 'pl-14' : 'pl-60'}`}
|
||||
>
|
||||
{/* Fixed Header with Breadcrumbs */}
|
||||
<div className='flex items-center gap-2 px-6 pt-[14px] pb-6'>
|
||||
<Link
|
||||
href='/w/knowledge'
|
||||
prefetch={true}
|
||||
className='group flex items-center gap-2 font-medium text-sm transition-colors hover:text-muted-foreground'
|
||||
>
|
||||
<LibraryBig className='h-[18px] w-[18px] text-muted-foreground transition-colors group-hover:text-muted-foreground/70' />
|
||||
<span>Knowledge</span>
|
||||
</Link>
|
||||
<span className='text-muted-foreground'>/</span>
|
||||
<Link
|
||||
href={`/w/knowledge/${knowledgeBaseId}`}
|
||||
className='font-medium text-sm transition-colors hover:text-muted-foreground'
|
||||
>
|
||||
{knowledgeBaseName}
|
||||
</Link>
|
||||
<span className='text-muted-foreground'>/</span>
|
||||
<span className='font-medium text-sm'>{documentName}</span>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-1 overflow-hidden'>
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Main Content */}
|
||||
<div className='flex-1 overflow-auto pt-[4px]'>
|
||||
<div className='px-6 pb-6'>
|
||||
{/* Search Section */}
|
||||
<div className='mb-4'>
|
||||
<div className='relative max-w-md'>
|
||||
<div className='relative flex items-center'>
|
||||
<Search className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-[18px] w-[18px] transform text-muted-foreground' />
|
||||
<input
|
||||
type='text'
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder='Search chunks...'
|
||||
className='h-10 w-full rounded-md border bg-background px-9 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => setSearchQuery('')}
|
||||
className='-translate-y-1/2 absolute top-1/2 right-3 transform text-muted-foreground hover:text-foreground'
|
||||
>
|
||||
<X className='h-[18px] w-[18px]' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error State for chunks */}
|
||||
{error && !isLoadingDocument && (
|
||||
<div className='mb-4 rounded-md border border-red-200 bg-red-50 p-4'>
|
||||
<p className='text-red-800 text-sm'>Error loading chunks: {error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Table container */}
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Table header - fixed */}
|
||||
<div className='sticky top-0 z-10 border-b bg-background'>
|
||||
<table className='w-full table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[5%]' />
|
||||
<col className='w-[8%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[57%]' : 'w-[55%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[12%]' />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<Checkbox
|
||||
checked={isAllSelected}
|
||||
onCheckedChange={handleSelectAll}
|
||||
aria-label='Select all chunks'
|
||||
className='h-3.5 w-3.5 border-gray-300 focus-visible:ring-[#701FFC]/20 data-[state=checked]:border-[#701FFC] data-[state=checked]:bg-[#701FFC] [&>*]:h-3 [&>*]:w-3'
|
||||
/>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Index</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>
|
||||
Content
|
||||
</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Tokens</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Status</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>
|
||||
Actions
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Table body - scrollable */}
|
||||
<div className='flex-1 overflow-auto'>
|
||||
<table className='w-full table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[5%]' />
|
||||
<col className='w-[8%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[57%]' : 'w-[55%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[12%]' />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{chunks.length === 0 ? (
|
||||
<tr className='border-b transition-colors hover:bg-accent/30'>
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-3.5 w-3.5' />
|
||||
</td>
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<FileText className='h-5 w-5 text-muted-foreground' />
|
||||
<span className='text-muted-foreground text-sm italic'>
|
||||
No chunks found
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
chunks.map((chunk) => (
|
||||
<tr
|
||||
key={chunk.id}
|
||||
className='cursor-pointer border-b transition-colors hover:bg-accent/30'
|
||||
onClick={() => handleChunkClick(chunk)}
|
||||
>
|
||||
{/* Select column */}
|
||||
<td className='px-4 py-3'>
|
||||
<Checkbox
|
||||
checked={selectedChunks.has(chunk.id)}
|
||||
onCheckedChange={(checked) =>
|
||||
handleSelectChunk(chunk.id, checked as boolean)
|
||||
}
|
||||
aria-label={`Select chunk ${chunk.chunkIndex}`}
|
||||
className='h-3.5 w-3.5 border-gray-300 focus-visible:ring-[#701FFC]/20 data-[state=checked]:border-[#701FFC] data-[state=checked]:bg-[#701FFC] [&>*]:h-3 [&>*]:w-3'
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</td>
|
||||
|
||||
{/* Index column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='font-mono text-sm'>{chunk.chunkIndex}</div>
|
||||
</td>
|
||||
|
||||
{/* Content column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-sm' title={chunk.content}>
|
||||
{truncateContent(chunk.content)}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Tokens column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-xs'>
|
||||
{chunk.tokenCount > 1000
|
||||
? `${(chunk.tokenCount / 1000).toFixed(1)}k`
|
||||
: chunk.tokenCount}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div
|
||||
className={`inline-flex items-center justify-center rounded-md px-2 py-1 text-xs ${getStatusBadgeStyles(chunk.enabled)}`}
|
||||
>
|
||||
<span className='font-medium'>
|
||||
{chunk.enabled ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Actions column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleToggleEnabled(chunk.id)
|
||||
}}
|
||||
className='h-8 w-8 p-0 text-gray-500 hover:text-gray-700'
|
||||
>
|
||||
{chunk.enabled ? (
|
||||
<Circle className='h-4 w-4' />
|
||||
) : (
|
||||
<CircleOff className='h-4 w-4' />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>
|
||||
{chunk.enabled ? 'Disable Chunk' : 'Enable Chunk'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleDeleteChunk(chunk.id)
|
||||
}}
|
||||
className='h-8 w-8 p-0 text-gray-500 hover:text-red-600'
|
||||
>
|
||||
<Trash2 className='h-4 w-4' />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>Delete Chunk</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Edit Chunk Modal */}
|
||||
<EditChunkModal
|
||||
chunk={selectedChunk}
|
||||
document={document}
|
||||
knowledgeBaseId={knowledgeBaseId}
|
||||
isOpen={isModalOpen}
|
||||
onClose={handleCloseModal}
|
||||
onChunkUpdate={(updatedChunk: ChunkData) => {
|
||||
setChunks((prev) => prev.map((c) => (c.id === updatedChunk.id ? updatedChunk : c)))
|
||||
setSelectedChunk(updatedChunk)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
26
apps/sim/app/w/knowledge/[id]/[documentId]/page.tsx
Normal file
26
apps/sim/app/w/knowledge/[id]/[documentId]/page.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Document } from './document'
|
||||
|
||||
interface DocumentPageProps {
|
||||
params: Promise<{
|
||||
id: string
|
||||
documentId: string
|
||||
}>
|
||||
searchParams: Promise<{
|
||||
kbName?: string
|
||||
docName?: string
|
||||
}>
|
||||
}
|
||||
|
||||
export default async function DocumentChunksPage({ params, searchParams }: DocumentPageProps) {
|
||||
const { id, documentId } = await params
|
||||
const { kbName, docName } = await searchParams
|
||||
|
||||
return (
|
||||
<Document
|
||||
knowledgeBaseId={id}
|
||||
documentId={documentId}
|
||||
knowledgeBaseName={kbName || 'Knowledge Base'}
|
||||
documentName={docName || 'Document'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
623
apps/sim/app/w/knowledge/[id]/base.tsx
Normal file
623
apps/sim/app/w/knowledge/[id]/base.tsx
Normal file
@@ -0,0 +1,623 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { format } from 'date-fns'
|
||||
import { Circle, CircleOff, FileText, LibraryBig, Plus, Search, Trash2, X } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { getDocumentIcon } from '@/app/w/knowledge/components/icons/document-icons'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
import { KnowledgeBaseLoading } from './components/knowledge-base-loading'
|
||||
|
||||
interface KnowledgeBaseProps {
|
||||
id: string
|
||||
knowledgeBaseName?: string
|
||||
}
|
||||
|
||||
interface KnowledgeBaseData {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
tokenCount: number
|
||||
embeddingModel: string
|
||||
embeddingDimension: number
|
||||
chunkingConfig: any
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
workspaceId?: string
|
||||
}
|
||||
|
||||
interface DocumentData {
|
||||
id: string
|
||||
knowledgeBaseId: string
|
||||
filename: string
|
||||
fileUrl: string
|
||||
fileSize: number
|
||||
mimeType: string
|
||||
fileHash?: string
|
||||
chunkCount: number
|
||||
tokenCount: number
|
||||
characterCount: number
|
||||
enabled: boolean
|
||||
uploadedAt: string
|
||||
}
|
||||
|
||||
// Helper function to get file icon based on mime type
|
||||
function getFileIcon(mimeType: string, filename: string) {
|
||||
const IconComponent = getDocumentIcon(mimeType, filename)
|
||||
return <IconComponent className='h-6 w-5' />
|
||||
}
|
||||
|
||||
// Helper function to format file size
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
// Helper function to get status badge styles
|
||||
function getStatusBadgeStyles(enabled: boolean) {
|
||||
return enabled
|
||||
? 'bg-green-100 dark:bg-green-950/40 text-green-700 dark:text-green-400'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-400'
|
||||
}
|
||||
|
||||
export function KnowledgeBase({
|
||||
id,
|
||||
knowledgeBaseName: passedKnowledgeBaseName,
|
||||
}: KnowledgeBaseProps) {
|
||||
const { mode, isExpanded } = useSidebarStore()
|
||||
const isSidebarCollapsed =
|
||||
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [selectedDocuments, setSelectedDocuments] = useState<Set<string>>(new Set())
|
||||
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseData | null>(null)
|
||||
const [documents, setDocuments] = useState<DocumentData[]>([])
|
||||
const [isLoadingKnowledgeBase, setIsLoadingKnowledgeBase] = useState(true)
|
||||
const [isLoadingDocuments, setIsLoadingDocuments] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const router = useRouter()
|
||||
|
||||
// Get the knowledge base name for navigation - use passed name first, then fetched name
|
||||
const knowledgeBaseName = knowledgeBase?.name || passedKnowledgeBaseName || 'Knowledge Base'
|
||||
|
||||
// Fetch knowledge base data
|
||||
useEffect(() => {
|
||||
const fetchKnowledgeBase = async () => {
|
||||
try {
|
||||
setIsLoadingKnowledgeBase(true)
|
||||
setError(null)
|
||||
|
||||
const response = await fetch(`/api/knowledge/${id}`)
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error('Knowledge base not found')
|
||||
}
|
||||
throw new Error(`Failed to fetch knowledge base: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
setKnowledgeBase(result.data)
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to fetch knowledge base')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching knowledge base:', err)
|
||||
setError(err instanceof Error ? err.message : 'An error occurred')
|
||||
} finally {
|
||||
setIsLoadingKnowledgeBase(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (id) {
|
||||
fetchKnowledgeBase()
|
||||
}
|
||||
}, [id])
|
||||
|
||||
// Fetch documents data
|
||||
useEffect(() => {
|
||||
const fetchDocuments = async () => {
|
||||
try {
|
||||
setIsLoadingDocuments(true)
|
||||
|
||||
const response = await fetch(`/api/knowledge/${id}/documents`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch documents: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
setDocuments(result.data)
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to fetch documents')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching documents:', err)
|
||||
// Don't set error here since we already have the knowledge base data
|
||||
setDocuments([])
|
||||
} finally {
|
||||
setIsLoadingDocuments(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (id && knowledgeBase) {
|
||||
fetchDocuments()
|
||||
}
|
||||
}, [id, knowledgeBase])
|
||||
|
||||
// Filter documents based on search query
|
||||
const filteredDocuments = documents.filter((doc) =>
|
||||
doc.filename.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
|
||||
const handleToggleEnabled = async (docId: string) => {
|
||||
const document = documents.find((doc) => doc.id === docId)
|
||||
if (!document) return
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/knowledge/${id}/documents/${docId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
enabled: !document.enabled,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update document')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
setDocuments((prev) =>
|
||||
prev.map((doc) => (doc.id === docId ? { ...doc, enabled: !doc.enabled } : doc))
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error updating document:', err)
|
||||
// TODO: Show toast notification for error
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteDocument = async (docId: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/knowledge/${id}/documents/${docId}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete document')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
setDocuments((prev) => prev.filter((doc) => doc.id !== docId))
|
||||
setSelectedDocuments((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.delete(docId)
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error deleting document:', err)
|
||||
// TODO: Show toast notification for error
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectDocument = (docId: string, checked: boolean) => {
|
||||
setSelectedDocuments((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
if (checked) {
|
||||
newSet.add(docId)
|
||||
} else {
|
||||
newSet.delete(docId)
|
||||
}
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
if (checked) {
|
||||
setSelectedDocuments(new Set(filteredDocuments.map((doc) => doc.id)))
|
||||
} else {
|
||||
setSelectedDocuments(new Set())
|
||||
}
|
||||
}
|
||||
|
||||
const isAllSelected =
|
||||
filteredDocuments.length > 0 && selectedDocuments.size === filteredDocuments.length
|
||||
|
||||
const handleDocumentClick = (docId: string) => {
|
||||
// Find the document to get its filename
|
||||
const document = documents.find((doc) => doc.id === docId)
|
||||
const params = new URLSearchParams({
|
||||
kbName: knowledgeBaseName, // Use the instantly available name
|
||||
docName: document?.filename || 'Document',
|
||||
})
|
||||
router.push(`/w/knowledge/${id}/${docId}?${params.toString()}`)
|
||||
}
|
||||
|
||||
// Show loading component while data is being fetched
|
||||
if (isLoadingKnowledgeBase || isLoadingDocuments) {
|
||||
return <KnowledgeBaseLoading knowledgeBaseName={knowledgeBaseName} />
|
||||
}
|
||||
|
||||
// Show error state for knowledge base fetch
|
||||
if (error && isLoadingKnowledgeBase) {
|
||||
return (
|
||||
<div
|
||||
className={`flex h-[100vh] flex-col transition-padding duration-200 ${isSidebarCollapsed ? 'pl-14' : 'pl-60'}`}
|
||||
>
|
||||
<div className='flex items-center gap-2 px-6 pt-[14px] pb-6'>
|
||||
<Link
|
||||
href='/w/knowledge'
|
||||
prefetch={true}
|
||||
className='group flex items-center gap-2 font-medium text-sm transition-colors hover:text-muted-foreground'
|
||||
>
|
||||
<LibraryBig className='h-[18px] w-[18px] text-muted-foreground transition-colors group-hover:text-muted-foreground/70' />
|
||||
<span>Knowledge</span>
|
||||
</Link>
|
||||
<span className='text-muted-foreground'>/</span>
|
||||
<span className='font-medium text-sm'>Error</span>
|
||||
</div>
|
||||
<div className='flex flex-1 items-center justify-center'>
|
||||
<div className='text-center'>
|
||||
<p className='mb-2 text-red-600 text-sm'>Error: {error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className='text-blue-600 text-sm underline hover:text-blue-800'
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex h-[100vh] flex-col transition-padding duration-200 ${isSidebarCollapsed ? 'pl-14' : 'pl-60'}`}
|
||||
>
|
||||
{/* Fixed Header with Breadcrumbs */}
|
||||
<div className='flex items-center gap-2 px-6 pt-[14px] pb-6'>
|
||||
<Link
|
||||
href='/w/knowledge'
|
||||
prefetch={true}
|
||||
className='group flex items-center gap-2 font-medium text-sm transition-colors hover:text-muted-foreground'
|
||||
>
|
||||
<LibraryBig className='h-[18px] w-[18px] text-muted-foreground transition-colors group-hover:text-muted-foreground/70' />
|
||||
<span>Knowledge</span>
|
||||
</Link>
|
||||
<span className='text-muted-foreground'>/</span>
|
||||
<span className='font-medium text-sm'>{knowledgeBaseName}</span>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-1 overflow-hidden'>
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Main Content */}
|
||||
<div className='flex-1 overflow-auto pt-[4px]'>
|
||||
<div className='px-6 pb-6'>
|
||||
{/* Search and Create Section */}
|
||||
<div className='mb-4 flex items-center justify-between'>
|
||||
<div className='relative max-w-md flex-1'>
|
||||
<div className='relative flex items-center'>
|
||||
<Search className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-[18px] w-[18px] transform text-muted-foreground' />
|
||||
<input
|
||||
type='text'
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder='Search documents...'
|
||||
className='h-10 w-full rounded-md border bg-background px-9 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => setSearchQuery('')}
|
||||
className='-translate-y-1/2 absolute top-1/2 right-3 transform text-muted-foreground hover:text-foreground'
|
||||
>
|
||||
<X className='h-[18px] w-[18px]' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <button className='flex items-center gap-1 rounded-md bg-[#701FFC] px-3 py-[7px] font-[480] text-primary-foreground text-sm shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]'>
|
||||
<Plus className='h-4 w-4 font-[480]' />
|
||||
<span>Add Document</span>
|
||||
</button> */}
|
||||
</div>
|
||||
|
||||
{/* Error State for documents */}
|
||||
{error && !isLoadingKnowledgeBase && (
|
||||
<div className='mb-4 rounded-md border border-red-200 bg-red-50 p-4'>
|
||||
<p className='text-red-800 text-sm'>Error loading documents: {error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Table container */}
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Table header - fixed */}
|
||||
<div className='sticky top-0 z-10 border-b bg-background'>
|
||||
<table className='w-full table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[5%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[18%]' : 'w-[20%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className='hidden w-[8%] lg:table-column' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[22%]' : 'w-[20%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[16%]' />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<Checkbox
|
||||
checked={isAllSelected}
|
||||
onCheckedChange={handleSelectAll}
|
||||
aria-label='Select all documents'
|
||||
className='h-3.5 w-3.5 border-gray-300 focus-visible:ring-[#701FFC]/20 data-[state=checked]:border-[#701FFC] data-[state=checked]:bg-[#701FFC] [&>*]:h-3 [&>*]:w-3'
|
||||
/>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Name</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Size</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Tokens</span>
|
||||
</th>
|
||||
<th className='hidden px-4 pt-2 pb-3 text-left font-medium lg:table-cell'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Chunks</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>
|
||||
Uploaded
|
||||
</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Status</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>
|
||||
Actions
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Table body - scrollable */}
|
||||
<div className='flex-1 overflow-auto'>
|
||||
<table className='w-full table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[5%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[18%]' : 'w-[20%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className='hidden w-[8%] lg:table-column' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[22%]' : 'w-[20%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[16%]' />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{filteredDocuments.length === 0 ? (
|
||||
<tr className='border-b transition-colors hover:bg-accent/30'>
|
||||
{/* Select column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-3.5 w-3.5' />
|
||||
</td>
|
||||
|
||||
{/* Name column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<FileText className='h-6 w-5 text-muted-foreground' />
|
||||
<span className='text-muted-foreground text-sm italic'>
|
||||
{documents.length === 0
|
||||
? 'No documents yet'
|
||||
: 'No documents match your search'}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Size column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
|
||||
{/* Tokens column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
|
||||
{/* Chunks column - hidden on small screens */}
|
||||
<td className='hidden px-4 py-3 lg:table-cell'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
|
||||
{/* Upload Time column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
|
||||
{/* Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
|
||||
{/* Actions column */}
|
||||
<td className='px-4 py-3'>
|
||||
{documents.length === 0 && (
|
||||
<button
|
||||
onClick={() => {
|
||||
// TODO: Open add document modal when implemented
|
||||
console.log('Add document clicked')
|
||||
}}
|
||||
className='inline-flex items-center gap-1 rounded-md bg-[#701FFC] px-2 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-[#6518E6]'
|
||||
>
|
||||
<Plus className='h-3 w-3' />
|
||||
<span>Add Document</span>
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
filteredDocuments.map((doc, index) => (
|
||||
<tr
|
||||
key={doc.id}
|
||||
className='cursor-pointer border-b transition-colors hover:bg-accent/30'
|
||||
onClick={() => handleDocumentClick(doc.id)}
|
||||
>
|
||||
{/* Select column */}
|
||||
<td className='px-4 py-3'>
|
||||
<Checkbox
|
||||
checked={selectedDocuments.has(doc.id)}
|
||||
onCheckedChange={(checked) =>
|
||||
handleSelectDocument(doc.id, checked as boolean)
|
||||
}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={`Select ${doc.filename}`}
|
||||
className='h-3.5 w-3.5 border-gray-300 focus-visible:ring-[#701FFC]/20 data-[state=checked]:border-[#701FFC] data-[state=checked]:bg-[#701FFC] [&>*]:h-3 [&>*]:w-3'
|
||||
/>
|
||||
</td>
|
||||
|
||||
{/* Name column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
{getFileIcon(doc.mimeType, doc.filename)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className='block truncate text-sm' title={doc.filename}>
|
||||
{doc.filename}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>{doc.filename}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Size column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{formatFileSize(doc.fileSize)}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Tokens column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-xs'>
|
||||
{doc.tokenCount > 1000
|
||||
? `${(doc.tokenCount / 1000).toFixed(1)}k`
|
||||
: doc.tokenCount}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Chunks column - hidden on small screens */}
|
||||
<td className='hidden px-4 py-3 lg:table-cell'>
|
||||
<div className='text-muted-foreground text-xs'>{doc.chunkCount}</div>
|
||||
</td>
|
||||
|
||||
{/* Upload Time column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex flex-col justify-center'>
|
||||
<div className='flex items-center font-medium text-xs'>
|
||||
<span>{format(new Date(doc.uploadedAt), 'h:mm a')}</span>
|
||||
<span className='mx-1.5 hidden text-muted-foreground xl:inline'>
|
||||
•
|
||||
</span>
|
||||
<span className='hidden text-muted-foreground xl:inline'>
|
||||
{format(new Date(doc.uploadedAt), 'MMM d, yyyy')}
|
||||
</span>
|
||||
</div>
|
||||
<div className='mt-0.5 text-muted-foreground text-xs lg:hidden'>
|
||||
{format(new Date(doc.uploadedAt), 'MMM d')}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div
|
||||
className={`inline-flex items-center justify-center rounded-md px-2 py-1 text-xs ${getStatusBadgeStyles(doc.enabled)}`}
|
||||
>
|
||||
<span className='font-medium'>
|
||||
{doc.enabled ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Actions column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleToggleEnabled(doc.id)
|
||||
}}
|
||||
className='h-8 w-8 p-0 text-gray-500 hover:text-gray-700'
|
||||
>
|
||||
{doc.enabled ? (
|
||||
<Circle className='h-4 w-4' />
|
||||
) : (
|
||||
<CircleOff className='h-4 w-4' />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>
|
||||
{doc.enabled ? 'Disable Document' : 'Enable Document'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleDeleteDocument(doc.id)
|
||||
}}
|
||||
className='h-8 w-8 p-0 text-gray-500 hover:text-red-600'
|
||||
>
|
||||
<Trash2 className='h-4 w-4' />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>Delete Document</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
'use client'
|
||||
|
||||
import { LibraryBig, Search } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
import { DocumentTableSkeleton } from '../../components/skeletons/table-skeleton'
|
||||
|
||||
interface KnowledgeBaseLoadingProps {
|
||||
knowledgeBaseName: string
|
||||
}
|
||||
|
||||
export function KnowledgeBaseLoading({ knowledgeBaseName }: KnowledgeBaseLoadingProps) {
|
||||
const { mode, isExpanded } = useSidebarStore()
|
||||
const isSidebarCollapsed =
|
||||
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex h-[100vh] flex-col transition-padding duration-200 ${isSidebarCollapsed ? 'pl-14' : 'pl-60'}`}
|
||||
>
|
||||
{/* Fixed Header with Breadcrumbs */}
|
||||
<div className='flex items-center gap-2 px-6 pt-[14px] pb-6'>
|
||||
<Link
|
||||
href='/w/knowledge'
|
||||
prefetch={true}
|
||||
className='group flex items-center gap-2 font-medium text-sm transition-colors hover:text-muted-foreground'
|
||||
>
|
||||
<LibraryBig className='h-[18px] w-[18px] text-muted-foreground transition-colors group-hover:text-muted-foreground/70' />
|
||||
<span>Knowledge</span>
|
||||
</Link>
|
||||
<span className='text-muted-foreground'>/</span>
|
||||
<span className='font-medium text-sm'>{knowledgeBaseName}</span>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-1 overflow-hidden'>
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Main Content */}
|
||||
<div className='flex-1 overflow-auto pt-[4px]'>
|
||||
<div className='px-6 pb-6'>
|
||||
{/* Search and Create Section */}
|
||||
<div className='mb-4 flex items-center justify-between'>
|
||||
<div className='relative max-w-md flex-1'>
|
||||
<div className='relative flex items-center'>
|
||||
<Search className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-[18px] w-[18px] transform text-muted-foreground' />
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Search documents...'
|
||||
disabled
|
||||
className='h-10 w-full rounded-md border bg-background px-9 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <button
|
||||
disabled
|
||||
className='flex items-center gap-1 rounded-md bg-[#701FFC] px-3 py-[7px] font-[480] text-primary-foreground text-sm shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)] disabled:opacity-50'
|
||||
>
|
||||
<Plus className='h-4 w-4 font-[480]' />
|
||||
<span>Add Document</span>
|
||||
</button> */}
|
||||
</div>
|
||||
|
||||
{/* Table container */}
|
||||
<DocumentTableSkeleton isSidebarCollapsed={isSidebarCollapsed} rowCount={8} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
17
apps/sim/app/w/knowledge/[id]/page.tsx
Normal file
17
apps/sim/app/w/knowledge/[id]/page.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { KnowledgeBase } from './base'
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
id: string
|
||||
}>
|
||||
searchParams: Promise<{
|
||||
kbName?: string
|
||||
}>
|
||||
}
|
||||
|
||||
export default async function KnowledgeBasePage({ params, searchParams }: PageProps) {
|
||||
const { id } = await params
|
||||
const { kbName } = await searchParams
|
||||
|
||||
return <KnowledgeBase id={id} knowledgeBaseName={kbName || 'Knowledge Base'} />
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Check, Copy, LibraryBig } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface BaseOverviewProps {
|
||||
id?: string
|
||||
title: string
|
||||
docCount: number
|
||||
description: string
|
||||
}
|
||||
|
||||
export function BaseOverview({ id, title, docCount, description }: BaseOverviewProps) {
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
|
||||
// Create URL with knowledge base name as query parameter
|
||||
const params = new URLSearchParams({
|
||||
kbName: title,
|
||||
})
|
||||
const href = `/w/knowledge/${id || title.toLowerCase().replace(/\s+/g, '-')}?${params.toString()}`
|
||||
|
||||
const handleCopy = async (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
if (id) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(id)
|
||||
setIsCopied(true)
|
||||
setTimeout(() => setIsCopied(false), 2000)
|
||||
} catch (err) {
|
||||
console.error('Failed to copy ID:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={href} prefetch={true}>
|
||||
<div className='group flex cursor-pointer flex-col gap-3 rounded-md border bg-background p-4 transition-colors hover:bg-accent/50'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<LibraryBig className='h-4 w-4 flex-shrink-0 text-muted-foreground' />
|
||||
<h3 className='truncate font-medium text-sm leading-tight'>{title}</h3>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex items-center gap-2 text-muted-foreground text-xs'>
|
||||
<span>
|
||||
{docCount} {docCount === 1 ? 'doc' : 'docs'}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='truncate font-mono'>{id?.slice(0, 8)}</span>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className='flex h-4 w-4 items-center justify-center rounded text-gray-500 hover:bg-gray-100 hover:text-gray-700'
|
||||
>
|
||||
{isCopied ? <Check className='h-3 w-3' /> : <Copy className='h-3 w-3' />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className='line-clamp-2 overflow-hidden text-muted-foreground text-xs'>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { AlertCircle, CheckCircle2, X } from 'lucide-react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { z } from 'zod'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { getDocumentIcon } from '@/app/w/knowledge/components/icons/document-icons'
|
||||
|
||||
// Define form schema
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required').max(100, 'Name must be less than 100 characters'),
|
||||
description: z.string().max(500, 'Description must be less than 500 characters').optional(),
|
||||
})
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>
|
||||
|
||||
// File upload constraints
|
||||
const MAX_FILE_SIZE = 50 * 1024 * 1024 // 50MB
|
||||
const ACCEPTED_FILE_TYPES = [
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'text/plain',
|
||||
'text/csv',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string
|
||||
}
|
||||
|
||||
interface KnowledgeBase {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
tokenCount: number
|
||||
embeddingModel: string
|
||||
embeddingDimension: number
|
||||
chunkingConfig: any
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
workspaceId?: string
|
||||
}
|
||||
|
||||
interface CreateFormProps {
|
||||
onClose: () => void
|
||||
onKnowledgeBaseCreated?: (knowledgeBase: KnowledgeBase) => void
|
||||
}
|
||||
|
||||
export function CreateForm({ onClose, onKnowledgeBaseCreated }: CreateFormProps) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [submitStatus, setSubmitStatus] = useState<'success' | 'error' | null>(null)
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
const [files, setFiles] = useState<FileWithPreview[]>([])
|
||||
const [fileError, setFileError] = useState<string | null>(null)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||
const dropZoneRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
description: '',
|
||||
},
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const processFiles = async (fileList: FileList | File[]) => {
|
||||
setFileError(null)
|
||||
|
||||
if (!fileList || fileList.length === 0) return
|
||||
|
||||
try {
|
||||
const newFiles: FileWithPreview[] = []
|
||||
let hasError = false
|
||||
|
||||
for (const file of Array.from(fileList)) {
|
||||
// Check file size
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
setFileError(`File ${file.name} is too large. Maximum size is 50MB.`)
|
||||
hasError = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Check file type
|
||||
if (!ACCEPTED_FILE_TYPES.includes(file.type)) {
|
||||
setFileError(
|
||||
`File ${file.name} has an unsupported format. Please use PDF, DOC, DOCX, TXT, CSV, XLS, or XLSX.`
|
||||
)
|
||||
hasError = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Create file with preview (using file icon since these aren't images)
|
||||
const fileWithPreview = Object.assign(file, {
|
||||
preview: URL.createObjectURL(file),
|
||||
}) as FileWithPreview
|
||||
|
||||
newFiles.push(fileWithPreview)
|
||||
}
|
||||
|
||||
if (!hasError && newFiles.length > 0) {
|
||||
setFiles((prev) => [...prev, ...newFiles])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing files:', error)
|
||||
setFileError('An error occurred while processing files. Please try again.')
|
||||
} finally {
|
||||
// Reset the input
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
await processFiles(e.target.files)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle drag events
|
||||
const handleDragEnter = (e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsDragging(true)
|
||||
}
|
||||
|
||||
const handleDragLeave = (e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsDragging(false)
|
||||
}
|
||||
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleDrop = async (e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsDragging(false)
|
||||
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
await processFiles(e.dataTransfer.files)
|
||||
}
|
||||
}
|
||||
|
||||
const removeFile = (index: number) => {
|
||||
setFiles((prev) => {
|
||||
// Revoke the URL to avoid memory leaks
|
||||
URL.revokeObjectURL(prev[index].preview)
|
||||
return prev.filter((_, i) => i !== index)
|
||||
})
|
||||
}
|
||||
|
||||
const getFileIcon = (mimeType: string, filename: string) => {
|
||||
const IconComponent = getDocumentIcon(mimeType, filename)
|
||||
return <IconComponent className='h-10 w-8' />
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
const onSubmit = async (data: FormValues) => {
|
||||
setIsSubmitting(true)
|
||||
setSubmitStatus(null)
|
||||
|
||||
try {
|
||||
// First create the knowledge base
|
||||
const knowledgeBasePayload = {
|
||||
name: data.name,
|
||||
description: data.description || undefined,
|
||||
}
|
||||
|
||||
const response = await fetch('/api/knowledge', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(knowledgeBasePayload),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.error || 'Failed to create knowledge base')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to create knowledge base')
|
||||
}
|
||||
|
||||
const newKnowledgeBase = result.data
|
||||
|
||||
// If files are uploaded, process them
|
||||
if (files.length > 0) {
|
||||
// First, upload all files to get their URLs
|
||||
const uploadedFiles = []
|
||||
|
||||
for (const file of files) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
const uploadResponse = await fetch('/api/files/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (!uploadResponse.ok) {
|
||||
const errorData = await uploadResponse.json()
|
||||
throw new Error(`Failed to upload ${file.name}: ${errorData.error || 'Unknown error'}`)
|
||||
}
|
||||
|
||||
const uploadResult = await uploadResponse.json()
|
||||
uploadedFiles.push({
|
||||
filename: file.name,
|
||||
fileUrl: uploadResult.path.startsWith('http')
|
||||
? uploadResult.path
|
||||
: `${window.location.origin}${uploadResult.path}`,
|
||||
fileSize: file.size,
|
||||
mimeType: file.type,
|
||||
fileHash: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
// Now process the uploaded files
|
||||
const processResponse = await fetch(
|
||||
`/api/knowledge/${newKnowledgeBase.id}/process-documents`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
documents: uploadedFiles,
|
||||
processingOptions: {
|
||||
chunkSize: 1024,
|
||||
minCharactersPerChunk: 24,
|
||||
recipe: 'default',
|
||||
lang: 'en',
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!processResponse.ok) {
|
||||
const errorData = await processResponse.json()
|
||||
throw new Error(errorData.error || 'Failed to process documents')
|
||||
}
|
||||
|
||||
const processResult = await processResponse.json()
|
||||
|
||||
if (!processResult.success) {
|
||||
throw new Error(processResult.error || 'Failed to process documents')
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Processed ${processResult.data.processed}/${processResult.data.total} documents with ${processResult.data.totalChunks} total chunks`
|
||||
)
|
||||
}
|
||||
|
||||
setSubmitStatus('success')
|
||||
reset()
|
||||
|
||||
// Clean up file previews
|
||||
files.forEach((file) => URL.revokeObjectURL(file.preview))
|
||||
setFiles([])
|
||||
|
||||
// Call the callback if provided
|
||||
if (onKnowledgeBaseCreated) {
|
||||
onKnowledgeBaseCreated(newKnowledgeBase)
|
||||
}
|
||||
|
||||
// Close modal after a short delay to show success message
|
||||
setTimeout(() => {
|
||||
onClose()
|
||||
}, 2000)
|
||||
} catch (error) {
|
||||
console.error('Error creating knowledge base:', error)
|
||||
setSubmitStatus('error')
|
||||
setErrorMessage(error instanceof Error ? error.message : 'An unknown error occurred')
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='flex h-full flex-col'>
|
||||
{/* Scrollable Content */}
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className='scrollbar-thin scrollbar-thumb-muted-foreground/20 hover:scrollbar-thumb-muted-foreground/25 scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto px-6'
|
||||
>
|
||||
<div className='py-4'>
|
||||
{submitStatus === 'success' ? (
|
||||
<Alert className='mb-6 border-border border-green-200 bg-green-50 dark:border-green-900 dark:bg-green-950/30'>
|
||||
<div className='flex items-start gap-4 py-1'>
|
||||
<div className='mt-[-1.5px] flex-shrink-0'>
|
||||
<CheckCircle2 className='h-4 w-4 text-green-600 dark:text-green-400' />
|
||||
</div>
|
||||
<div className='mr-4 flex-1 space-y-2'>
|
||||
<AlertTitle className='-mt-0.5 flex items-center justify-between'>
|
||||
<span className='font-medium text-green-600 dark:text-green-400'>Success</span>
|
||||
</AlertTitle>
|
||||
<AlertDescription className='text-green-600 dark:text-green-400'>
|
||||
Your knowledge base has been created successfully!
|
||||
</AlertDescription>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
) : submitStatus === 'error' ? (
|
||||
<Alert variant='destructive' className='mb-6'>
|
||||
<AlertCircle className='h-4 w-4' />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>
|
||||
{errorMessage ||
|
||||
'There was an error creating your knowledge base. Please try again.'}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<div className='space-y-4'>
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor='name'>Name *</Label>
|
||||
<Input
|
||||
id='name'
|
||||
placeholder='Enter knowledge base name'
|
||||
{...register('name')}
|
||||
className={errors.name ? 'border-red-500' : ''}
|
||||
/>
|
||||
{errors.name && <p className='mt-1 text-red-500 text-sm'>{errors.name.message}</p>}
|
||||
</div>
|
||||
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor='description'>Description</Label>
|
||||
<Textarea
|
||||
id='description'
|
||||
placeholder='Describe what this knowledge base contains (optional)'
|
||||
rows={3}
|
||||
{...register('description')}
|
||||
className={errors.description ? 'border-red-500' : ''}
|
||||
/>
|
||||
{errors.description && (
|
||||
<p className='mt-1 text-red-500 text-sm'>{errors.description.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File Upload Section */}
|
||||
<div className='mt-6 space-y-2'>
|
||||
<Label>Upload Documents</Label>
|
||||
{files.length === 0 ? (
|
||||
<div
|
||||
ref={dropZoneRef}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className={`relative cursor-pointer rounded-lg border-[1px] border-dashed p-16 text-center transition-colors ${
|
||||
isDragging
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-muted-foreground/25 hover:border-muted-foreground/50'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type='file'
|
||||
accept={ACCEPTED_FILE_TYPES.join(',')}
|
||||
onChange={handleFileChange}
|
||||
className='hidden'
|
||||
multiple
|
||||
/>
|
||||
<div className='flex flex-col items-center gap-2'>
|
||||
<div className='space-y-1'>
|
||||
<p className='font-medium text-sm'>Drop files here or click to browse</p>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
Supports PDF, DOC, DOCX, TXT, CSV, XLS, XLSX (max 50MB each)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className='space-y-2'>
|
||||
<div className='space-y-2'>
|
||||
{/* Compact drop area at top of file list */}
|
||||
<div
|
||||
ref={dropZoneRef}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className={`flex cursor-pointer items-center justify-center rounded-md border border-dashed p-3 transition-colors ${
|
||||
isDragging
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-muted-foreground/25 hover:border-muted-foreground/50'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type='file'
|
||||
accept={ACCEPTED_FILE_TYPES.join(',')}
|
||||
onChange={handleFileChange}
|
||||
className='hidden'
|
||||
multiple
|
||||
/>
|
||||
<div className='text-center'>
|
||||
<p className='font-medium text-sm'>Drop more files or click to browse</p>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
PDF, DOC, DOCX, TXT, CSV, XLS, XLSX (max 50MB each)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File list */}
|
||||
{files.map((file, index) => (
|
||||
<div key={index} className='flex items-center gap-3 rounded-md border p-3'>
|
||||
{getFileIcon(file.type, file.name)}
|
||||
<div className='min-w-0 flex-1'>
|
||||
<p className='truncate font-medium text-sm'>{file.name}</p>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{formatFileSize(file.size)}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={() => removeFile(index)}
|
||||
className='h-8 w-8 p-0 text-muted-foreground hover:text-destructive'
|
||||
>
|
||||
<X className='h-4 w-4' />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{fileError && <p className='mt-1 text-red-500 text-sm'>{fileError}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fixed Footer */}
|
||||
<div className='mt-auto border-t px-6 pt-4 pb-6'>
|
||||
<div className='flex justify-between'>
|
||||
<Button variant='outline' onClick={onClose} type='button'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type='submit'
|
||||
disabled={isSubmitting}
|
||||
className='bg-[#701FFC] font-[480] text-primary-foreground shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]'
|
||||
>
|
||||
{isSubmitting ? 'Creating...' : 'Create Knowledge Base'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
'use client'
|
||||
|
||||
import { X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { CreateForm } from './components/create-form/create-form'
|
||||
|
||||
interface KnowledgeBase {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
tokenCount: number
|
||||
embeddingModel: string
|
||||
embeddingDimension: number
|
||||
chunkingConfig: any
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
workspaceId?: string
|
||||
}
|
||||
|
||||
interface CreateModalProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onKnowledgeBaseCreated?: (knowledgeBase: KnowledgeBase) => void
|
||||
}
|
||||
|
||||
export function CreateModal({ open, onOpenChange, onKnowledgeBaseCreated }: CreateModalProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className='flex h-[74vh] flex-col gap-0 overflow-hidden p-0 sm:max-w-[600px]'
|
||||
hideCloseButton
|
||||
>
|
||||
<DialogHeader className='flex-shrink-0 border-b px-6 py-4'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<DialogTitle className='font-medium text-lg'>Create Knowledge Base</DialogTitle>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='icon'
|
||||
className='h-8 w-8 p-0'
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
<X className='h-4 w-4' />
|
||||
<span className='sr-only'>Close</span>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
<CreateForm
|
||||
onClose={() => onOpenChange(false)}
|
||||
onKnowledgeBaseCreated={onKnowledgeBaseCreated}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import { LibraryBig } from 'lucide-react'
|
||||
|
||||
interface EmptyStateCardProps {
|
||||
title: string
|
||||
description: string
|
||||
buttonText: string
|
||||
onClick: () => void
|
||||
icon?: React.ReactNode
|
||||
}
|
||||
|
||||
export function EmptyStateCard({
|
||||
title,
|
||||
description,
|
||||
buttonText,
|
||||
onClick,
|
||||
icon,
|
||||
}: EmptyStateCardProps) {
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className='group flex cursor-pointer flex-col gap-3 rounded-md border border-muted-foreground/25 border-dashed bg-background p-4 transition-colors hover:border-muted-foreground/40 hover:bg-accent/50'
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
{icon || <LibraryBig className='h-4 w-4 flex-shrink-0 text-muted-foreground' />}
|
||||
<h3 className='truncate font-medium text-sm leading-tight'>{title}</h3>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex items-center gap-2 text-muted-foreground text-xs'>
|
||||
<span>Get started</span>
|
||||
</div>
|
||||
|
||||
<p className='line-clamp-2 overflow-hidden text-muted-foreground text-xs'>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
197
apps/sim/app/w/knowledge/components/icons/document-icons.tsx
Normal file
197
apps/sim/app/w/knowledge/components/icons/document-icons.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import type React from 'react'
|
||||
|
||||
interface IconProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const PdfIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#E53935'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#EF5350' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#C62828'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<text
|
||||
x='12'
|
||||
y='16'
|
||||
textAnchor='middle'
|
||||
fontSize='7'
|
||||
fontWeight='bold'
|
||||
fill='white'
|
||||
fontFamily='Arial, sans-serif'
|
||||
>
|
||||
PDF
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const DocxIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#2196F3'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#64B5F6' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#1565C0'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<text
|
||||
x='12'
|
||||
y='16'
|
||||
textAnchor='middle'
|
||||
fontSize='8'
|
||||
fontWeight='bold'
|
||||
fill='white'
|
||||
fontFamily='Arial, sans-serif'
|
||||
>
|
||||
W
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const XlsxIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#4CAF50'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#81C784' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#2E7D32'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<text
|
||||
x='12'
|
||||
y='16'
|
||||
textAnchor='middle'
|
||||
fontSize='8'
|
||||
fontWeight='bold'
|
||||
fill='white'
|
||||
fontFamily='Arial, sans-serif'
|
||||
>
|
||||
X
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CsvIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#4CAF50'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#81C784' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#2E7D32'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<text
|
||||
x='12'
|
||||
y='16'
|
||||
textAnchor='middle'
|
||||
fontSize='6.5'
|
||||
fontWeight='bold'
|
||||
fill='white'
|
||||
fontFamily='Arial, sans-serif'
|
||||
>
|
||||
CSV
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const TxtIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#757575'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#9E9E9E' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#424242'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<text
|
||||
x='12'
|
||||
y='16'
|
||||
textAnchor='middle'
|
||||
fontSize='6'
|
||||
fontWeight='bold'
|
||||
fill='white'
|
||||
fontFamily='Arial, sans-serif'
|
||||
>
|
||||
TXT
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const DefaultFileIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#607D8B'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#90A4AE' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#37474F'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<rect x='8' y='13' width='8' height='1' fill='white' rx='0.5' />
|
||||
<rect x='8' y='15' width='8' height='1' fill='white' rx='0.5' />
|
||||
<rect x='8' y='17' width='5' height='1' fill='white' rx='0.5' />
|
||||
</svg>
|
||||
)
|
||||
|
||||
// Helper function to get the appropriate icon component
|
||||
export function getDocumentIcon(mimeType: string, filename: string): React.FC<IconProps> {
|
||||
const extension = filename.split('.').pop()?.toLowerCase()
|
||||
|
||||
if (mimeType === 'application/pdf' || extension === 'pdf') {
|
||||
return PdfIcon
|
||||
}
|
||||
if (
|
||||
mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
||||
mimeType === 'application/msword' ||
|
||||
extension === 'docx' ||
|
||||
extension === 'doc'
|
||||
) {
|
||||
return DocxIcon
|
||||
}
|
||||
if (
|
||||
mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||
mimeType === 'application/vnd.ms-excel' ||
|
||||
extension === 'xlsx' ||
|
||||
extension === 'xls'
|
||||
) {
|
||||
return XlsxIcon
|
||||
}
|
||||
if (mimeType === 'text/csv' || extension === 'csv') {
|
||||
return CsvIcon
|
||||
}
|
||||
if (mimeType === 'text/plain' || extension === 'txt') {
|
||||
return TxtIcon
|
||||
}
|
||||
return DefaultFileIcon
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
export function KnowledgeBaseCardSkeleton() {
|
||||
return (
|
||||
<div className='rounded-lg border bg-background p-4'>
|
||||
<div className='flex items-start justify-between'>
|
||||
<div className='flex-1 space-y-3'>
|
||||
{/* Title skeleton */}
|
||||
<div className='h-4 w-3/4 animate-pulse rounded bg-muted' />
|
||||
|
||||
{/* Description skeleton */}
|
||||
<div className='space-y-2'>
|
||||
<div className='h-3 w-full animate-pulse rounded bg-muted' />
|
||||
<div className='h-3 w-2/3 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
|
||||
{/* Stats skeleton */}
|
||||
<div className='flex items-center gap-4 pt-2'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='h-3 w-3 animate-pulse rounded bg-muted' />
|
||||
<div className='h-3 w-8 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='h-3 w-3 animate-pulse rounded bg-muted' />
|
||||
<div className='h-3 w-12 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function KnowledgeBaseCardSkeletonGrid({ count = 8 }: { count?: number }) {
|
||||
return (
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'>
|
||||
{Array.from({ length: count }).map((_, i) => (
|
||||
<KnowledgeBaseCardSkeleton key={i} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
241
apps/sim/app/w/knowledge/components/skeletons/table-skeleton.tsx
Normal file
241
apps/sim/app/w/knowledge/components/skeletons/table-skeleton.tsx
Normal file
@@ -0,0 +1,241 @@
|
||||
export function DocumentTableRowSkeleton({ isSidebarCollapsed }: { isSidebarCollapsed: boolean }) {
|
||||
return (
|
||||
<tr className='border-b'>
|
||||
{/* Select column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-3.5 w-3.5 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Name column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='h-6 w-5 animate-pulse rounded bg-muted' />
|
||||
<div className='h-4 w-32 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Size column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-3 w-12 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Tokens column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-3 w-8 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Chunks column - hidden on small screens */}
|
||||
<td className='hidden px-4 py-3 lg:table-cell'>
|
||||
<div className='h-3 w-6 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Upload Time column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='space-y-1'>
|
||||
<div className='h-3 w-16 animate-pulse rounded bg-muted' />
|
||||
<div className='h-3 w-12 animate-pulse rounded bg-muted lg:hidden' />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-6 w-16 animate-pulse rounded-md bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Actions column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='h-8 w-8 animate-pulse rounded bg-muted' />
|
||||
<div className='h-8 w-8 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
export function ChunkTableRowSkeleton({ isSidebarCollapsed }: { isSidebarCollapsed: boolean }) {
|
||||
return (
|
||||
<tr className='border-b'>
|
||||
{/* Select column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-3.5 w-3.5 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Index column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-4 w-6 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Content column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='space-y-2'>
|
||||
<div className='h-4 w-full animate-pulse rounded bg-muted' />
|
||||
<div className='h-4 w-3/4 animate-pulse rounded bg-muted' />
|
||||
<div className='h-4 w-1/2 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Tokens column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-3 w-8 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-6 w-16 animate-pulse rounded-md bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Actions column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='h-8 w-8 animate-pulse rounded bg-muted' />
|
||||
<div className='h-8 w-8 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
export function DocumentTableSkeleton({
|
||||
isSidebarCollapsed,
|
||||
rowCount = 5,
|
||||
}: {
|
||||
isSidebarCollapsed: boolean
|
||||
rowCount?: number
|
||||
}) {
|
||||
return (
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Table header - fixed */}
|
||||
<div className='sticky top-0 z-10 border-b bg-background'>
|
||||
<table className='w-full table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[5%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[18%]' : 'w-[20%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className='hidden w-[8%] lg:table-column' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[22%]' : 'w-[20%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[16%]' />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<div className='h-3.5 w-3.5 animate-pulse rounded bg-muted' />
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Name</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Size</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Tokens</span>
|
||||
</th>
|
||||
<th className='hidden px-4 pt-2 pb-3 text-left font-medium lg:table-cell'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Chunks</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Uploaded</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Status</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Table body - scrollable */}
|
||||
<div className='flex-1 overflow-auto'>
|
||||
<table className='w-full table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[5%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[18%]' : 'w-[20%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className='hidden w-[8%] lg:table-column' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[22%]' : 'w-[20%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[16%]' />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{Array.from({ length: rowCount }).map((_, i) => (
|
||||
<DocumentTableRowSkeleton key={i} isSidebarCollapsed={isSidebarCollapsed} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ChunkTableSkeleton({
|
||||
isSidebarCollapsed,
|
||||
rowCount = 5,
|
||||
}: {
|
||||
isSidebarCollapsed: boolean
|
||||
rowCount?: number
|
||||
}) {
|
||||
return (
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Table header - fixed */}
|
||||
<div className='sticky top-0 z-10 border-b bg-background'>
|
||||
<table className='w-full table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[5%]' />
|
||||
<col className='w-[8%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[57%]' : 'w-[55%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[12%]' />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<div className='h-3.5 w-3.5 animate-pulse rounded bg-muted' />
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Index</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Content</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Tokens</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Status</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Table body - scrollable */}
|
||||
<div className='flex-1 overflow-auto'>
|
||||
<table className='w-full table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[5%]' />
|
||||
<col className='w-[8%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[57%]' : 'w-[55%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[12%]' />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{Array.from({ length: rowCount }).map((_, i) => (
|
||||
<ChunkTableRowSkeleton key={i} isSidebarCollapsed={isSidebarCollapsed} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
193
apps/sim/app/w/knowledge/knowledge.tsx
Normal file
193
apps/sim/app/w/knowledge/knowledge.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { LibraryBig, Plus, Search, X } from 'lucide-react'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
import { BaseOverview } from './components/base-overview/base-overview'
|
||||
import { CreateModal } from './components/create-modal/create-modal'
|
||||
import { EmptyStateCard } from './components/empty-state-card/empty-state-card'
|
||||
import { KnowledgeBaseCardSkeletonGrid } from './components/skeletons/knowledge-base-card-skeleton'
|
||||
|
||||
interface KnowledgeBase {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
tokenCount: number
|
||||
embeddingModel: string
|
||||
embeddingDimension: number
|
||||
chunkingConfig: any
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
workspaceId?: string
|
||||
docCount?: number
|
||||
}
|
||||
|
||||
export function Knowledge() {
|
||||
const { mode, isExpanded } = useSidebarStore()
|
||||
const isSidebarCollapsed =
|
||||
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
|
||||
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
// Fetch knowledge bases on component mount
|
||||
useEffect(() => {
|
||||
const fetchKnowledgeBases = async () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
const response = await fetch('/api/knowledge')
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch knowledge bases: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
setKnowledgeBases(result.data)
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to fetch knowledge bases')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching knowledge bases:', err)
|
||||
setError(err instanceof Error ? err.message : 'An error occurred')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchKnowledgeBases()
|
||||
}, [])
|
||||
|
||||
// Handle knowledge base creation success
|
||||
const handleKnowledgeBaseCreated = (newKnowledgeBase: KnowledgeBase) => {
|
||||
setKnowledgeBases((prev) => [newKnowledgeBase, ...prev])
|
||||
}
|
||||
|
||||
// Filter knowledge bases based on search query
|
||||
const filteredKnowledgeBases = knowledgeBases.filter(
|
||||
(kb) =>
|
||||
kb.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
kb.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
|
||||
// Format document count for display
|
||||
const formatKnowledgeBaseForDisplay = (kb: KnowledgeBase) => ({
|
||||
id: kb.id,
|
||||
title: kb.name,
|
||||
docCount: kb.docCount || 0,
|
||||
description: kb.description || 'No description provided',
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`fixed inset-0 flex flex-col transition-all duration-200 ${isSidebarCollapsed ? 'left-14' : 'left-60'}`}
|
||||
>
|
||||
{/* Fixed Header */}
|
||||
<div className='flex items-center gap-2 px-6 pt-4 pb-6'>
|
||||
<LibraryBig className='h-[18px] w-[18px] text-muted-foreground' />
|
||||
<h1 className='font-medium text-sm'>Knowledge</h1>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className='flex-1 overflow-auto pt-[6px]'>
|
||||
<div className='px-6 pb-6'>
|
||||
{/* Search and Create Section */}
|
||||
<div className='mb-6 flex items-center justify-between'>
|
||||
<div className='relative max-w-md flex-1'>
|
||||
<div className='relative flex items-center'>
|
||||
<Search className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-[18px] w-[18px] transform text-muted-foreground' />
|
||||
<input
|
||||
type='text'
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder='Search knowledge bases...'
|
||||
className='h-10 w-full rounded-md border bg-background px-9 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => setSearchQuery('')}
|
||||
className='-translate-y-1/2 absolute top-1/2 right-3 transform text-muted-foreground hover:text-foreground'
|
||||
>
|
||||
<X className='h-[18px] w-[18px]' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setIsCreateModalOpen(true)}
|
||||
className='flex items-center gap-1 rounded-md bg-[#701FFC] px-3 py-[7px] font-[480] text-primary-foreground text-sm shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]'
|
||||
>
|
||||
<Plus className='h-4 w-4 font-[480]' />
|
||||
<span>Create</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Error State */}
|
||||
{error && (
|
||||
<div className='mb-6 rounded-md border border-red-200 bg-red-50 p-4'>
|
||||
<p className='text-red-800 text-sm'>Error loading knowledge bases: {error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className='mt-2 text-red-600 text-sm underline hover:text-red-800'
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Content Area */}
|
||||
{isLoading ? (
|
||||
<KnowledgeBaseCardSkeletonGrid count={8} />
|
||||
) : (
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'>
|
||||
{filteredKnowledgeBases.length === 0 ? (
|
||||
knowledgeBases.length === 0 ? (
|
||||
<EmptyStateCard
|
||||
title='Create your first knowledge base'
|
||||
description='Upload your documents to create a knowledge base for your agents.'
|
||||
buttonText='Create Knowledge Base'
|
||||
onClick={() => setIsCreateModalOpen(true)}
|
||||
icon={<LibraryBig className='h-4 w-4 text-muted-foreground' />}
|
||||
/>
|
||||
) : (
|
||||
<div className='col-span-full py-12 text-center'>
|
||||
<p className='text-muted-foreground'>No knowledge bases match your search.</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
filteredKnowledgeBases.map((kb) => {
|
||||
const displayData = formatKnowledgeBaseForDisplay(kb)
|
||||
return (
|
||||
<BaseOverview
|
||||
key={kb.id}
|
||||
id={displayData.id}
|
||||
title={displayData.title}
|
||||
docCount={displayData.docCount}
|
||||
description={displayData.description}
|
||||
/>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Create Modal */}
|
||||
<CreateModal
|
||||
open={isCreateModalOpen}
|
||||
onOpenChange={setIsCreateModalOpen}
|
||||
onKnowledgeBaseCreated={handleKnowledgeBaseCreated}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
54
apps/sim/app/w/knowledge/loading.tsx
Normal file
54
apps/sim/app/w/knowledge/loading.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
|
||||
import { LibraryBig, Plus, Search } from 'lucide-react'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
import { KnowledgeBaseCardSkeletonGrid } from './components/skeletons/knowledge-base-card-skeleton'
|
||||
|
||||
export default function KnowledgeLoading() {
|
||||
const { mode, isExpanded } = useSidebarStore()
|
||||
const isSidebarCollapsed =
|
||||
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-0 flex flex-col transition-all duration-200 ${isSidebarCollapsed ? 'left-14' : 'left-60'}`}
|
||||
>
|
||||
{/* Fixed Header */}
|
||||
<div className='flex items-center gap-2 px-6 pt-4 pb-6'>
|
||||
<LibraryBig className='h-[18px] w-[18px] text-muted-foreground' />
|
||||
<h1 className='font-medium text-sm'>Knowledge</h1>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className='flex-1 overflow-auto pt-[6px]'>
|
||||
<div className='px-6 pb-6'>
|
||||
{/* Search and Create Section */}
|
||||
<div className='mb-6 flex items-center justify-between'>
|
||||
<div className='relative max-w-md flex-1'>
|
||||
<div className='relative flex items-center'>
|
||||
<Search className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-[18px] w-[18px] transform text-muted-foreground' />
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Search knowledge bases...'
|
||||
disabled
|
||||
className='h-10 w-full rounded-md border bg-background px-9 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
disabled
|
||||
className='flex items-center gap-1 rounded-md bg-[#701FFC] px-3 py-[7px] font-[480] text-primary-foreground text-sm shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)] disabled:opacity-50'
|
||||
>
|
||||
<Plus className='h-4 w-4 font-[480]' />
|
||||
<span>Create</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
<KnowledgeBaseCardSkeletonGrid count={8} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
5
apps/sim/app/w/knowledge/page.tsx
Normal file
5
apps/sim/app/w/knowledge/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Knowledge } from './knowledge'
|
||||
|
||||
export default function KnowledgePage() {
|
||||
return <Knowledge />
|
||||
}
|
||||
57
apps/sim/blocks/blocks/knowledge.ts
Normal file
57
apps/sim/blocks/blocks/knowledge.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { PackageSearchIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '../types'
|
||||
|
||||
export const KnowledgeBlock: BlockConfig = {
|
||||
type: 'knowledge',
|
||||
name: 'Knowledge',
|
||||
description: 'Search knowledge',
|
||||
longDescription:
|
||||
'Perform semantic vector search across your knowledge base to find the most relevant content. Uses advanced AI embeddings to understand meaning and context, returning the most similar documents to your search query.',
|
||||
bgColor: '#00B0B0',
|
||||
icon: PackageSearchIcon,
|
||||
category: 'blocks',
|
||||
docsLink: 'https://docs.simstudio.ai/blocks/knowledge',
|
||||
tools: {
|
||||
access: ['knowledge_search'],
|
||||
},
|
||||
inputs: {
|
||||
knowledgeBaseId: { type: 'string', required: true },
|
||||
query: { type: 'string', required: true },
|
||||
topK: { type: 'number', required: false },
|
||||
},
|
||||
outputs: {
|
||||
response: {
|
||||
type: {
|
||||
results: 'json',
|
||||
query: 'string',
|
||||
knowledgeBaseId: 'string',
|
||||
topK: 'number',
|
||||
totalResults: 'number',
|
||||
message: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'knowledgeBaseId',
|
||||
title: 'Knowledge Base ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter knowledge base ID',
|
||||
},
|
||||
{
|
||||
id: 'query',
|
||||
title: 'Search Query',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter your search query',
|
||||
},
|
||||
{
|
||||
id: 'topK',
|
||||
title: 'Number of Results',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter number of results (default: 10)',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { GoogleSheetsBlock } from './blocks/google_sheets'
|
||||
import { ImageGeneratorBlock } from './blocks/image_generator'
|
||||
import { JinaBlock } from './blocks/jina'
|
||||
import { JiraBlock } from './blocks/jira'
|
||||
import { KnowledgeBlock } from './blocks/knowledge'
|
||||
import { LinearBlock } from './blocks/linear'
|
||||
import { LinkupBlock } from './blocks/linkup'
|
||||
import { Mem0Block } from './blocks/mem0'
|
||||
@@ -109,6 +110,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
stagehand_agent: StagehandAgentBlock,
|
||||
slack: SlackBlock,
|
||||
starter: StarterBlock,
|
||||
knowledge: KnowledgeBlock,
|
||||
supabase: SupabaseBlock,
|
||||
tavily: TavilyBlock,
|
||||
telegram: TelegramBlock,
|
||||
|
||||
@@ -2562,6 +2562,9 @@ export function MicrosoftTeamsIcon(props: SVGProps<SVGSVGElement>) {
|
||||
<stop offset='0' stopColor='#5a62c3' />
|
||||
<stop offset='.5' stopColor='#4d55bd' />
|
||||
<stop offset='1' stopColor='#3940ab' />
|
||||
<stop offset='0' stopColor='#5a62c3' />
|
||||
<stop offset='.5' stopColor='#4d55bd' />
|
||||
<stop offset='1' stopColor='#3940ab' />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill='url(#a)'
|
||||
@@ -2760,3 +2763,27 @@ export function MicrosoftExcelIcon(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function PackageSearchIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
>
|
||||
<path d='M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14' />
|
||||
<path d='m7.5 4.27 9 5.15' />
|
||||
<polyline points='3.29 7 12 12 20.71 7' />
|
||||
<line x1='12' x2='12' y1='22' y2='12' />
|
||||
<circle cx='18.5' cy='15.5' r='2.5' />
|
||||
<path d='M20.27 17.27 22 19' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
192
apps/sim/components/icons/document-icons.tsx
Normal file
192
apps/sim/components/icons/document-icons.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import type React from 'react'
|
||||
|
||||
interface IconProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const PdfIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#E53935'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#EF5350' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#C62828'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<text
|
||||
x='12'
|
||||
y='16'
|
||||
textAnchor='middle'
|
||||
fontSize='7'
|
||||
fontWeight='bold'
|
||||
fill='white'
|
||||
fontFamily='Arial, sans-serif'
|
||||
>
|
||||
PDF
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const DocxIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#2196F3'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#64B5F6' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#1565C0'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<text
|
||||
x='12'
|
||||
y='16'
|
||||
textAnchor='middle'
|
||||
fontSize='8'
|
||||
fontWeight='bold'
|
||||
fill='white'
|
||||
fontFamily='Arial, sans-serif'
|
||||
>
|
||||
W
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const XlsxIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#4CAF50'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#81C784' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#2E7D32'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<text
|
||||
x='12'
|
||||
y='16'
|
||||
textAnchor='middle'
|
||||
fontSize='8'
|
||||
fontWeight='bold'
|
||||
fill='white'
|
||||
fontFamily='Arial, sans-serif'
|
||||
>
|
||||
X
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CsvIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#4CAF50'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#81C784' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#2E7D32'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<g transform='translate(0, -1)'>
|
||||
<rect x='8' y='11' width='8' height='0.5' fill='white' />
|
||||
<rect x='8' y='13' width='8' height='0.5' fill='white' />
|
||||
<rect x='8' y='15' width='8' height='0.5' fill='white' />
|
||||
<rect x='11.75' y='11' width='0.5' height='6' fill='white' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const TxtIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#757575'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#9E9E9E' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#424242'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<text
|
||||
x='12'
|
||||
y='16'
|
||||
textAnchor='middle'
|
||||
fontSize='6'
|
||||
fontWeight='bold'
|
||||
fill='white'
|
||||
fontFamily='Arial, sans-serif'
|
||||
>
|
||||
TXT
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const DefaultFileIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' }) => (
|
||||
<svg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
<path
|
||||
d='M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z'
|
||||
fill='#607D8B'
|
||||
/>
|
||||
<path d='M14 2V8H20' fill='#90A4AE' />
|
||||
<path
|
||||
d='M14 2L20 8V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V4C4 2.9 4.9 2 6 2H14Z'
|
||||
stroke='#37474F'
|
||||
strokeWidth='0.5'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<rect x='8' y='13' width='8' height='1' fill='white' rx='0.5' />
|
||||
<rect x='8' y='15' width='8' height='1' fill='white' rx='0.5' />
|
||||
<rect x='8' y='17' width='5' height='1' fill='white' rx='0.5' />
|
||||
</svg>
|
||||
)
|
||||
|
||||
// Helper function to get the appropriate icon component
|
||||
export function getDocumentIcon(mimeType: string, filename: string): React.FC<IconProps> {
|
||||
const extension = filename.split('.').pop()?.toLowerCase()
|
||||
|
||||
if (mimeType === 'application/pdf' || extension === 'pdf') {
|
||||
return PdfIcon
|
||||
}
|
||||
if (
|
||||
mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
||||
mimeType === 'application/msword' ||
|
||||
extension === 'docx' ||
|
||||
extension === 'doc'
|
||||
) {
|
||||
return DocxIcon
|
||||
}
|
||||
if (
|
||||
mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||
mimeType === 'application/vnd.ms-excel' ||
|
||||
extension === 'xlsx' ||
|
||||
extension === 'xls'
|
||||
) {
|
||||
return XlsxIcon
|
||||
}
|
||||
if (mimeType === 'text/csv' || extension === 'csv') {
|
||||
return CsvIcon
|
||||
}
|
||||
if (mimeType === 'text/plain' || extension === 'txt') {
|
||||
return TxtIcon
|
||||
}
|
||||
return DefaultFileIcon
|
||||
}
|
||||
114
apps/sim/db/migrations/0039_tranquil_speed.sql
Normal file
114
apps/sim/db/migrations/0039_tranquil_speed.sql
Normal file
@@ -0,0 +1,114 @@
|
||||
-- Enable pgvector extension
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
|
||||
-- Create knowledge_base table
|
||||
CREATE TABLE IF NOT EXISTS "knowledge_base" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"workspace_id" text,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"token_count" integer DEFAULT 0 NOT NULL,
|
||||
"embedding_model" text DEFAULT 'text-embedding-3-small' NOT NULL,
|
||||
"embedding_dimension" integer DEFAULT 1536 NOT NULL,
|
||||
"chunking_config" json DEFAULT '{"maxSize": 1024, "minSize": 100, "overlap": 200}' NOT NULL,
|
||||
"deleted_at" timestamp,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- Create document table
|
||||
CREATE TABLE IF NOT EXISTS "document" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"knowledge_base_id" text NOT NULL,
|
||||
"filename" text NOT NULL,
|
||||
"file_url" text NOT NULL,
|
||||
"file_size" integer NOT NULL,
|
||||
"mime_type" text NOT NULL,
|
||||
"file_hash" text,
|
||||
"chunk_count" integer DEFAULT 0 NOT NULL,
|
||||
"token_count" integer DEFAULT 0 NOT NULL,
|
||||
"character_count" integer DEFAULT 0 NOT NULL,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"deleted_at" timestamp,
|
||||
"uploaded_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- Create embedding table with optimized vector type
|
||||
CREATE TABLE IF NOT EXISTS "embedding" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"knowledge_base_id" text NOT NULL,
|
||||
"document_id" text NOT NULL,
|
||||
"chunk_index" integer NOT NULL,
|
||||
"chunk_hash" text NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"content_length" integer NOT NULL,
|
||||
"token_count" integer NOT NULL,
|
||||
"embedding" vector(1536) NOT NULL, -- Optimized for text-embedding-3-small with HNSW support
|
||||
"embedding_model" text DEFAULT 'text-embedding-3-small' NOT NULL,
|
||||
"start_offset" integer NOT NULL,
|
||||
"end_offset" integer NOT NULL,
|
||||
"overlap_tokens" integer DEFAULT 0 NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}' NOT NULL,
|
||||
"search_rank" numeric DEFAULT '1.0',
|
||||
"access_count" integer DEFAULT 0 NOT NULL,
|
||||
"last_accessed_at" timestamp,
|
||||
"quality_score" numeric,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
|
||||
-- Ensure embedding exists (simplified constraint)
|
||||
CONSTRAINT "embedding_not_null_check" CHECK ("embedding" IS NOT NULL)
|
||||
);
|
||||
|
||||
-- Add foreign key constraints
|
||||
ALTER TABLE "knowledge_base" ADD CONSTRAINT "knowledge_base_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "knowledge_base" ADD CONSTRAINT "knowledge_base_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "workspace"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "document" ADD CONSTRAINT "document_knowledge_base_id_knowledge_base_id_fk" FOREIGN KEY ("knowledge_base_id") REFERENCES "knowledge_base"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "embedding" ADD CONSTRAINT "embedding_knowledge_base_id_knowledge_base_id_fk" FOREIGN KEY ("knowledge_base_id") REFERENCES "knowledge_base"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "embedding" ADD CONSTRAINT "embedding_document_id_document_id_fk" FOREIGN KEY ("document_id") REFERENCES "document"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
-- Create indexes for knowledge_base table
|
||||
CREATE INDEX IF NOT EXISTS "kb_user_id_idx" ON "knowledge_base" USING btree ("user_id");
|
||||
CREATE INDEX IF NOT EXISTS "kb_workspace_id_idx" ON "knowledge_base" USING btree ("workspace_id");
|
||||
CREATE INDEX IF NOT EXISTS "kb_user_workspace_idx" ON "knowledge_base" USING btree ("user_id","workspace_id");
|
||||
CREATE INDEX IF NOT EXISTS "kb_deleted_at_idx" ON "knowledge_base" USING btree ("deleted_at");
|
||||
|
||||
-- Create indexes for document table
|
||||
CREATE INDEX IF NOT EXISTS "doc_kb_id_idx" ON "document" USING btree ("knowledge_base_id");
|
||||
CREATE INDEX IF NOT EXISTS "doc_file_hash_idx" ON "document" USING btree ("file_hash");
|
||||
CREATE INDEX IF NOT EXISTS "doc_filename_idx" ON "document" USING btree ("filename");
|
||||
CREATE INDEX IF NOT EXISTS "doc_kb_uploaded_at_idx" ON "document" USING btree ("knowledge_base_id","uploaded_at");
|
||||
|
||||
-- Create embedding table indexes
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_id_idx" ON "embedding" USING btree ("knowledge_base_id");
|
||||
CREATE INDEX IF NOT EXISTS "emb_doc_id_idx" ON "embedding" USING btree ("document_id");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emb_doc_chunk_idx" ON "embedding" USING btree ("document_id","chunk_index");
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_model_idx" ON "embedding" USING btree ("knowledge_base_id","embedding_model");
|
||||
CREATE INDEX IF NOT EXISTS "emb_chunk_hash_idx" ON "embedding" USING btree ("chunk_hash");
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_access_idx" ON "embedding" USING btree ("knowledge_base_id","last_accessed_at");
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_rank_idx" ON "embedding" USING btree ("knowledge_base_id","search_rank");
|
||||
|
||||
-- Create optimized HNSW index for vector similarity search
|
||||
CREATE INDEX IF NOT EXISTS "embedding_vector_hnsw_idx" ON "embedding"
|
||||
USING hnsw ("embedding" vector_cosine_ops)
|
||||
WITH (m = 16, ef_construction = 64);
|
||||
|
||||
-- GIN index for JSONB metadata queries
|
||||
CREATE INDEX IF NOT EXISTS "emb_metadata_gin_idx" ON "embedding" USING gin ("metadata");
|
||||
|
||||
-- Full-text search support with generated tsvector column
|
||||
ALTER TABLE "embedding" ADD COLUMN IF NOT EXISTS "content_tsv" tsvector GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED;
|
||||
CREATE INDEX IF NOT EXISTS "emb_content_fts_idx" ON "embedding" USING gin ("content_tsv");
|
||||
|
||||
-- Performance optimization: Set fillfactor for high-update tables
|
||||
ALTER TABLE "embedding" SET (fillfactor = 85);
|
||||
ALTER TABLE "document" SET (fillfactor = 90);
|
||||
|
||||
-- Add table comments for documentation
|
||||
COMMENT ON TABLE "knowledge_base" IS 'Stores knowledge base configurations and settings';
|
||||
COMMENT ON TABLE "document" IS 'Stores document metadata and processing status';
|
||||
COMMENT ON TABLE "embedding" IS 'Stores vector embeddings optimized for text-embedding-3-small with HNSW similarity search';
|
||||
COMMENT ON COLUMN "embedding"."embedding" IS 'Vector embedding using pgvector type optimized for HNSW similarity search';
|
||||
COMMENT ON COLUMN "embedding"."metadata" IS 'JSONB metadata for flexible filtering (e.g., page numbers, sections, tags)';
|
||||
COMMENT ON COLUMN "embedding"."search_rank" IS 'Boost factor for search results, higher values appear first';
|
||||
8
apps/sim/db/migrations/0040_silky_monster_badoon.sql
Normal file
8
apps/sim/db/migrations/0040_silky_monster_badoon.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Add enabled field to embedding table
|
||||
ALTER TABLE "embedding" ADD COLUMN IF NOT EXISTS "enabled" boolean DEFAULT true NOT NULL;
|
||||
|
||||
-- Composite index for knowledge base + enabled chunks (for search optimization)
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_enabled_idx" ON "embedding" USING btree ("knowledge_base_id", "enabled");
|
||||
|
||||
-- Composite index for document + enabled chunks (for document chunk listings)
|
||||
CREATE INDEX IF NOT EXISTS "emb_doc_enabled_idx" ON "embedding" USING btree ("document_id", "enabled");
|
||||
2810
apps/sim/db/migrations/meta/0039_snapshot.json
Normal file
2810
apps/sim/db/migrations/meta/0039_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2859
apps/sim/db/migrations/meta/0040_snapshot.json
Normal file
2859
apps/sim/db/migrations/meta/0040_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -205,7 +205,6 @@
|
||||
"tag": "0028_absent_triton",
|
||||
"breakpoints": true
|
||||
},
|
||||
|
||||
{
|
||||
"idx": 30,
|
||||
"version": "7",
|
||||
@@ -268,6 +267,20 @@
|
||||
"when": 1747559012564,
|
||||
"tag": "0038_shocking_thor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 39,
|
||||
"version": "7",
|
||||
"when": 1748846346219,
|
||||
"tag": "0039_tranquil_speed",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 40,
|
||||
"version": "7",
|
||||
"when": 1748846439041,
|
||||
"tag": "0040_silky_monster_badoon",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
import { type SQL, sql } from 'drizzle-orm'
|
||||
import {
|
||||
boolean,
|
||||
check,
|
||||
customType,
|
||||
decimal,
|
||||
index,
|
||||
integer,
|
||||
json,
|
||||
jsonb,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
uniqueIndex,
|
||||
vector,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
|
||||
// Custom tsvector type for full-text search
|
||||
export const tsvector = customType<{
|
||||
data: string
|
||||
}>({
|
||||
dataType() {
|
||||
return `tsvector`
|
||||
},
|
||||
})
|
||||
|
||||
export const user = pgTable('user', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
@@ -410,3 +424,178 @@ export const memory = pgTable(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export const knowledgeBase = pgTable(
|
||||
'knowledge_base',
|
||||
{
|
||||
id: text('id').primaryKey(),
|
||||
userId: text('user_id')
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: 'cascade' }),
|
||||
workspaceId: text('workspace_id').references(() => workspace.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
|
||||
// Token tracking for usage
|
||||
tokenCount: integer('token_count').notNull().default(0),
|
||||
|
||||
// Embedding configuration
|
||||
embeddingModel: text('embedding_model').notNull().default('text-embedding-3-small'),
|
||||
embeddingDimension: integer('embedding_dimension').notNull().default(1536),
|
||||
|
||||
// Chunking configuration stored as JSON for flexibility
|
||||
chunkingConfig: json('chunking_config')
|
||||
.notNull()
|
||||
.default('{"maxSize": 1024, "minSize": 100, "overlap": 200}'),
|
||||
|
||||
// Soft delete support
|
||||
deletedAt: timestamp('deleted_at'),
|
||||
|
||||
// Metadata and timestamps
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
// Primary access patterns
|
||||
userIdIdx: index('kb_user_id_idx').on(table.userId),
|
||||
workspaceIdIdx: index('kb_workspace_id_idx').on(table.workspaceId),
|
||||
// Composite index for user's workspaces
|
||||
userWorkspaceIdx: index('kb_user_workspace_idx').on(table.userId, table.workspaceId),
|
||||
// Index for soft delete filtering
|
||||
deletedAtIdx: index('kb_deleted_at_idx').on(table.deletedAt),
|
||||
})
|
||||
)
|
||||
|
||||
export const document = pgTable(
|
||||
'document',
|
||||
{
|
||||
id: text('id').primaryKey(),
|
||||
knowledgeBaseId: text('knowledge_base_id')
|
||||
.notNull()
|
||||
.references(() => knowledgeBase.id, { onDelete: 'cascade' }),
|
||||
|
||||
// File information
|
||||
filename: text('filename').notNull(),
|
||||
fileUrl: text('file_url').notNull(),
|
||||
fileSize: integer('file_size').notNull(), // Size in bytes
|
||||
mimeType: text('mime_type').notNull(), // e.g., 'application/pdf', 'text/plain'
|
||||
fileHash: text('file_hash'), // SHA-256 hash for deduplication
|
||||
|
||||
// Content statistics
|
||||
chunkCount: integer('chunk_count').notNull().default(0),
|
||||
tokenCount: integer('token_count').notNull().default(0),
|
||||
characterCount: integer('character_count').notNull().default(0),
|
||||
|
||||
// Document state
|
||||
enabled: boolean('enabled').notNull().default(true), // Enable/disable from knowledge base
|
||||
deletedAt: timestamp('deleted_at'), // Soft delete
|
||||
|
||||
// Timestamps
|
||||
uploadedAt: timestamp('uploaded_at').notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
// Primary access pattern - documents by knowledge base
|
||||
knowledgeBaseIdIdx: index('doc_kb_id_idx').on(table.knowledgeBaseId),
|
||||
// File deduplication
|
||||
fileHashIdx: index('doc_file_hash_idx').on(table.fileHash),
|
||||
// Search by filename (for search functionality)
|
||||
filenameIdx: index('doc_filename_idx').on(table.filename),
|
||||
// Order by upload date (for listing documents)
|
||||
kbUploadedAtIdx: index('doc_kb_uploaded_at_idx').on(table.knowledgeBaseId, table.uploadedAt),
|
||||
})
|
||||
)
|
||||
|
||||
export const embedding = pgTable(
|
||||
'embedding',
|
||||
{
|
||||
id: text('id').primaryKey(),
|
||||
knowledgeBaseId: text('knowledge_base_id')
|
||||
.notNull()
|
||||
.references(() => knowledgeBase.id, { onDelete: 'cascade' }),
|
||||
documentId: text('document_id')
|
||||
.notNull()
|
||||
.references(() => document.id, { onDelete: 'cascade' }),
|
||||
|
||||
// Chunk information
|
||||
chunkIndex: integer('chunk_index').notNull(),
|
||||
chunkHash: text('chunk_hash').notNull(),
|
||||
content: text('content').notNull(),
|
||||
contentLength: integer('content_length').notNull(),
|
||||
tokenCount: integer('token_count').notNull(),
|
||||
|
||||
// Vector embeddings - optimized for text-embedding-3-small with HNSW support
|
||||
embedding: vector('embedding', { dimensions: 1536 }), // For text-embedding-3-small
|
||||
embeddingModel: text('embedding_model').notNull().default('text-embedding-3-small'),
|
||||
|
||||
// Chunk boundaries and overlap
|
||||
startOffset: integer('start_offset').notNull(),
|
||||
endOffset: integer('end_offset').notNull(),
|
||||
overlapTokens: integer('overlap_tokens').notNull().default(0),
|
||||
|
||||
// Rich metadata for advanced filtering
|
||||
metadata: jsonb('metadata').notNull().default('{}'),
|
||||
|
||||
// Search optimization
|
||||
searchRank: decimal('search_rank').default('1.0'),
|
||||
accessCount: integer('access_count').notNull().default(0),
|
||||
lastAccessedAt: timestamp('last_accessed_at'),
|
||||
|
||||
// Quality metrics
|
||||
qualityScore: decimal('quality_score'),
|
||||
|
||||
// Chunk state - enable/disable from knowledge base
|
||||
enabled: boolean('enabled').notNull().default(true),
|
||||
|
||||
// Full-text search support - generated tsvector column
|
||||
contentTsv: tsvector('content_tsv').generatedAlwaysAs(
|
||||
(): SQL => sql`to_tsvector('english', ${embedding.content})`
|
||||
),
|
||||
|
||||
// Timestamps
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
// Primary vector search pattern
|
||||
kbIdIdx: index('emb_kb_id_idx').on(table.knowledgeBaseId),
|
||||
|
||||
// Document-level access
|
||||
docIdIdx: index('emb_doc_id_idx').on(table.documentId),
|
||||
|
||||
// Chunk ordering within documents
|
||||
docChunkIdx: uniqueIndex('emb_doc_chunk_idx').on(table.documentId, table.chunkIndex),
|
||||
|
||||
// Model-specific queries for A/B testing or migrations
|
||||
kbModelIdx: index('emb_kb_model_idx').on(table.knowledgeBaseId, table.embeddingModel),
|
||||
|
||||
// Deduplication
|
||||
chunkHashIdx: index('emb_chunk_hash_idx').on(table.chunkHash),
|
||||
|
||||
// Access patterns for hot data
|
||||
kbAccessIdx: index('emb_kb_access_idx').on(table.knowledgeBaseId, table.lastAccessedAt),
|
||||
|
||||
// Search rank optimization
|
||||
kbRankIdx: index('emb_kb_rank_idx').on(table.knowledgeBaseId, table.searchRank),
|
||||
|
||||
// Enabled state filtering indexes (for chunk enable/disable functionality)
|
||||
kbEnabledIdx: index('emb_kb_enabled_idx').on(table.knowledgeBaseId, table.enabled),
|
||||
docEnabledIdx: index('emb_doc_enabled_idx').on(table.documentId, table.enabled),
|
||||
|
||||
// Vector similarity search indexes (HNSW) - optimized for small embeddings
|
||||
embeddingVectorHnswIdx: index('embedding_vector_hnsw_idx')
|
||||
.using('hnsw', table.embedding.op('vector_cosine_ops'))
|
||||
.with({
|
||||
m: 16,
|
||||
ef_construction: 64,
|
||||
}),
|
||||
|
||||
// GIN index for JSONB metadata queries
|
||||
metadataGinIdx: index('emb_metadata_gin_idx').using('gin', table.metadata),
|
||||
|
||||
// Full-text search index
|
||||
contentFtsIdx: index('emb_content_fts_idx').using('gin', table.contentTsv),
|
||||
|
||||
// Ensure embedding exists (simplified since we only support one model)
|
||||
embeddingNotNullCheck: check('embedding_not_null_check', sql`"embedding" IS NOT NULL`),
|
||||
})
|
||||
)
|
||||
|
||||
367
apps/sim/lib/document-processor.ts
Normal file
367
apps/sim/lib/document-processor.ts
Normal file
@@ -0,0 +1,367 @@
|
||||
import { RecursiveChunker } from 'chonkie/cloud'
|
||||
import type { RecursiveChunk } from 'chonkie/types'
|
||||
import { env } from '@/lib/env'
|
||||
import { isSupportedFileType, parseBuffer, parseFile } from '@/lib/file-parsers'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { type CustomS3Config, getPresignedUrlWithConfig, uploadToS3 } from '@/lib/uploads/s3-client'
|
||||
import { mistralParserTool } from '@/tools/mistral/parser'
|
||||
|
||||
const logger = createLogger('DocumentProcessor')
|
||||
|
||||
const S3_KB_CONFIG: CustomS3Config = {
|
||||
bucket: env.S3_KB_BUCKET_NAME || '',
|
||||
region: env.AWS_REGION || '',
|
||||
}
|
||||
|
||||
export interface ProcessedDocument {
|
||||
content: string
|
||||
chunks: RecursiveChunk[]
|
||||
metadata: {
|
||||
filename: string
|
||||
fileSize: number
|
||||
mimeType: string
|
||||
characterCount: number
|
||||
tokenCount: number
|
||||
chunkCount: number
|
||||
processingMethod: 'file-parser' | 'mistral-ocr'
|
||||
s3Url?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface DocumentProcessingOptions {
|
||||
knowledgeBaseId: string
|
||||
chunkSize?: number
|
||||
minCharactersPerChunk?: number
|
||||
recipe?: string
|
||||
lang?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the appropriate processing method for a file based on its type
|
||||
*/
|
||||
function determineProcessingMethod(
|
||||
mimeType: string,
|
||||
filename: string
|
||||
): 'file-parser' | 'mistral-ocr' {
|
||||
// Use Mistral OCR for PDFs since it provides better results
|
||||
if (mimeType === 'application/pdf' || filename.toLowerCase().endsWith('.pdf')) {
|
||||
return 'mistral-ocr'
|
||||
}
|
||||
|
||||
// Extract file extension for supported file type check
|
||||
const extension = filename.split('.').pop()?.toLowerCase()
|
||||
|
||||
// Use file parser for supported non-PDF types
|
||||
if (extension && isSupportedFileType(extension)) {
|
||||
return 'file-parser'
|
||||
}
|
||||
|
||||
// For unsupported types, try file parser first (it might handle text files)
|
||||
return 'file-parser'
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a document using the appropriate method (file parser or Mistral OCR)
|
||||
*/
|
||||
async function parseDocument(
|
||||
fileUrl: string,
|
||||
filename: string,
|
||||
mimeType: string
|
||||
): Promise<{ content: string; processingMethod: 'file-parser' | 'mistral-ocr'; s3Url?: string }> {
|
||||
const processingMethod = determineProcessingMethod(mimeType, filename)
|
||||
|
||||
logger.info(`Processing document "${filename}" using ${processingMethod}`)
|
||||
|
||||
try {
|
||||
if (processingMethod === 'mistral-ocr') {
|
||||
// Use Mistral OCR for PDFs - but first ensure we have an HTTPS URL
|
||||
const mistralApiKey = env.MISTRAL_API_KEY
|
||||
if (!mistralApiKey) {
|
||||
throw new Error('MISTRAL_API_KEY not configured')
|
||||
}
|
||||
|
||||
let httpsUrl = fileUrl
|
||||
let s3Url: string | undefined
|
||||
|
||||
// If the URL is not HTTPS, we need to upload to S3 first
|
||||
if (!fileUrl.startsWith('https://')) {
|
||||
logger.info(`Uploading "${filename}" to S3 for Mistral OCR access`)
|
||||
|
||||
// Download the file content
|
||||
const response = await fetch(fileUrl)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download file for S3 upload: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await response.arrayBuffer())
|
||||
|
||||
// Always upload to S3 for Mistral OCR, even in development
|
||||
if (!S3_KB_CONFIG.bucket || !S3_KB_CONFIG.region) {
|
||||
throw new Error(
|
||||
'S3 configuration missing: AWS_REGION and S3_KB_BUCKET_NAME environment variables are required for PDF processing with Mistral OCR'
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
// Upload to S3
|
||||
const s3Result = await uploadToS3(buffer, filename, mimeType, S3_KB_CONFIG)
|
||||
// Generate presigned URL with 15 minutes expiration
|
||||
httpsUrl = await getPresignedUrlWithConfig(s3Result.key, S3_KB_CONFIG, 900)
|
||||
s3Url = httpsUrl
|
||||
logger.info(`Successfully uploaded to S3 for Mistral OCR: ${s3Result.key}`)
|
||||
} catch (uploadError) {
|
||||
logger.error('Failed to upload to S3 for Mistral OCR:', uploadError)
|
||||
throw new Error(
|
||||
`S3 upload failed: ${uploadError instanceof Error ? uploadError.message : 'Unknown error'}. S3 upload is required for PDF processing with Mistral OCR.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!mistralParserTool.request?.body) {
|
||||
throw new Error('Mistral parser tool not properly configured')
|
||||
}
|
||||
|
||||
const requestBody = mistralParserTool.request.body({
|
||||
filePath: httpsUrl,
|
||||
apiKey: mistralApiKey,
|
||||
resultType: 'text',
|
||||
})
|
||||
|
||||
// Make the actual API call to Mistral
|
||||
const response = await fetch('https://api.mistral.ai/v1/ocr', {
|
||||
method: mistralParserTool.request.method,
|
||||
headers: mistralParserTool.request.headers({
|
||||
filePath: httpsUrl,
|
||||
apiKey: mistralApiKey,
|
||||
resultType: 'text',
|
||||
}),
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
||||
if (!mistralParserTool.transformResponse) {
|
||||
throw new Error('Mistral parser transform function not available')
|
||||
}
|
||||
|
||||
const result = await mistralParserTool.transformResponse(response, {
|
||||
filePath: httpsUrl,
|
||||
apiKey: mistralApiKey,
|
||||
resultType: 'text',
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error('Mistral OCR processing failed')
|
||||
}
|
||||
|
||||
return {
|
||||
content: result.output.content,
|
||||
processingMethod: 'mistral-ocr',
|
||||
s3Url,
|
||||
}
|
||||
}
|
||||
// Use file parser for other supported types
|
||||
let content: string
|
||||
|
||||
if (fileUrl.startsWith('http')) {
|
||||
// Download the file and parse buffer
|
||||
const response = await fetch(fileUrl)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download file: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await response.arrayBuffer())
|
||||
const extension = filename.split('.').pop()?.toLowerCase()
|
||||
|
||||
if (!extension) {
|
||||
throw new Error('Could not determine file extension')
|
||||
}
|
||||
|
||||
const parseResult = await parseBuffer(buffer, extension)
|
||||
content = parseResult.content
|
||||
} else {
|
||||
// Local file path
|
||||
const parseResult = await parseFile(fileUrl)
|
||||
content = parseResult.content
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
processingMethod: 'file-parser',
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to parse document "${filename}":`, error)
|
||||
throw new Error(
|
||||
`Document parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk text content using RecursiveChunker
|
||||
*/
|
||||
async function chunkContent(
|
||||
content: string,
|
||||
options: DocumentProcessingOptions
|
||||
): Promise<RecursiveChunk[]> {
|
||||
const apiKey = env.CHONKIE_API_KEY
|
||||
if (!apiKey) {
|
||||
throw new Error('CHONKIE_API_KEY not configured')
|
||||
}
|
||||
|
||||
const chunker = new RecursiveChunker(apiKey, {
|
||||
chunkSize: options.chunkSize || 512,
|
||||
recipe: options.recipe || 'default',
|
||||
lang: options.lang || 'en',
|
||||
minCharactersPerChunk: options.minCharactersPerChunk || 24,
|
||||
})
|
||||
|
||||
try {
|
||||
logger.info('Chunking content with RecursiveChunker', {
|
||||
contentLength: content.length,
|
||||
chunkSize: options.chunkSize || 512,
|
||||
})
|
||||
|
||||
const chunks = await chunker.chunk({ text: content })
|
||||
|
||||
logger.info(`Successfully created ${chunks.length} chunks`)
|
||||
return chunks as RecursiveChunk[]
|
||||
} catch (error) {
|
||||
logger.error('Chunking failed:', error)
|
||||
throw new Error(
|
||||
`Text chunking failed: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate token count estimation (rough approximation: 4 chars per token)
|
||||
*/
|
||||
function estimateTokenCount(text: string): number {
|
||||
return Math.ceil(text.length / 4)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single document: parse content and create chunks
|
||||
*/
|
||||
export async function processDocument(
|
||||
fileUrl: string,
|
||||
filename: string,
|
||||
mimeType: string,
|
||||
fileSize: number,
|
||||
options: DocumentProcessingOptions
|
||||
): Promise<ProcessedDocument> {
|
||||
const startTime = Date.now()
|
||||
logger.info(`Starting document processing for "${filename}"`)
|
||||
|
||||
try {
|
||||
// Step 1: Parse the document
|
||||
const { content, processingMethod, s3Url } = await parseDocument(fileUrl, filename, mimeType)
|
||||
|
||||
if (!content || content.trim().length === 0) {
|
||||
throw new Error('No content extracted from document')
|
||||
}
|
||||
|
||||
// Step 2: Chunk the content
|
||||
const chunks = await chunkContent(content, options)
|
||||
|
||||
if (chunks.length === 0) {
|
||||
throw new Error('No chunks created from content')
|
||||
}
|
||||
|
||||
// Step 3: Calculate metadata
|
||||
const characterCount = content.length
|
||||
const tokenCount = estimateTokenCount(content)
|
||||
const chunkCount = chunks.length
|
||||
|
||||
const processedDocument: ProcessedDocument = {
|
||||
content,
|
||||
chunks,
|
||||
metadata: {
|
||||
filename,
|
||||
fileSize,
|
||||
mimeType,
|
||||
characterCount,
|
||||
tokenCount,
|
||||
chunkCount,
|
||||
processingMethod,
|
||||
s3Url,
|
||||
},
|
||||
}
|
||||
|
||||
const processingTime = Date.now() - startTime
|
||||
logger.info(`Document processing completed for "${filename}"`, {
|
||||
processingTime: `${processingTime}ms`,
|
||||
contentLength: characterCount,
|
||||
chunkCount,
|
||||
tokenCount,
|
||||
processingMethod,
|
||||
})
|
||||
|
||||
return processedDocument
|
||||
} catch (error) {
|
||||
const processingTime = Date.now() - startTime
|
||||
logger.error(`Document processing failed for "${filename}" after ${processingTime}ms:`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process multiple documents in parallel
|
||||
*/
|
||||
export async function processDocuments(
|
||||
documents: Array<{
|
||||
fileUrl: string
|
||||
filename: string
|
||||
mimeType: string
|
||||
fileSize: number
|
||||
}>,
|
||||
options: DocumentProcessingOptions
|
||||
): Promise<ProcessedDocument[]> {
|
||||
const startTime = Date.now()
|
||||
logger.info(`Starting batch processing of ${documents.length} documents`)
|
||||
|
||||
try {
|
||||
// Process all documents in parallel
|
||||
const processingPromises = documents.map((doc) =>
|
||||
processDocument(doc.fileUrl, doc.filename, doc.mimeType, doc.fileSize, options)
|
||||
)
|
||||
|
||||
const results = await Promise.allSettled(processingPromises)
|
||||
|
||||
// Separate successful and failed results
|
||||
const successfulResults: ProcessedDocument[] = []
|
||||
const errors: string[] = []
|
||||
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
successfulResults.push(result.value)
|
||||
} else {
|
||||
const filename = documents[index].filename
|
||||
const errorMessage =
|
||||
result.reason instanceof Error ? result.reason.message : 'Unknown error'
|
||||
errors.push(`${filename}: ${errorMessage}`)
|
||||
logger.error(`Failed to process document "${filename}":`, result.reason)
|
||||
}
|
||||
})
|
||||
|
||||
const processingTime = Date.now() - startTime
|
||||
logger.info(`Batch processing completed in ${processingTime}ms`, {
|
||||
totalDocuments: documents.length,
|
||||
successful: successfulResults.length,
|
||||
failed: errors.length,
|
||||
})
|
||||
|
||||
if (errors.length > 0) {
|
||||
logger.warn('Some documents failed to process:', errors)
|
||||
}
|
||||
|
||||
if (successfulResults.length === 0) {
|
||||
throw new Error(`All documents failed to process. Errors: ${errors.join('; ')}`)
|
||||
}
|
||||
|
||||
return successfulResults
|
||||
} catch (error) {
|
||||
const processingTime = Date.now() - startTime
|
||||
logger.error(`Batch processing failed after ${processingTime}ms:`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ export const env = createEnv({
|
||||
OPENAI_API_KEY_1: z.string().min(1).optional(),
|
||||
OPENAI_API_KEY_2: z.string().min(1).optional(),
|
||||
OPENAI_API_KEY_3: z.string().min(1).optional(),
|
||||
MISTRAL_API_KEY: z.string().min(1).optional(),
|
||||
ANTHROPIC_API_KEY_1: z.string().min(1).optional(),
|
||||
ANTHROPIC_API_KEY_2: z.string().min(1).optional(),
|
||||
ANTHROPIC_API_KEY_3: z.string().min(1).optional(),
|
||||
@@ -66,10 +67,12 @@ export const env = createEnv({
|
||||
AWS_SECRET_ACCESS_KEY: z.string().optional(),
|
||||
S3_BUCKET_NAME: z.string().optional(),
|
||||
S3_LOGS_BUCKET_NAME: z.string().optional(),
|
||||
S3_KB_BUCKET_NAME: z.string().optional(),
|
||||
CRON_SECRET: z.string().optional(),
|
||||
FREE_PLAN_LOG_RETENTION_DAYS: z.string().optional(),
|
||||
NODE_ENV: z.string().optional(),
|
||||
GITHUB_TOKEN: z.string().optional(),
|
||||
CHONKIE_API_KEY: z.string().min(1).optional(),
|
||||
|
||||
// OAuth blocks (all optional)
|
||||
GOOGLE_CLIENT_ID: z.string().optional(),
|
||||
|
||||
@@ -44,6 +44,14 @@ export interface FileInfo {
|
||||
type: string // MIME type
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom S3 configuration
|
||||
*/
|
||||
export interface CustomS3Config {
|
||||
bucket: string
|
||||
region: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file to S3
|
||||
* @param file Buffer containing file data
|
||||
@@ -57,7 +65,46 @@ export async function uploadToS3(
|
||||
fileName: string,
|
||||
contentType: string,
|
||||
size?: number
|
||||
): Promise<FileInfo>
|
||||
|
||||
/**
|
||||
* Upload a file to S3 with custom bucket configuration
|
||||
* @param file Buffer containing file data
|
||||
* @param fileName Original file name
|
||||
* @param contentType MIME type of the file
|
||||
* @param customConfig Custom S3 configuration (bucket and region)
|
||||
* @param size File size in bytes (optional, will use buffer length if not provided)
|
||||
* @returns Object with file information
|
||||
*/
|
||||
export async function uploadToS3(
|
||||
file: Buffer,
|
||||
fileName: string,
|
||||
contentType: string,
|
||||
customConfig: CustomS3Config,
|
||||
size?: number
|
||||
): Promise<FileInfo>
|
||||
|
||||
export async function uploadToS3(
|
||||
file: Buffer,
|
||||
fileName: string,
|
||||
contentType: string,
|
||||
configOrSize?: CustomS3Config | number,
|
||||
size?: number
|
||||
): Promise<FileInfo> {
|
||||
// Handle overloaded parameters
|
||||
let config: CustomS3Config
|
||||
let fileSize: number
|
||||
|
||||
if (typeof configOrSize === 'object') {
|
||||
// Custom config provided
|
||||
config = configOrSize
|
||||
fileSize = size ?? file.length
|
||||
} else {
|
||||
// Use default config
|
||||
config = { bucket: S3_CONFIG.bucket, region: S3_CONFIG.region }
|
||||
fileSize = configOrSize ?? file.length
|
||||
}
|
||||
|
||||
// Create a unique filename with timestamp to prevent collisions
|
||||
// Use a simple timestamp without directory structure
|
||||
const safeFileName = fileName.replace(/\s+/g, '-') // Replace spaces with hyphens
|
||||
@@ -68,7 +115,7 @@ export async function uploadToS3(
|
||||
// Upload the file to S3
|
||||
await s3Client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: S3_CONFIG.bucket,
|
||||
Bucket: config.bucket,
|
||||
Key: uniqueKey,
|
||||
Body: file,
|
||||
ContentType: contentType,
|
||||
@@ -87,7 +134,7 @@ export async function uploadToS3(
|
||||
path: servePath,
|
||||
key: uniqueKey,
|
||||
name: fileName,
|
||||
size: size ?? file.length,
|
||||
size: fileSize,
|
||||
type: contentType,
|
||||
}
|
||||
}
|
||||
@@ -107,6 +154,26 @@ export async function getPresignedUrl(key: string, expiresIn = 3600) {
|
||||
return getSignedUrl(getS3Client(), command, { expiresIn })
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a presigned URL for direct file access with custom bucket
|
||||
* @param key S3 object key
|
||||
* @param customConfig Custom S3 configuration
|
||||
* @param expiresIn Time in seconds until URL expires
|
||||
* @returns Presigned URL
|
||||
*/
|
||||
export async function getPresignedUrlWithConfig(
|
||||
key: string,
|
||||
customConfig: CustomS3Config,
|
||||
expiresIn = 3600
|
||||
) {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: customConfig.bucket,
|
||||
Key: key,
|
||||
})
|
||||
|
||||
return getSignedUrl(getS3Client(), command, { expiresIn })
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file from S3
|
||||
* @param key S3 object key
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"ai": "^4.3.2",
|
||||
"better-auth": "^1.2.8-beta.3",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"chonkie": "^0.2.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
|
||||
3
apps/sim/tools/knowledge/index.ts
Normal file
3
apps/sim/tools/knowledge/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { knowledgeSearchTool } from './search'
|
||||
|
||||
export { knowledgeSearchTool }
|
||||
92
apps/sim/tools/knowledge/search.ts
Normal file
92
apps/sim/tools/knowledge/search.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { ToolConfig } from '../types'
|
||||
import type { KnowledgeSearchResponse } from './types'
|
||||
|
||||
export const knowledgeSearchTool: ToolConfig<any, KnowledgeSearchResponse> = {
|
||||
id: 'knowledge_search',
|
||||
name: 'Knowledge Search',
|
||||
description: 'Search for similar content in a knowledge base using vector similarity',
|
||||
version: '1.0.0',
|
||||
params: {
|
||||
knowledgeBaseId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'ID of the knowledge base to search in',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Search query text',
|
||||
},
|
||||
topK: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Number of most similar results to return (1-100)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: () => '/api/knowledge/search',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
knowledgeBaseId: params.knowledgeBaseId,
|
||||
query: params.query,
|
||||
topK: params.topK ? Number.parseInt(params.topK.toString()) : 10,
|
||||
}),
|
||||
isInternalRoute: true,
|
||||
},
|
||||
transformResponse: async (response): Promise<KnowledgeSearchResponse> => {
|
||||
try {
|
||||
const result = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
const errorMessage =
|
||||
result.error?.message || result.message || 'Failed to perform vector search'
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
const data = result.data || result
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
results: data.results || [],
|
||||
query: data.query,
|
||||
knowledgeBaseId: data.knowledgeBaseId,
|
||||
topK: data.topK,
|
||||
totalResults: data.totalResults || 0,
|
||||
message: `Found ${data.totalResults || 0} similar results`,
|
||||
},
|
||||
}
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
results: [],
|
||||
query: '',
|
||||
knowledgeBaseId: '',
|
||||
topK: 0,
|
||||
totalResults: 0,
|
||||
message: `Vector search failed: ${error.message || 'Unknown error'}`,
|
||||
},
|
||||
error: `Vector search failed: ${error.message || 'Unknown error'}`,
|
||||
}
|
||||
}
|
||||
},
|
||||
transformError: async (error): Promise<KnowledgeSearchResponse> => {
|
||||
const errorMessage = `Vector search failed: ${error.message || 'Unknown error'}`
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
results: [],
|
||||
query: '',
|
||||
knowledgeBaseId: '',
|
||||
topK: 0,
|
||||
totalResults: 0,
|
||||
message: errorMessage,
|
||||
},
|
||||
error: errorMessage,
|
||||
}
|
||||
},
|
||||
}
|
||||
27
apps/sim/tools/knowledge/types.ts
Normal file
27
apps/sim/tools/knowledge/types.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export interface KnowledgeSearchResult {
|
||||
id: string
|
||||
content: string
|
||||
documentId: string
|
||||
chunkIndex: number
|
||||
metadata: Record<string, any>
|
||||
similarity: number
|
||||
}
|
||||
|
||||
export interface KnowledgeSearchResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
results: KnowledgeSearchResult[]
|
||||
query: string
|
||||
knowledgeBaseId: string
|
||||
topK: number
|
||||
totalResults: number
|
||||
message: string
|
||||
}
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface KnowledgeSearchParams {
|
||||
knowledgeBaseId: string
|
||||
query: string
|
||||
topK?: number
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import { requestTool as httpRequest } from './http'
|
||||
import { contactsTool as hubspotContacts } from './hubspot/contacts'
|
||||
import { readUrlTool } from './jina'
|
||||
import { jiraBulkRetrieveTool, jiraRetrieveTool, jiraUpdateTool, jiraWriteTool } from './jira'
|
||||
import { knowledgeSearchTool } from './knowledge'
|
||||
import { linearCreateIssueTool, linearReadIssuesTool } from './linear'
|
||||
import { linkupSearchTool } from './linkup'
|
||||
import { mem0AddMemoriesTool, mem0GetMemoriesTool, mem0SearchMemoriesTool } from './mem0'
|
||||
@@ -177,6 +178,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
memory_get: memoryGetTool,
|
||||
memory_get_all: memoryGetAllTool,
|
||||
memory_delete: memoryDeleteTool,
|
||||
knowledge_search: knowledgeSearchTool,
|
||||
elevenlabs_tts: elevenLabsTtsTool,
|
||||
s3_get_object: s3GetObjectTool,
|
||||
telegram_message: telegramMessageTool,
|
||||
|
||||
246
bun.lock
246
bun.lock
@@ -33,6 +33,7 @@
|
||||
"fumadocs-mdx": "^11.5.6",
|
||||
"fumadocs-ui": "^15.0.16",
|
||||
"lucide-react": "^0.511.0",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "^15.2.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.1.0",
|
||||
@@ -98,6 +99,7 @@
|
||||
"ai": "^4.3.2",
|
||||
"better-auth": "^1.2.8-beta.3",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"chonkie": "^0.2.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
@@ -145,6 +147,7 @@
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/node": "^22",
|
||||
"@types/node": "^22",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
@@ -251,12 +254,18 @@
|
||||
|
||||
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-MNGwOJDQU0jpvsLLPSuPQDhPtDzFTc/k7rLmiKoPrIlgb3Y8pSF4crpJ+ZH3+xod2NWyyOVMEMQeMaKFFdMaKw=="],
|
||||
|
||||
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.812.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.812.0", "@aws-sdk/credential-provider-node": "3.812.0", "@aws-sdk/middleware-bucket-endpoint": "3.808.0", "@aws-sdk/middleware-expect-continue": "3.804.0", "@aws-sdk/middleware-flexible-checksums": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-location-constraint": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-sdk-s3": "3.812.0", "@aws-sdk/middleware-ssec": "3.804.0", "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/signature-v4-multi-region": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.812.0", "@aws-sdk/xml-builder": "3.804.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/eventstream-serde-browser": "^4.0.2", "@smithy/eventstream-serde-config-resolver": "^4.1.0", "@smithy/eventstream-serde-node": "^4.0.2", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-blob-browser": "^4.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/hash-stream-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/md5-js": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.3", "tslib": "^2.6.2" } }, "sha512-kHgw9JDXNPLa/mHtWpOd5btBVXFSe+wwp1Ed9+bqz9uLkv0iV4joZrdQwnydkO8zlTs60Sc5ez+P2OiZ76i2Qg=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker": ["@aws-sdk/client-sagemaker@3.820.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.3", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-cGSWgLrxd4YUru27dTLW06DA9l2cDhVkCXO6SrRLG101sCQdCS+QZPKDLGScLYgDn9OK+IPqJsak5kPVwYZLJQ=="],
|
||||
|
||||
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.812.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA=="],
|
||||
|
||||
"@aws-sdk/core": ["@aws-sdk/core@3.812.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/core": "^3.3.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.817.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-+dzgWGmdmMNDdeSF+VvONN+hwqoGKX5A6Z3+siMO4CIoKWN7u5nDOx/JLjTGdVQji3522pJjJ+o9veQJNWOMRg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g=="],
|
||||
|
||||
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw=="],
|
||||
@@ -271,6 +280,8 @@
|
||||
|
||||
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.812.0", "", { "dependencies": { "@aws-sdk/core": "3.812.0", "@aws-sdk/nested-clients": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg=="],
|
||||
|
||||
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.817.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.817.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/credential-provider-cognito-identity": "3.817.0", "@aws-sdk/credential-provider-env": "3.816.0", "@aws-sdk/credential-provider-http": "3.816.0", "@aws-sdk/credential-provider-ini": "3.817.0", "@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/credential-provider-process": "3.816.0", "@aws-sdk/credential-provider-sso": "3.817.0", "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/node-config-provider": "^4.1.1", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-i6Q2MyktWHG4YG+EmLlnXTgNVjW9/yeNHSKzF55GTho5fjqfU+t9beJfuMWclanRCifamm3N5e5OCm52rVDdTQ=="],
|
||||
|
||||
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.808.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@aws-sdk/util-arn-parser": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "@smithy/util-config-provider": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wEPlNcs8dir9lXbuviEGtSzYSxG/NRKQrJk5ybOc7OpPGHovsN+QhDOdY3lcjOFdwMTiMIG9foUkPz3zBpLB1A=="],
|
||||
|
||||
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.804.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-YW1hySBolALMII6C8y7Z0CRG2UX1dGJjLEBNFeefhO/xP7ZuE1dvnmfJGaEuBMnvc3wkRS63VZ3aqX6sevM1CA=="],
|
||||
@@ -293,10 +304,14 @@
|
||||
|
||||
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.812.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-FS/fImbEpJU3cXtBGR9fyVd+CP51eNKlvTMi3f4/6lSk3RmHjudNC9yEF/og3jtpT3O+7vsNOUW9mHco5IjdQQ=="],
|
||||
|
||||
"@aws-sdk/protocol-http": ["@aws-sdk/protocol-http@3.374.0", "", { "dependencies": { "@smithy/protocol-http": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-9WpRUbINdGroV3HiZZIBoJvL2ndoWk39OfwxWs2otxByppJZNN14bg/lvCx5e8ggHUti7IBk5rb0nqQZ4m05pg=="],
|
||||
|
||||
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.808.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.2", "tslib": "^2.6.2" } }, "sha512-9x2QWfphkARZY5OGkl9dJxZlSlYM2l5inFeo2bKntGuwg4A4YUe5h7d5yJ6sZbam9h43eBrkOdumx03DAkQF9A=="],
|
||||
|
||||
"@aws-sdk/s3-request-presigner": ["@aws-sdk/s3-request-presigner@3.812.0", "", { "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-format-url": "3.804.0", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-OpyANELjcD2oknkd3/qWanaRaZDx4SSV6NwYuWIk+fuxDZ+KxZZrrfue1X7OAdaP2TdSapbs7xLisxtTuptWYg=="],
|
||||
|
||||
"@aws-sdk/signature-v4": ["@aws-sdk/signature-v4@3.374.0", "", { "dependencies": { "@smithy/signature-v4": "^1.0.1", "tslib": "^2.5.0" } }, "sha512-2xLJvSdzcZZAg0lsDLUAuSQuihzK0dcxIK7WmfuJeF7DGKJFmp9czQmz5f3qiDz6IDQzvgK1M9vtJSVCslJbyQ=="],
|
||||
|
||||
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.812.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JTpk3ZHf7TXYbicKfOKi+VrsBTqcAszg9QR9fQmT9aCxPp39gsF3WsXq7NjepwZ5So11ixGIsPE/jtMym399QQ=="],
|
||||
|
||||
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.812.0", "", { "dependencies": { "@aws-sdk/nested-clients": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ=="],
|
||||
@@ -315,6 +330,8 @@
|
||||
|
||||
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.812.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg=="],
|
||||
|
||||
"@aws-sdk/util-utf8-browser": ["@aws-sdk/util-utf8-browser@3.259.0", "", { "dependencies": { "tslib": "^2.3.1" } }, "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw=="],
|
||||
|
||||
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.804.0", "", { "dependencies": { "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JbGWp36IG9dgxtvC6+YXwt5WDZYfuamWFtVfK6fQpnmL96dx+GUPOXPKRWdw67WLKf2comHY28iX2d3z35I53Q=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
@@ -481,6 +498,14 @@
|
||||
|
||||
"@hookform/resolvers": ["@hookform/resolvers@4.1.3", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.0.0" } }, "sha512-Jsv6UOWYTrEFJ/01ZrnwVXs7KDvP8XIo115i++5PWvNkNvkrsTfGiLS6w+eJ57CYtUtDQalUWovCZDHFJ8u1VQ=="],
|
||||
|
||||
"@huggingface/hub": ["@huggingface/hub@2.1.0", "", { "dependencies": { "@huggingface/tasks": "^0.19.6" }, "bin": { "hfjs": "dist/cli.js" } }, "sha512-L+125tAXwCOwVd8n4qQlT20ZJP7I5sEXOLNrIzpgkUG6eMIsGlxT+9hmmiCH/LGmjbXjlcRHoPB8GGT3zCws4A=="],
|
||||
|
||||
"@huggingface/jinja": ["@huggingface/jinja@0.4.1", "", {}, "sha512-3WXbMFaPkk03LRCM0z0sylmn8ddDm4ubjU7X+Hg4M2GOuMklwoGAFXp9V2keq7vltoB/c7McE5aHUVVddAewsw=="],
|
||||
|
||||
"@huggingface/tasks": ["@huggingface/tasks@0.19.11", "", {}, "sha512-oBhSgVlg7Pp643MsH8BiI3OAXIMJNxdSiMtv4mApRZV8dmAz8oasKhg6CVKIplO7vAO7F6dkmMn4bYM64I2A9w=="],
|
||||
|
||||
"@huggingface/transformers": ["@huggingface/transformers@3.5.1", "", { "dependencies": { "@huggingface/jinja": "^0.4.1", "onnxruntime-node": "1.21.0", "onnxruntime-web": "1.22.0-dev.20250409-89f8206ba4", "sharp": "^0.34.1" } }, "sha512-qWsPoJMBPYcrGuzRMVL//3dwcLXED9r+j+Hzw+hZpBfrMPdyq57ehNs7lew0D34Paj0DO0E0kZQjOZcIVN17hQ=="],
|
||||
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.1.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.1.0" }, "os": "darwin", "cpu": "x64" }, "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q=="],
|
||||
@@ -1303,6 +1328,8 @@
|
||||
|
||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||
|
||||
"@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="],
|
||||
|
||||
"@types/yargs": ["@types/yargs@15.0.19", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA=="],
|
||||
|
||||
"@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
|
||||
@@ -1445,6 +1472,8 @@
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="],
|
||||
|
||||
"bowser": ["bowser@2.11.0", "", {}, "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
@@ -1455,7 +1484,7 @@
|
||||
|
||||
"browserslist": ["browserslist@4.24.5", "", { "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw=="],
|
||||
|
||||
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
@@ -1507,6 +1536,8 @@
|
||||
|
||||
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"chonkie": ["chonkie@0.2.5", "", { "dependencies": { "@huggingface/hub": "^2.0.1", "@huggingface/transformers": "^3.5.1", "jsonschema": "^1.5.0" }, "optionalDependencies": { "cohere-ai": "^7.17.1", "openai": "^4.98.0", "tree-sitter-wasms": "^0.1.0", "web-tree-sitter": "^0.25.4" } }, "sha512-8+H0CFIGZ8ANxXLv37g9mXuwfxqxwdPNWwxxH+cYpzv9bLzGiqQhM6wlG764i9eiT1CXjphYSUqVHtfjrbEe5A=="],
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
|
||||
"chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="],
|
||||
@@ -1537,6 +1568,8 @@
|
||||
|
||||
"cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="],
|
||||
|
||||
"cohere-ai": ["cohere-ai@7.17.1", "", { "dependencies": { "@aws-sdk/client-sagemaker": "^3.583.0", "@aws-sdk/credential-providers": "^3.583.0", "@aws-sdk/protocol-http": "^3.374.0", "@aws-sdk/signature-v4": "^3.374.0", "convict": "^6.2.4", "form-data": "^4.0.0", "form-data-encoder": "^4.0.2", "formdata-node": "^6.0.3", "js-base64": "3.7.2", "node-fetch": "2.7.0", "qs": "6.11.2", "readable-stream": "^4.5.2", "url-join": "4.0.1" } }, "sha512-GI/uWVYYGIN3gdjJRlbjEaLJNJVXsUJyOlPqwBWgAmK18kP4CJoErxKwU0aLe3tHHOBcC2RqXe6PmGO0dz7dpQ=="],
|
||||
|
||||
"collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="],
|
||||
|
||||
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
||||
@@ -1563,6 +1596,8 @@
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"convict": ["convict@6.2.4", "", { "dependencies": { "lodash.clonedeep": "^4.5.0", "yargs-parser": "^20.2.7" } }, "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
||||
@@ -1657,6 +1692,10 @@
|
||||
|
||||
"defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
|
||||
|
||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
@@ -1667,6 +1706,8 @@
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
|
||||
"detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
|
||||
|
||||
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
@@ -1745,6 +1786,8 @@
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="],
|
||||
|
||||
"esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="],
|
||||
|
||||
"esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
|
||||
@@ -1825,6 +1868,8 @@
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flatbuffers": ["flatbuffers@25.2.10", "", {}, "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
|
||||
@@ -1877,8 +1922,12 @@
|
||||
|
||||
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
|
||||
|
||||
"global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="],
|
||||
|
||||
"globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
|
||||
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
||||
|
||||
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
||||
|
||||
"google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="],
|
||||
@@ -1897,8 +1946,12 @@
|
||||
|
||||
"gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="],
|
||||
|
||||
"guid-typescript": ["guid-typescript@1.0.9", "", {}, "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
@@ -2041,6 +2094,8 @@
|
||||
|
||||
"joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
|
||||
|
||||
"js-base64": ["js-base64@3.7.2", "", {}, "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||
@@ -2057,10 +2112,14 @@
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"jsondiffpatch": ["jsondiffpatch@0.6.0", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="],
|
||||
|
||||
"jsonschema": ["jsonschema@1.5.0", "", {}, "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw=="],
|
||||
|
||||
"jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
|
||||
|
||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
@@ -2119,6 +2178,8 @@
|
||||
|
||||
"lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
|
||||
|
||||
"lodash.clonedeep": ["lodash.clonedeep@4.5.0", "", {}, "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="],
|
||||
|
||||
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||
|
||||
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
|
||||
@@ -2143,6 +2204,7 @@
|
||||
|
||||
"lru-cache": ["lru-cache@11.1.0", "", {}, "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.511.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w=="],
|
||||
"lucide-react": ["lucide-react@0.511.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w=="],
|
||||
|
||||
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
|
||||
@@ -2161,6 +2223,8 @@
|
||||
|
||||
"marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="],
|
||||
|
||||
"matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"md-to-react-email": ["md-to-react-email@5.0.5", "", { "dependencies": { "marked": "7.0.4" }, "peerDependencies": { "react": "^18.0 || ^19.0" } }, "sha512-OvAXqwq57uOk+WZqFFNCMZz8yDp8BD3WazW1wAKHUrPbbdr89K9DWS6JXY09vd9xNdPNeurI8DU/X4flcfaD8A=="],
|
||||
@@ -2345,6 +2409,8 @@
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
|
||||
|
||||
"ollama-ai-provider": ["ollama-ai-provider@1.2.0", "", { "dependencies": { "@ai-sdk/provider": "^1.0.0", "@ai-sdk/provider-utils": "^2.0.0", "partial-json": "0.1.7" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-jTNFruwe3O/ruJeppI/quoOUxG7NA6blG3ZyQj3lei4+NnJo7bi3eIRWqlVpRlu/mbzbFXeJSBuYQWF6pzGKww=="],
|
||||
|
||||
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
|
||||
@@ -2357,6 +2423,12 @@
|
||||
|
||||
"oniguruma-to-es": ["oniguruma-to-es@4.3.3", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg=="],
|
||||
|
||||
"onnxruntime-common": ["onnxruntime-common@1.21.0", "", {}, "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ=="],
|
||||
|
||||
"onnxruntime-node": ["onnxruntime-node@1.21.0", "", { "dependencies": { "global-agent": "^3.0.0", "onnxruntime-common": "1.21.0", "tar": "^7.0.1" }, "os": [ "linux", "win32", "darwin", ] }, "sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw=="],
|
||||
|
||||
"onnxruntime-web": ["onnxruntime-web@1.22.0-dev.20250409-89f8206ba4", "", { "dependencies": { "flatbuffers": "^25.1.24", "guid-typescript": "^1.0.9", "long": "^5.2.3", "onnxruntime-common": "1.22.0-dev.20250409-89f8206ba4", "platform": "^1.3.6", "protobufjs": "^7.2.4" } }, "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ=="],
|
||||
|
||||
"openai": ["openai@4.100.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-9soq/wukv3utxcuD7TWFqKdKp0INWdeyhUCvxwrne5KwnxaCp4eHL4GdT/tMFhYolxgNhxFzg5GFwM331Z5CZg=="],
|
||||
|
||||
"openapi": ["openapi@1.0.1", "", { "dependencies": { "@types/jest": "^26.0.14", "change-case": "^4.1.1", "commander": "^6.1.0", "cosmiconfig": "^6.0.0", "is-url": "^1.2.4", "js-yaml": "^3.13.1", "node-fetch": "^2.6.0", "object-hash": "^2.0.3", "url-parse": "^1.4.7" }, "bin": { "openapi": "src/cli/index.js" } }, "sha512-hiQ6/K2Q2eFqlOoPQb8V2hzsVsbv31ipMCKfuwZQmqf+MnLzVUcYMBy0h/Y+Sv/HeDCTN4mf0GoOmET4EoJS8A=="],
|
||||
@@ -2441,6 +2513,8 @@
|
||||
|
||||
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
|
||||
|
||||
"platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="],
|
||||
|
||||
"playwright": ["playwright@1.52.0", "", { "dependencies": { "playwright-core": "1.52.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw=="],
|
||||
|
||||
"playwright-core": ["playwright-core@1.52.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg=="],
|
||||
@@ -2479,7 +2553,7 @@
|
||||
|
||||
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
||||
|
||||
"process": ["process@0.10.1", "", {}, "sha512-dyIett8dgGIZ/TXKUzeYExt7WA6ldDzys9vTDU/cCA9L17Ypme+KzS+NjQCjpn9xsvi/shbMC+yP/BcFMBz0NA=="],
|
||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||
|
||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
||||
|
||||
@@ -2551,7 +2625,7 @@
|
||||
|
||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||
|
||||
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
||||
|
||||
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
@@ -2617,6 +2691,8 @@
|
||||
|
||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||
|
||||
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
|
||||
|
||||
"rollup": ["rollup@4.35.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.35.0", "@rollup/rollup-android-arm64": "4.35.0", "@rollup/rollup-darwin-arm64": "4.35.0", "@rollup/rollup-darwin-x64": "4.35.0", "@rollup/rollup-freebsd-arm64": "4.35.0", "@rollup/rollup-freebsd-x64": "4.35.0", "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", "@rollup/rollup-linux-arm-musleabihf": "4.35.0", "@rollup/rollup-linux-arm64-gnu": "4.35.0", "@rollup/rollup-linux-arm64-musl": "4.35.0", "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", "@rollup/rollup-linux-riscv64-gnu": "4.35.0", "@rollup/rollup-linux-s390x-gnu": "4.35.0", "@rollup/rollup-linux-x64-gnu": "4.35.0", "@rollup/rollup-linux-x64-musl": "4.35.0", "@rollup/rollup-win32-arm64-msvc": "4.35.0", "@rollup/rollup-win32-ia32-msvc": "4.35.0", "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg=="],
|
||||
|
||||
"rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="],
|
||||
@@ -2653,8 +2729,12 @@
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],
|
||||
|
||||
"sentence-case": ["sentence-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case-first": "^2.0.2" } }, "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg=="],
|
||||
|
||||
"serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="],
|
||||
|
||||
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||
@@ -2735,7 +2815,7 @@
|
||||
|
||||
"string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
@@ -2825,6 +2905,8 @@
|
||||
|
||||
"tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="],
|
||||
|
||||
"tree-sitter-wasms": ["tree-sitter-wasms@0.1.12", "", { "dependencies": { "tree-sitter-wasms": "^0.1.11" } }, "sha512-N9Jp+dkB23Ul5Gw0utm+3pvG4km4Fxsi2jmtMFg7ivzwqWPlSyrYQIrOmcX+79taVfcHEA+NzP0hl7vXL8DNUQ=="],
|
||||
|
||||
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||
|
||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||
@@ -2885,6 +2967,8 @@
|
||||
|
||||
"upper-case-first": ["upper-case-first@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg=="],
|
||||
|
||||
"url-join": ["url-join@4.0.1", "", {}, "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="],
|
||||
|
||||
"url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
|
||||
|
||||
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
||||
@@ -2923,6 +3007,8 @@
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
|
||||
|
||||
"web-tree-sitter": ["web-tree-sitter@0.25.5", "", {}, "sha512-JXSE2AbBenAwD9fB0En0aDPRVd6/O//8dPKZrZm8L4aHskRrRamc0tPd1PKZxJluz5GeOAC3AD4WOvrSpV8AzQ=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
|
||||
|
||||
"webpack": ["webpack@5.99.8", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.14.0", "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.2", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ=="],
|
||||
@@ -2967,7 +3053,7 @@
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
"yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
@@ -2995,6 +3081,46 @@
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.816.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/core": "^3.3.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.817.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.816.0", "@aws-sdk/credential-provider-http": "3.816.0", "@aws-sdk/credential-provider-ini": "3.817.0", "@aws-sdk/credential-provider-process": "3.816.0", "@aws-sdk/credential-provider-sso": "3.817.0", "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.816.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/core": ["@aws-sdk/core@3.816.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/core": "^3.3.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.817.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.816.0", "@aws-sdk/credential-provider-http": "3.816.0", "@aws-sdk/credential-provider-ini": "3.817.0", "@aws-sdk/credential-provider-process": "3.816.0", "@aws-sdk/credential-provider-sso": "3.817.0", "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.816.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/core": ["@aws-sdk/core@3.816.0", "", { "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/core": "^3.3.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.817.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/credential-provider-env": "3.816.0", "@aws-sdk/credential-provider-http": "3.816.0", "@aws-sdk/credential-provider-process": "3.816.0", "@aws-sdk/credential-provider-sso": "3.817.0", "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.817.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.816.0", "@aws-sdk/credential-provider-http": "3.816.0", "@aws-sdk/credential-provider-ini": "3.817.0", "@aws-sdk/credential-provider-process": "3.816.0", "@aws-sdk/credential-provider-sso": "3.817.0", "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.817.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.817.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/token-providers": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.817.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ=="],
|
||||
|
||||
"@aws-sdk/protocol-http/@smithy/protocol-http": ["@smithy/protocol-http@1.2.0", "", { "dependencies": { "@smithy/types": "^1.2.0", "tslib": "^2.5.0" } }, "sha512-GfGfruksi3nXdFok5RhgtOnWe5f6BndzYfmEXISD+5gAGdayFGpjWu5pIqIweTudMtse20bGbc+7MFZXT1Tb8Q=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4": ["@smithy/signature-v4@1.1.0", "", { "dependencies": { "@smithy/eventstream-codec": "^1.1.0", "@smithy/is-array-buffer": "^1.1.0", "@smithy/types": "^1.2.0", "@smithy/util-hex-encoding": "^1.1.0", "@smithy/util-middleware": "^1.1.0", "@smithy/util-uri-escape": "^1.1.0", "@smithy/util-utf8": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-fDo3m7YqXBs7neciOePPd/X9LPm5QLlDMdIC4m1H6dgNLnXfLMFNIxEfPyohGA8VW9Wn4X8lygnPSGxDZSmp0Q=="],
|
||||
|
||||
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
@@ -3269,6 +3395,8 @@
|
||||
|
||||
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
||||
|
||||
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
|
||||
"bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
@@ -3277,6 +3405,12 @@
|
||||
|
||||
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"cohere-ai/form-data-encoder": ["form-data-encoder@4.0.2", "", {}, "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw=="],
|
||||
|
||||
"cohere-ai/formdata-node": ["formdata-node@6.0.3", "", {}, "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg=="],
|
||||
|
||||
"cohere-ai/qs": ["qs@6.11.2", "", { "dependencies": { "side-channel": "^1.0.4" } }, "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA=="],
|
||||
|
||||
"cosmiconfig/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
|
||||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
@@ -3309,6 +3443,8 @@
|
||||
|
||||
"groq-sdk/@types/node": ["@types/node@18.19.100", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA=="],
|
||||
|
||||
"hexer/process": ["process@0.10.1", "", {}, "sha512-dyIett8dgGIZ/TXKUzeYExt7WA6ldDzys9vTDU/cCA9L17Ypme+KzS+NjQCjpn9xsvi/shbMC+yP/BcFMBz0NA=="],
|
||||
|
||||
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
|
||||
"htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
@@ -3325,6 +3461,8 @@
|
||||
|
||||
"jsondiffpatch/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
|
||||
"jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
|
||||
"linebreak/base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="],
|
||||
|
||||
"lint-staged/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
@@ -3345,6 +3483,8 @@
|
||||
|
||||
"log-update/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
@@ -3353,6 +3493,10 @@
|
||||
|
||||
"node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"onnxruntime-web/long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"onnxruntime-web/onnxruntime-common": ["onnxruntime-common@1.22.0-dev.20250409-89f8206ba4", "", {}, "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ=="],
|
||||
|
||||
"openai/@types/node": ["@types/node@18.19.100", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA=="],
|
||||
|
||||
"openapi/commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="],
|
||||
@@ -3397,6 +3541,10 @@
|
||||
|
||||
"restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
|
||||
|
||||
"serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
"sim/lucide-react": ["lucide-react@0.479.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ=="],
|
||||
|
||||
"sim/tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="],
|
||||
@@ -3421,6 +3569,8 @@
|
||||
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
||||
|
||||
"sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
@@ -3439,6 +3589,8 @@
|
||||
|
||||
"webpack/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
@@ -3447,6 +3599,54 @@
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.817.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/credential-provider-env": "3.816.0", "@aws-sdk/credential-provider-http": "3.816.0", "@aws-sdk/credential-provider-process": "3.816.0", "@aws-sdk/credential-provider-sso": "3.817.0", "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.817.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.817.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/token-providers": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.817.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.817.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/credential-provider-env": "3.816.0", "@aws-sdk/credential-provider-http": "3.816.0", "@aws-sdk/credential-provider-process": "3.816.0", "@aws-sdk/credential-provider-sso": "3.817.0", "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.817.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.817.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/token-providers": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.817.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.817.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.816.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg=="],
|
||||
|
||||
"@aws-sdk/protocol-http/@smithy/protocol-http/@smithy/types": ["@smithy/types@1.2.0", "", { "dependencies": { "tslib": "^2.5.0" } }, "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/eventstream-codec": ["@smithy/eventstream-codec@1.1.0", "", { "dependencies": { "@aws-crypto/crc32": "3.0.0", "@smithy/types": "^1.2.0", "@smithy/util-hex-encoding": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-3tEbUb8t8an226jKB6V/Q2XU/J53lCwCzULuBPEaF4JjSh+FlCMp7TmogE/Aij5J9DwlsZ4VAD/IRDuQ/0ZtMw=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@1.1.0", "", { "dependencies": { "tslib": "^2.5.0" } }, "sha512-twpQ/n+3OWZJ7Z+xu43MJErmhB/WO/mMTnqR6PwWQShvSJ/emx5d1N59LQZk6ZpTAeuRWrc+eHhkzTp9NFjNRQ=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/types": ["@smithy/types@1.2.0", "", { "dependencies": { "tslib": "^2.5.0" } }, "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@1.1.0", "", { "dependencies": { "tslib": "^2.5.0" } }, "sha512-7UtIE9eH0u41zpB60Jzr0oNCQ3hMJUabMcKRUVjmyHTXiWDE4vjSqN6qlih7rCNeKGbioS7f/y2Jgym4QZcKFg=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-middleware": ["@smithy/util-middleware@1.1.0", "", { "dependencies": { "tslib": "^2.5.0" } }, "sha512-6hhckcBqVgjWAqLy2vqlPZ3rfxLDhFWEmM7oLh2POGvsi7j0tHkbN7w4DFhuBExVJAbJ/qqxqZdRY6Fu7/OezQ=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@1.1.0", "", { "dependencies": { "tslib": "^2.5.0" } }, "sha512-/jL/V1xdVRt5XppwiaEU8Etp5WHZj609n0xMTuehmCqdoOFbId1M+aEeDWZsQ+8JbEB/BJ6ynY2SlYmOaKtt8w=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-utf8": ["@smithy/util-utf8@1.1.0", "", { "dependencies": { "@smithy/util-buffer-from": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-p/MYV+JmqmPyjdgyN2UxAeYDj9cBqCjp0C/NsTWnnjoZUVqoeZ6IrW915L9CAKWVECgv9lVQGc4u/yz26/bI1A=="],
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"@browserbasehq/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
@@ -3601,6 +3801,8 @@
|
||||
|
||||
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"bl/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"cli-truncate/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"cli-truncate/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
@@ -3617,7 +3819,7 @@
|
||||
|
||||
"inquirer/ora/log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
|
||||
|
||||
"jest-diff/pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||
"jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"lint-staged/listr2/cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
|
||||
|
||||
@@ -3699,6 +3901,30 @@
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.817.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.817.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.816.0", "", { "dependencies": { "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ=="],
|
||||
|
||||
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.816.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/eventstream-codec/@aws-crypto/crc32": ["@aws-crypto/crc32@3.0.0", "", { "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", "tslib": "^1.11.1" } }, "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@1.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
@@ -3735,6 +3961,14 @@
|
||||
|
||||
"test-exclude/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ=="],
|
||||
|
||||
"@aws-sdk/client-sagemaker/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.817.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-retry": "^4.1.7", "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.14", "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/eventstream-codec/@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@3.0.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w=="],
|
||||
|
||||
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/eventstream-codec/@aws-crypto/crc32/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
|
||||
|
||||
"lint-staged/listr2/cli-truncate/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"lint-staged/listr2/log-update/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
|
||||
|
||||
Reference in New Issue
Block a user