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:
Emir Karabeg
2025-06-02 03:41:23 -07:00
committed by GitHub
parent 7d640823c3
commit d9e99a4fab
49 changed files with 11889 additions and 38 deletions

3
apps/sim/.gitignore vendored
View File

@@ -47,3 +47,6 @@ next-env.d.ts
# Sentry Config File
.env.sentry-build-plugin
# Uploads
/uploads

View File

@@ -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 })
}
}

View File

@@ -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 })
}
}

View 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 })
}
}

View 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 })
}
}

View 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 }
)
}
}

View 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 })
}
}

View 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 })
}
}

View 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 }
)
}
}

View File

@@ -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}

View File

@@ -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',

View File

@@ -13,37 +13,55 @@ 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 */
.react-flow__node-group:hover,
.hover-highlight {
border-color: #1e293b !important;
}
/* Ensure hover effects work well */
.group-node-container:hover .react-flow__resize-control.bottom-right {
opacity: 1 !important;
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 {
transform-origin: center;
position: absolute;
}
/* Remove default border from React Flow group nodes */
.react-flow__node-group {
border: none;
@@ -51,15 +69,15 @@ const ParallelNodeStyles: React.FC = () => {
outline: none;
box-shadow: none;
}
/* Ensure child nodes stay within parent bounds */
.react-flow__node[data-parent-node-id] .react-flow__handle {
z-index: 30;
}
/* 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'

View File

@@ -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)}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View 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>
)
}

View 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'}
/>
)
}

View 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>
)
}

View File

@@ -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>
)
}

View 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'} />
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View 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
}

View File

@@ -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>
)
}

View 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>
)
}

View 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}
/>
</>
)
}

View 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>
)
}

View File

@@ -0,0 +1,5 @@
import { Knowledge } from './knowledge'
export default function KnowledgePage() {
return <Knowledge />
}

View 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)',
},
],
}

View File

@@ -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,

View File

@@ -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>
)
}

View 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
}

View 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';

View 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");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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
}
]
}

View File

@@ -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`),
})
)

View 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
}
}

View File

@@ -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(),

View File

@@ -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

View File

@@ -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",

View File

@@ -0,0 +1,3 @@
import { knowledgeSearchTool } from './search'
export { knowledgeSearchTool }

View 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,
}
},
}

View 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
}

View File

@@ -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
View File

@@ -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=="],